hot-loading saml config from enterprise repo

This commit is contained in:
Dario Ghunney Ware 2025-03-27 16:37:38 +00:00
parent 845c81d942
commit 16a226fe5e
31 changed files with 297 additions and 806 deletions

12
META-INF/MANIFEST.MF Normal file
View File

@ -0,0 +1,12 @@
Manifest-Version: 1.0
Main-Class: org.springframework.boot.loader.launch.PropertiesLauncher
Start-Class: stirling.software.SPDF.SPDFApplication
Spring-Boot-Version: 3.4.3
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Build-Jdk-Spec: 17
Implementation-Title: Stirling-PDF
Implementation-Version: 0.43.1
Class-Path: stirling-pdf-enterprise-0.1.0-all.jar

View File

@ -10,10 +10,13 @@ plugins {
//id "nebula.lint" version "19.0.3"
id("org.panteleyev.jpackageplugin") version "1.6.1"
id "org.sonarqube" version "6.0.1.5171"
id "maven-publish"
}
import com.github.jk1.license.render.*
import java.time.Year
ext {
springBootVersion = "3.4.3"
pdfboxVersion = "3.0.4"
@ -32,6 +35,41 @@ java {
sourceCompatibility = JavaVersion.VERSION_17
}
jar {
enabled = true
manifest {
attributes "Implementation-Title": "Stirling-PDF",
"Implementation-Version": project.version
}
}
bootJar {
archiveClassifier.set("exec")
mainClass = "stirling.software.SPDF.SPDFApplication"
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
groupId = "stirling.software"
artifactId = "stirling-pdf"
version = project.version
}
}
}
processResources {
from("src/main/resources") {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
include "application.properties"
}
from("configs") {
include "settings.yml"
}
}
repositories {
mavenCentral()
maven { url = "https://build.shibboleth.net/maven/releases" }
@ -220,13 +258,12 @@ jpackage {
licenseFile = "LICENSE"
}
launch4j {
icon = "${projectDir}/src/main/resources/static/favicon.ico"
outfile="Stirling-PDF.exe"
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == "true") {
headerType = "gui"
} else {
headerType = "console"
@ -236,7 +273,7 @@ launch4j {
errTitle="Encountered error, Do you have Java 21?"
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == "true") {
variables=["BROWSER_OPEN=true", "STIRLING_PDF_DESKTOP_UI=true"]
} else {
variables=["BROWSER_OPEN=true"]
@ -256,7 +293,7 @@ launch4j {
spotless {
java {
target project.fileTree('src/main/java')
target project.fileTree("src/main/java")
googleJavaFormat("1.25.2").aosp().reorderImports(false)
@ -292,17 +329,17 @@ configurations.all {
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
}
dependencies {
implementation "org.springframework.boot:spring-boot-loader"
//tmp for security bumps
implementation 'ch.qos.logback:logback-core:1.5.17'
implementation 'ch.qos.logback:logback-classic:1.5.17'
implementation "ch.qos.logback:logback-core:1.5.17"
implementation "ch.qos.logback:logback-classic:1.5.17"
// Exclude vulnerable BouncyCastle version used in tableau
configurations.all {
exclude group: 'org.bouncycastle', module: 'bcpkix-jdk15on'
exclude group: 'org.bouncycastle', module: 'bcutil-jdk15on'
exclude group: 'org.bouncycastle', module: 'bcmail-jdk15on'
exclude group: "org.bouncycastle", module: "bcpkix-jdk15on"
exclude group: "org.bouncycastle", module: "bcutil-jdk15on"
exclude group: "org.bouncycastle", module: "bcmail-jdk15on"
}
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
@ -321,8 +358,8 @@ dependencies {
implementation "org.springframework.boot:spring-boot-starter-jetty:$springBootVersion"
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
implementation 'com.posthog.java:posthog:1.2.0'
implementation 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
implementation "com.posthog.java:posthog:1.2.0"
implementation "com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1"
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
@ -334,21 +371,23 @@ dependencies {
implementation "org.springframework.session:spring-session-core:3.4.2"
implementation "org.springframework:spring-jdbc:6.2.3"
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
implementation "com.unboundid.product.scim2:scim2-sdk-client:2.3.5"
// Don't upgrade h2database
runtimeOnly "com.h2database:h2:2.3.232"
runtimeOnly "org.postgresql:postgresql:42.7.5"
constraints {
implementation "org.opensaml:opensaml-core:$openSamlVersion"
implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion"
// constraints {
implementation "org.opensaml:opensaml-core-api:5.1.3"
// implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
// implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion"
// }
implementation ("org.springframework.security:spring-security-saml2-service-provider:$springSecuritySamlVersion") {
exclude group: "org.opensaml"
}
implementation "org.springframework.security:spring-security-saml2-service-provider:$springSecuritySamlVersion"
// implementation 'org.springframework.security:spring-security-core:$springSecuritySamlVersion'
implementation 'com.coveo:saml-client:5.0.0'
implementation "org.springframework.security:spring-security-core:$springSecuritySamlVersion"
// implementation 'com.coveo:saml-client:5.0.0'
}
implementation 'org.snakeyaml:snakeyaml-engine:2.9'
implementation "org.snakeyaml:snakeyaml-engine:2.9"
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
@ -396,13 +435,13 @@ dependencies {
}
// https://mvnrepository.com/artifact/technology.tabula/tabula
implementation ('technology.tabula:tabula:1.0.5') {
implementation ("technology.tabula:tabula:1.0.5") {
exclude group: "org.slf4j", module: "slf4j-simple"
exclude group: "org.bouncycastle", module: "bcprov-jdk15on"
exclude group: "com.google.code.gson", module: "gson"
}
implementation 'org.apache.pdfbox:jbig2-imageio:3.0.4'
implementation "org.apache.pdfbox:jbig2-imageio:3.0.4"
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
@ -416,13 +455,13 @@ dependencies {
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
implementation "com.fathzer:javaluator:3.0.5"
implementation 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
implementation "com.vladsch.flexmark:flexmark-html2md-converter:0.64.8"
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
compileOnly "org.projectlombok:lombok:$lombokVersion"
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
testRuntimeOnly 'org.mockito:mockito-inline:5.2.0'
testRuntimeOnly "org.mockito:mockito-inline:5.2.0"
}
tasks.withType(JavaCompile).configureEach {
@ -450,15 +489,6 @@ swaggerhubUpload {
oas = "3.0.0" // The version of the OpenAPI Specification you"re using
}
jar {
enabled = false
manifest {
attributes "Implementation-Title": "Stirling-PDF",
"Implementation-Version": project.version
}
}
tasks.named("test") {
useJUnitPlatform()
}

View File

@ -0,0 +1,10 @@
package stirling.software.SPDF.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
@ConditionalOnProperty(value = "enterpriseEdition.enabled", havingValue = "true")
@ImportResource("${enterpriseEdition.externalConfigLocation}")
public class ExternalPluginConfig {}

View File

@ -14,10 +14,10 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class PostHogConfig {
@Value("${posthog.api.key}")
@Value("${posthog.api.key:phc_fiR65u5j6qmXTYL56MNrLZSWqLaDW74OrZH0Insd2xq}")
private String posthogApiKey;
@Value("${posthog.host}")
@Value("${posthog.host:https://eu.i.posthog.com}")
private String posthogHost;
private PostHog postHogClient;

View File

@ -1,6 +1,5 @@
package stirling.software.SPDF.config;
import java.io.IOException;
import java.util.Properties;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
@ -12,8 +11,7 @@ import org.springframework.core.io.support.PropertySourceFactory;
public class YamlPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource)
throws IOException {
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();

View File

@ -1,77 +1 @@
// package stirling.software.SPDF.config.fingerprint;
//
// import java.security.MessageDigest;
// import java.security.NoSuchAlgorithmException;
//
// import org.springframework.stereotype.Component;
//
// import jakarta.servlet.http.HttpServletRequest;
//
// @Component
// public class FingerprintGenerator {
//
// public String generateFingerprint(HttpServletRequest request) {
// if (request == null) {
// return "";
// }
// StringBuilder fingerprintBuilder = new StringBuilder();
//
// // Add IP address
// fingerprintBuilder.append(request.getRemoteAddr());
//
// // Add X-Forwarded-For header if present (for clients behind proxies)
// String forwardedFor = request.getHeader("X-Forwarded-For");
// if (forwardedFor != null) {
// fingerprintBuilder.append(forwardedFor);
// }
//
// // Add User-Agent
// String userAgent = request.getHeader("User-Agent");
// if (userAgent != null) {
// fingerprintBuilder.append(userAgent);
// }
//
// // Add Accept-Language header
// String acceptLanguage = request.getHeader("Accept-Language");
// if (acceptLanguage != null) {
// fingerprintBuilder.append(acceptLanguage);
// }
//
// // Add Accept header
// String accept = request.getHeader("Accept");
// if (accept != null) {
// fingerprintBuilder.append(accept);
// }
//
// // Add Connection header
// String connection = request.getHeader("Connection");
// if (connection != null) {
// fingerprintBuilder.append(connection);
// }
//
// // Add server port
// fingerprintBuilder.append(request.getServerPort());
//
// // Add secure flag
// fingerprintBuilder.append(request.isSecure());
//
// // Generate a hash of the fingerprint
// return generateHash(fingerprintBuilder.toString());
// }
//
// private String generateHash(String input) {
// try {
// MessageDigest digest = MessageDigest.getInstance("SHA-256");
// byte[] hash = digest.digest(input.getBytes());
// StringBuilder hexString = new StringBuilder();
// for (byte b : hash) {
// String hex = Integer.toHexString(0xff & b);
// if (hex.length() == 1) hexString.append('0');
// hexString.append(hex);
// }
// return hexString.toString();
// } catch (NoSuchAlgorithmException e) {
// throw new RuntimeException("Failed to generate fingerprint hash", e);
// }
// }
// }

View File

@ -13,22 +13,17 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import com.coveo.saml.SamlClient;
import com.coveo.saml.SamlException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.SPDFApplication;
import stirling.software.SPDF.config.security.saml2.CertificateUtils;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
import stirling.software.SPDF.model.provider.KeycloakProvider;
import stirling.software.SPDF.utils.CertificateUtils;
import stirling.software.SPDF.utils.UrlUtils;
@Slf4j
@ -79,10 +74,12 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
String registrationId = samlConf.getRegistrationId();
CustomSaml2AuthenticatedPrincipal principal =
(CustomSaml2AuthenticatedPrincipal) samlAuthentication.getPrincipal();
// CustomSaml2AuthenticatedPrincipal principal =
// (CustomSaml2AuthenticatedPrincipal) samlAuthentication.getPrincipal();
Object principal = samlAuthentication.getPrincipal();
String nameIdValue = principal.name();
String nameIdValue = principal.toString();
// String nameIdValue = principal.name();
try {
// Read certificate from the resource
@ -93,17 +90,18 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
certificates.add(certificate);
// Construct URLs required for SAML configuration
SamlClient samlClient = getSamlClient(registrationId, samlConf, certificates);
// SamlClient samlClient = getSamlClient(registrationId, samlConf,
// certificates);
// Read private key for service provider
Resource privateKeyResource = samlConf.getPrivateKey();
RSAPrivateKey privateKey = CertificateUtils.readPrivateKey(privateKeyResource);
// Set service provider keys for the SamlClient
samlClient.setSPKeys(certificate, privateKey);
// samlClient.setSPKeys(certificate, privateKey);
// Redirect to identity provider for logout
samlClient.redirectToIdentityProvider(response, null, nameIdValue);
// samlClient.redirectToIdentityProvider(response, null, nameIdValue);
} catch (Exception e) {
log.error(
"Error retrieving logout URL from Provider {} for user {}",
@ -172,30 +170,30 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
}
}
private static SamlClient getSamlClient(
String registrationId, SAML2 samlConf, List<X509Certificate> certificates)
throws SamlException {
String serverUrl =
SPDFApplication.getStaticBaseUrl() + ":" + SPDFApplication.getStaticPort();
String relyingPartyIdentifier =
serverUrl + "/saml2/service-provider-metadata/" + registrationId;
String assertionConsumerServiceUrl = serverUrl + "/login/saml2/sso/" + registrationId;
String idpSLOUrl = samlConf.getIdpSingleLogoutUrl();
String idpIssuer = samlConf.getIdpIssuer();
// Create SamlClient instance for SAML logout
return new SamlClient(
relyingPartyIdentifier,
assertionConsumerServiceUrl,
idpSLOUrl,
idpIssuer,
certificates,
SamlClient.SamlIdpBinding.POST);
}
// private static SamlClient getSamlClient(
// String registrationId, SAML2 samlConf, List<X509Certificate> certificates)
// throws SamlException {
// String serverUrl =
// SPDFApplication.getStaticBaseUrl() + ":" + SPDFApplication.getStaticPort();
//
// String relyingPartyIdentifier =
// serverUrl + "/saml2/service-provider-metadata/" + registrationId;
//
// String assertionConsumerServiceUrl = serverUrl + "/login/saml2/sso/" + registrationId;
//
// String idpSLOUrl = samlConf.getIdpSingleLogoutUrl();
//
// String idpIssuer = samlConf.getIdpIssuer();
//
// // Create SamlClient instance for SAML logout
// return new SamlClient(
// relyingPartyIdentifier,
// assertionConsumerServiceUrl,
// idpSLOUrl,
// idpIssuer,
// certificates,
// SamlClient.SamlIdpBinding.POST);
// }
/**
* Handles different error scenarios during logout. Will return a <code>String</code> containing

View File

@ -0,0 +1,14 @@
package stirling.software.SPDF.config.security;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml5AuthenticationRequestResolver;
public interface SAML2ConfigurationInterface {
OpenSaml5AuthenticationProvider authenticationProvider();
RelyingPartyRegistrationRepository relyingPartyRegistrations();
OpenSaml5AuthenticationRequestResolver authenticationRequestResolver();
}

View File

@ -8,7 +8,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@ -17,9 +16,9 @@ import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml5AuthenticationRequestResolver;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
@ -33,9 +32,6 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationFailureHandler;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationSuccessHandler;
import stirling.software.SPDF.config.security.saml2.CustomSaml2ResponseAuthenticationConverter;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.User;
@ -61,8 +57,10 @@ public class SecurityConfiguration {
private final SessionPersistentRegistry sessionRegistry;
private final PersistentLoginRepository persistentLoginRepository;
private final GrantedAuthoritiesMapper oAuth2userAuthoritiesMapper;
private final RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations;
private final OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver;
private final OpenSaml5AuthenticationRequestResolver saml2AuthenticationRequestResolver;
private final OpenSaml5AuthenticationProvider saml2AuthenticationProvider;
public SecurityConfiguration(
PersistentLoginRepository persistentLoginRepository,
@ -76,10 +74,9 @@ public class SecurityConfiguration {
FirstLoginFilter firstLoginFilter,
SessionPersistentRegistry sessionRegistry,
@Autowired(required = false) GrantedAuthoritiesMapper oAuth2userAuthoritiesMapper,
@Autowired(required = false)
RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations,
@Autowired(required = false)
OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver) {
RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations,
OpenSaml5AuthenticationRequestResolver saml2AuthenticationRequestResolver,
OpenSaml5AuthenticationProvider saml2AuthenticationProvider) {
this.userDetailsService = userDetailsService;
this.userService = userService;
this.loginEnabledValue = loginEnabledValue;
@ -93,6 +90,7 @@ public class SecurityConfiguration {
this.oAuth2userAuthoritiesMapper = oAuth2userAuthoritiesMapper;
this.saml2RelyingPartyRegistrations = saml2RelyingPartyRegistrations;
this.saml2AuthenticationRequestResolver = saml2AuthenticationRequestResolver;
this.saml2AuthenticationProvider = saml2AuthenticationProvider;
}
@Bean
@ -256,33 +254,44 @@ public class SecurityConfiguration {
// Handle SAML
if (applicationProperties.getSecurity().isSaml2Active() && runningEE) {
// Configure the authentication provider
OpenSaml4AuthenticationProvider authenticationProvider =
new OpenSaml4AuthenticationProvider();
authenticationProvider.setResponseAuthenticationConverter(
new CustomSaml2ResponseAuthenticationConverter(userService));
http.authenticationProvider(authenticationProvider)
// OpenSaml4AuthenticationProvider authenticationProvider =
// new OpenSaml4AuthenticationProvider();
// authenticationProvider.setResponseAuthenticationConverter(
// new
// CustomSaml2ResponseAuthenticationConverter(userService));
http
// http.authenticationProvider(authenticationProvider)
.saml2Login(
saml2 -> {
try {
saml2.loginPage("/saml2")
.relyingPartyRegistrationRepository(
saml2RelyingPartyRegistrations)
.authenticationManager(
new ProviderManager(authenticationProvider))
.successHandler(
new CustomSaml2AuthenticationSuccessHandler(
loginAttemptService,
applicationProperties,
userService))
.failureHandler(
new CustomSaml2AuthenticationFailureHandler())
.authenticationRequestResolver(
saml2AuthenticationRequestResolver);
} catch (Exception e) {
log.error("Error configuring SAML 2 login", e);
throw new RuntimeException(e);
}
});
saml2 -> {
try {
saml2.loginPage("/saml2")
.relyingPartyRegistrationRepository(
saml2RelyingPartyRegistrations)
//
// .authenticationManager(
//
// new ProviderManager(authenticationProvider))
//
// .successHandler(
//
// new CustomSaml2AuthenticationSuccessHandler(
//
// loginAttemptService,
//
// applicationProperties,
//
// userService))
//
// .failureHandler(
//
// new CustomSaml2AuthenticationFailureHandler())
.authenticationRequestResolver(
saml2AuthenticationRequestResolver);
} catch (Exception e) {
log.error("Error configuring SAML 2 login", e);
throw new RuntimeException(e);
}
});
}
} else {
log.debug("SAML 2 login is not enabled. Using default.");

View File

@ -25,13 +25,11 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
import stirling.software.SPDF.model.User;
@Slf4j
@ -151,11 +149,12 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
loginMethod = LoginMethod.OAUTH2USER;
OAUTH2 oAuth = securityProp.getOauth2();
blockRegistration = oAuth != null && oAuth.getBlockRegistration();
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
username = saml2User.name();
loginMethod = LoginMethod.SAML2USER;
SAML2 saml2 = securityProp.getSaml2();
blockRegistration = saml2 != null && saml2.getBlockRegistration();
// } else if (principal instanceof CustomSaml2AuthenticatedPrincipal
// saml2User) {
// username = saml2User.name();
// loginMethod = LoginMethod.SAML2USER;
// SAML2 saml2 = securityProp.getSaml2();
// blockRegistration = saml2 != null && saml2.getBlockRegistration();
} else if (principal instanceof String stringUser) {
username = stringUser;
loginMethod = LoginMethod.STRINGUSER;

View File

@ -2,7 +2,13 @@ package stirling.software.SPDF.config.security;
import java.io.IOException;
import java.sql.SQLException;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.springframework.context.MessageSource;
@ -23,10 +29,13 @@ import org.springframework.transaction.annotation.Transactional;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
import stirling.software.SPDF.model.*;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.AuthenticationType;
import stirling.software.SPDF.model.Authority;
import stirling.software.SPDF.model.Role;
import stirling.software.SPDF.model.User;
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
import stirling.software.SPDF.repository.AuthorityRepository;
import stirling.software.SPDF.repository.UserRepository;
@ -389,8 +398,9 @@ public class UserService implements UserServiceInterface {
usernameP = detailsUser.getUsername();
} else if (principal instanceof OAuth2User oAuth2User) {
usernameP = oAuth2User.getName();
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
usernameP = saml2User.name();
// } else if (principal instanceof
// CustomSaml2AuthenticatedPrincipal saml2User) {
// usernameP = saml2User.name();
} else if (principal instanceof String stringUser) {
usernameP = stringUser;
}
@ -409,8 +419,8 @@ public class UserService implements UserServiceInterface {
} else if (principal instanceof OAuth2User oAuth2User) {
return oAuth2User.getAttribute(
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
return saml2User.name();
// } else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
// return saml2User.name();
} else if (principal instanceof String stringUser) {
return stringUser;
}

View File

@ -1,27 +0,0 @@
package stirling.software.SPDF.config.security.saml2;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
public record CustomSaml2AuthenticatedPrincipal(
String name,
Map<String, List<Object>> attributes,
String nameId,
List<String> sessionIndexes)
implements Saml2AuthenticatedPrincipal, Serializable {
@Override
public String getName() {
return this.name;
}
@Override
public Map<String, List<Object>> getAttributes() {
return this.attributes;
}
}

View File

@ -1,41 +0,0 @@
package stirling.software.SPDF.config.security.saml2;
import java.io.IOException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.authentication.ProviderNotFoundException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception)
throws IOException {
log.error("Authentication error", exception);
if (exception instanceof Saml2AuthenticationException) {
Saml2Error error = ((Saml2AuthenticationException) exception).getSaml2Error();
getRedirectStrategy()
.sendRedirect(request, response, "/login?errorOAuth=" + error.getErrorCode());
} else if (exception instanceof ProviderNotFoundException) {
getRedirectStrategy()
.sendRedirect(
request,
response,
"/login?errorOAuth=not_authentication_provider_found");
}
}
}

View File

@ -1,127 +0,0 @@
package stirling.software.SPDF.config.security.saml2;
import java.io.IOException;
import java.sql.SQLException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.SavedRequest;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.LoginAttemptService;
import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
import stirling.software.SPDF.model.AuthenticationType;
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
import stirling.software.SPDF.utils.RequestUriUtils;
@AllArgsConstructor
@Slf4j
public class CustomSaml2AuthenticationSuccessHandler
extends SavedRequestAwareAuthenticationSuccessHandler {
private LoginAttemptService loginAttemptService;
private ApplicationProperties applicationProperties;
private UserService userService;
@Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
Object principal = authentication.getPrincipal();
log.debug("Starting SAML2 authentication success handling");
if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2Principal) {
String username = saml2Principal.name();
log.debug("Authenticated principal found for user: {}", username);
HttpSession session = request.getSession(false);
String contextPath = request.getContextPath();
SavedRequest savedRequest =
(session != null)
? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
: null;
log.debug(
"Session exists: {}, Saved request exists: {}",
session != null,
savedRequest != null);
if (savedRequest != null
&& !RequestUriUtils.isStaticResource(
contextPath, savedRequest.getRedirectUrl())) {
log.debug(
"Valid saved request found, redirecting to original destination: {}",
savedRequest.getRedirectUrl());
super.onAuthenticationSuccess(request, response, authentication);
} else {
SAML2 saml2 = applicationProperties.getSecurity().getSaml2();
log.debug(
"Processing SAML2 authentication with autoCreateUser: {}",
saml2.getAutoCreateUser());
if (loginAttemptService.isBlocked(username)) {
log.debug("User {} is blocked due to too many login attempts", username);
if (session != null) {
session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST");
}
throw new LockedException(
"Your account has been locked due to too many failed login attempts.");
}
boolean userExists = userService.usernameExistsIgnoreCase(username);
boolean hasPassword = userExists && userService.hasPassword(username);
boolean isSSOUser =
userExists
&& userService.isAuthenticationTypeByUsername(
username, AuthenticationType.SSO);
log.debug(
"User status - Exists: {}, Has password: {}, Is SSO user: {}",
userExists,
hasPassword,
isSSOUser);
if (userExists && hasPassword && !isSSOUser && saml2.getAutoCreateUser()) {
log.debug(
"User {} exists with password but is not SSO user, redirecting to logout",
username);
response.sendRedirect(
contextPath + "/logout?oAuth2AuthenticationErrorWeb=true");
return;
}
try {
if (saml2.getBlockRegistration() && !userExists) {
log.debug("Registration blocked for new user: {}", username);
response.sendRedirect(
contextPath + "/login?errorOAuth=oAuth2AdminBlockedUser");
return;
}
log.debug("Processing SSO post-login for user: {}", username);
userService.processSSOPostLogin(username, saml2.getAutoCreateUser());
log.debug("Successfully processed authentication for user: {}", username);
response.sendRedirect(contextPath + "/");
} catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) {
log.debug(
"Invalid username detected for user: {}, redirecting to logout",
username);
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
}
}
} else {
log.debug("Non-SAML2 principal detected, delegating to parent handler");
super.onAuthenticationSuccess(request, response, authentication);
}
}
}

View File

@ -1,116 +0,0 @@
package stirling.software.SPDF.config.security.saml2;
import java.util.*;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.Attribute;
import org.opensaml.saml.saml2.core.AttributeStatement;
import org.opensaml.saml.saml2.core.AuthnStatement;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.User;
@Slf4j
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
public class CustomSaml2ResponseAuthenticationConverter
implements Converter<ResponseToken, Saml2Authentication> {
private final UserService userService;
public CustomSaml2ResponseAuthenticationConverter(UserService userService) {
this.userService = userService;
}
private Map<String, List<Object>> extractAttributes(Assertion assertion) {
Map<String, List<Object>> attributes = new HashMap<>();
for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) {
for (Attribute attribute : attributeStatement.getAttributes()) {
String attributeName = attribute.getName();
List<Object> values = new ArrayList<>();
for (XMLObject xmlObject : attribute.getAttributeValues()) {
// Get the text content directly
String value = xmlObject.getDOM().getTextContent();
if (value != null && !value.trim().isEmpty()) {
values.add(value);
}
}
if (!values.isEmpty()) {
// Store with both full URI and last part of the URI
attributes.put(attributeName, values);
String shortName = attributeName.substring(attributeName.lastIndexOf('/') + 1);
attributes.put(shortName, values);
}
}
}
return attributes;
}
@Override
public Saml2Authentication convert(ResponseToken responseToken) {
Assertion assertion = responseToken.getResponse().getAssertions().get(0);
Map<String, List<Object>> attributes = extractAttributes(assertion);
// Debug log with actual values
log.debug("Extracted SAML Attributes: {}", attributes);
// Try to get username/identifier in order of preference
String userIdentifier;
if (hasAttribute(attributes, "username")) {
userIdentifier = getFirstAttributeValue(attributes, "username");
} else if (hasAttribute(attributes, "emailaddress")) {
userIdentifier = getFirstAttributeValue(attributes, "emailaddress");
} else if (hasAttribute(attributes, "name")) {
userIdentifier = getFirstAttributeValue(attributes, "name");
} else if (hasAttribute(attributes, "upn")) {
userIdentifier = getFirstAttributeValue(attributes, "upn");
} else if (hasAttribute(attributes, "uid")) {
userIdentifier = getFirstAttributeValue(attributes, "uid");
} else {
userIdentifier = assertion.getSubject().getNameID().getValue();
}
// Rest of your existing code...
Optional<User> userOpt = userService.findByUsernameIgnoreCase(userIdentifier);
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_USER");
if (userOpt.isPresent()) {
User user = userOpt.get();
simpleGrantedAuthority =
new SimpleGrantedAuthority(userService.findRole(user).getAuthority());
}
List<String> sessionIndexes = new ArrayList<>();
for (AuthnStatement authnStatement : assertion.getAuthnStatements()) {
sessionIndexes.add(authnStatement.getSessionIndex());
}
CustomSaml2AuthenticatedPrincipal principal =
new CustomSaml2AuthenticatedPrincipal(
userIdentifier, attributes, userIdentifier, sessionIndexes);
return new Saml2Authentication(
principal,
responseToken.getToken().getSaml2Response(),
List.of(simpleGrantedAuthority));
}
private boolean hasAttribute(Map<String, List<Object>> attributes, String name) {
return attributes.containsKey(name) && !attributes.get(name).isEmpty();
}
private String getFirstAttributeValue(Map<String, List<Object>> attributes, String name) {
List<Object> values = attributes.get(name);
return values != null && !values.isEmpty() ? values.get(0).toString() : null;
}
}

View File

@ -1,176 +0,0 @@
package stirling.software.SPDF.config.security.saml2;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.UUID;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
@Configuration
@Slf4j
@ConditionalOnProperty(value = "security.saml2.enabled", havingValue = "true")
public class SAML2Configuration {
private final ApplicationProperties applicationProperties;
public SAML2Configuration(ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
}
@Bean
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getIdpCert());
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
Resource privateKeyResource = samlConf.getPrivateKey();
Resource certificateResource = samlConf.getSpCert();
Saml2X509Credential signingCredential =
new Saml2X509Credential(
CertificateUtils.readPrivateKey(privateKeyResource),
CertificateUtils.readCertificate(certificateResource),
Saml2X509CredentialType.SIGNING);
RelyingPartyRegistration rp =
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
.signingX509Credentials(c -> c.add(signingCredential))
.entityId(samlConf.getIdpIssuer())
.singleLogoutServiceBinding(Saml2MessageBinding.POST)
.singleLogoutServiceLocation(samlConf.getIdpSingleLogoutUrl())
.singleLogoutServiceResponseLocation("http://localhost:8080/login")
.assertionConsumerServiceBinding(Saml2MessageBinding.POST)
.assertionConsumerServiceLocation(
"{baseUrl}/login/saml2/sso/{registrationId}")
.assertingPartyMetadata(
metadata ->
metadata.entityId(samlConf.getIdpIssuer())
.verificationX509Credentials(
c -> c.add(verificationCredential))
.singleSignOnServiceBinding(
Saml2MessageBinding.POST)
.singleSignOnServiceLocation(
samlConf.getIdpSingleLoginUrl())
.singleLogoutServiceBinding(
Saml2MessageBinding.POST)
.singleLogoutServiceLocation(
samlConf.getIdpSingleLogoutUrl())
.wantAuthnRequestsSigned(true))
.build();
return new InMemoryRelyingPartyRegistrationRepository(rp);
}
@Bean
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
public OpenSaml4AuthenticationRequestResolver authenticationRequestResolver(
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
OpenSaml4AuthenticationRequestResolver resolver =
new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository);
resolver.setAuthnRequestCustomizer(
customizer -> {
HttpServletRequest request = customizer.getRequest();
AuthnRequest authnRequest = customizer.getAuthnRequest();
HttpSessionSaml2AuthenticationRequestRepository requestRepository =
new HttpSessionSaml2AuthenticationRequestRepository();
AbstractSaml2AuthenticationRequest saml2AuthenticationRequest =
requestRepository.loadAuthenticationRequest(request);
if (saml2AuthenticationRequest != null) {
String sessionId = request.getSession(false).getId();
log.debug(
"Retrieving SAML 2 authentication request ID from the current HTTP session {}",
sessionId);
String authenticationRequestId = saml2AuthenticationRequest.getId();
if (!authenticationRequestId.isBlank()) {
authnRequest.setID(authenticationRequestId);
} else {
log.warn(
"No authentication request found for HTTP session {}. Generating new ID",
sessionId);
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
}
} else {
log.debug("Generating new authentication request ID");
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
}
logAuthnRequestDetails(authnRequest);
logHttpRequestDetails(request);
});
return resolver;
}
private static void logAuthnRequestDetails(AuthnRequest authnRequest) {
String message =
"""
AuthnRequest:
ID: {}
Issuer: {}
IssueInstant: {}
AssertionConsumerService (ACS) URL: {}
""";
log.debug(
message,
authnRequest.getID(),
authnRequest.getIssuer() != null ? authnRequest.getIssuer().getValue() : null,
authnRequest.getIssueInstant(),
authnRequest.getAssertionConsumerServiceURL());
if (authnRequest.getNameIDPolicy() != null) {
log.debug("NameIDPolicy Format: {}", authnRequest.getNameIDPolicy().getFormat());
}
}
private static void logHttpRequestDetails(HttpServletRequest request) {
log.debug("HTTP Headers: ");
Collections.list(request.getHeaderNames())
.forEach(
headerName ->
log.debug("{}: {}", headerName, request.getHeader(headerName)));
String message =
"""
HTTP Request Method: {}
Session ID: {}
Request Path: {}
Query String: {}
Remote Address: {}
SAML Request Parameters:
SAMLRequest: {}
RelayState: {}
""";
log.debug(
message,
request.getMethod(),
request.getSession().getId(),
request.getRequestURI(),
request.getQueryString(),
request.getRemoteAddr(),
request.getParameter("SAMLRequest"),
request.getParameter("RelayState"));
}
}

View File

@ -0,0 +1,52 @@
package stirling.software.SPDF.config.security.session;
import org.opensaml.core.config.InitializationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml5AuthenticationRequestResolver;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.SAML2ConfigurationInterface;
@Slf4j
@Configuration
public class SAML2Configs {
@PostConstruct
public void initOpenSAML() {
try {
InitializationService.initialize();
log.info("✅ OpenSAML initialized successfully");
} catch (Throwable e) {
log.error("❌ OpenSAML initialization failed: " + e.getMessage());
e.printStackTrace();
}
}
@Bean
public RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations(
@Autowired(required = false) SAML2ConfigurationInterface saml2Config) {
log.info("Loaded jar from enterprise! {}", saml2Config.toString());
return saml2Config.relyingPartyRegistrations();
}
@Bean
public OpenSaml5AuthenticationProvider saml2AuthenticationProvider(
@Autowired(required = false) SAML2ConfigurationInterface saml2Config) {
log.info("Loaded jar from enterprise! {}", saml2Config.toString());
return saml2Config.authenticationProvider();
}
@Bean
public OpenSaml5AuthenticationRequestResolver saml2AuthenticationRequestResolver(
@Autowired(required = false) SAML2ConfigurationInterface saml2Config) {
log.info("Loaded jar from enterprise! {}", saml2Config.toString());
return saml2Config.authenticationRequestResolver();
}
}

View File

@ -1,7 +1,12 @@
package stirling.software.SPDF.config.security.session;
import java.time.Duration;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.session.SessionInformation;
@ -12,7 +17,6 @@ import org.springframework.stereotype.Component;
import jakarta.transaction.Transactional;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.model.SessionEntity;
@Component
@ -46,9 +50,9 @@ public class SessionPersistentRegistry implements SessionRegistry {
if (principal instanceof UserDetails detailsUser) {
principalName = detailsUser.getUsername();
} else if (principal instanceof OAuth2User oAuth2User) {
principalName = oAuth2User.getName();
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
principalName = saml2User.name();
// principalName = oAuth2User.getName();
// } else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
// principalName = saml2User.name();
} else if (principal instanceof String stringUser) {
principalName = stringUser;
}
@ -78,8 +82,8 @@ public class SessionPersistentRegistry implements SessionRegistry {
principalName = detailsUser.getUsername();
} else if (principal instanceof OAuth2User oAuth2User) {
principalName = oAuth2User.getName();
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
principalName = saml2User.name();
// } else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
// principalName = saml2User.name();
} else if (principal instanceof String stringUser) {
principalName = stringUser;
}

View File

@ -30,7 +30,6 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.model.AuthenticationType;
import stirling.software.SPDF.model.Role;
@ -301,8 +300,9 @@ public class UserController {
userNameP = detailsUser.getUsername();
} else if (principal instanceof OAuth2User oAuth2User) {
userNameP = oAuth2User.getName();
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
userNameP = saml2User.name();
// } else if (principal instanceof
// CustomSaml2AuthenticatedPrincipal saml2User) {
// userNameP = saml2User.name();
} else if (principal instanceof String stringUser) {
userNameP = stringUser;
}

View File

@ -111,9 +111,9 @@ public class AutoSplitPdfController {
summary = "Auto split PDF pages into separate documents",
description =
"This endpoint accepts a PDF file, scans each page for a specific QR code, and"
+ " splits the document at the QR code boundaries. The output is a zip file"
+ " containing each separate PDF document. Input:PDF Output:ZIP-PDF"
+ " Type:SISO")
+ " splits the document at the QR code boundaries. The output is a zip file"
+ " containing each separate PDF document. Input:PDF Output:ZIP-PDF"
+ " Type:SISO")
public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request)
throws IOException {
MultipartFile file = request.getFileInput();

View File

@ -119,7 +119,7 @@ public class CompressController {
summary = "Optimize PDF file",
description =
"This endpoint accepts a PDF file and optimizes it based on the provided"
+ " parameters. Input:PDF Output:PDF Type:SISO")
+ " parameters. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> optimizePdf(@ModelAttribute OptimizePdfRequest request)
throws Exception {
MultipartFile inputFile = request.getFileInput();
@ -222,7 +222,7 @@ public class CompressController {
if (pdfBytes.length > inputFileSize) {
log.warn(
"Optimized file is larger than the original. Returning the original file"
+ " instead.");
+ " instead.");
finalFile = tempInputFile;
}

View File

@ -130,8 +130,8 @@ public class CertSignController {
summary = "Sign PDF with a Digital Certificate",
description =
"This endpoint accepts a PDF file, a digital certificate and related"
+ " information to sign the PDF. It then returns the digitally signed PDF"
+ " file. Input:PDF Output:PDF Type:SISO")
+ " information to sign the PDF. It then returns the digitally signed PDF"
+ " file. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
throws Exception {
MultipartFile pdf = request.getFileInput();

View File

@ -45,7 +45,7 @@ public class SanitizeController {
summary = "Sanitize a PDF file",
description =
"This endpoint processes a PDF file and removes specific elements based on the"
+ " provided options. Input:PDF Output:PDF Type:SISO")
+ " provided options. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> sanitizePDF(@ModelAttribute SanitizePdfRequest request)
throws IOException {
MultipartFile inputFile = request.getFileInput();

View File

@ -29,7 +29,6 @@ import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security;
@ -340,10 +339,10 @@ public class AccountWebController {
username = oAuth2User.getName();
model.addAttribute("oAuth2Login", true);
}
if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
username = saml2User.name();
model.addAttribute("saml2Login", true);
}
// if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
// username = saml2User.name();
// model.addAttribute("saml2Login", true);
// }
if (username != null) {
// Fetch user details from the database
Optional<User> user = userRepository.findByUsernameIgnoreCaseWithSettings(username);

View File

@ -4,6 +4,8 @@ import lombok.Getter;
@Getter
public enum UsernameAttribute {
UID("uid"),
MAIL("mail"),
EMAIL("email"),
LOGIN("login"),
PROFILE("profile"),

View File

@ -1,4 +1,4 @@
package stirling.software.SPDF.config.security.saml2;
package stirling.software.SPDF.utils;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;

View File

@ -1,30 +0,0 @@
package stirling.software.SPDF.utils;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
@Slf4j
public class ClassLoaderUtil {
public static ClassLoader getClassLoader(String url) {
try {
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
if (!method.canAccess(method)) {
method.setAccessible(true);
}
URLClassLoader classLoader = new URLClassLoader(new URL[]{}, ClassLoader.getSystemClassLoader());
method.invoke(classLoader, new URL(url));
return classLoader;
} catch (NoSuchMethodException | MalformedURLException | IllegalAccessException | InvocationTargetException e) {
log.error("Error loading class from {}", url, e);
throw new RuntimeException(e); // todo: more specific exception? Maybe ClassNotFoundException
}
}
}

View File

@ -1,31 +0,0 @@
package stirling.software.SPDF.utils;
import lombok.SneakyThrows;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class PluginImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
private static final String TARGET_URL = "file:/stirling-pdf-enterprise-0.1.0.jar";
private final String pluginClass;
public PluginImportBeanDefinitionRegistrar(String pluginClass) {
this.pluginClass = pluginClass;
}
@SneakyThrows
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
ClassLoader classLoader = ClassLoaderUtil.getClassLoader(TARGET_URL);
Class<?> clazz = classLoader.loadClass(pluginClass);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
BeanDefinition beanDefinition = builder.getBeanDefinition();
registry.registerBeanDefinition(clazz.getName(), beanDefinition);
}
}

View File

@ -1,31 +0,0 @@
package stirling.software.SPDF.utils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class SpringUtil implements ApplicationContextAware {
private DefaultListableBeanFactory defaultListableBeanFactory;
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
this.defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
}
public void registerBean(String beanName, Class<?> clazz) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getRawBeanDefinition());
}
public Object getBean(String name) {
return applicationContext.getBean(name);
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="saml2Configuration" class="stirling.software.spdf.enterprise.config.security.sso.SAML2Configuration" />
</beans>

View File

@ -65,6 +65,7 @@ enterpriseEdition:
enabled: false # set to 'true' to enable enterprise edition
key: 00000000-0000-0000-0000-000000000000
SSOAutoLogin: false # Enable to auto login to first provided SSO
externalConfigLocation: classpath:context.xml
CustomMetadata:
autoUpdateMetadata: false # set to 'true' to automatically update metadata with below values
author: username # supports text such as 'John Doe' or types such as username to autopopulate with user's username