more fixes

This commit is contained in:
Anthony Stirling 2024-11-29 08:43:57 +00:00
parent b4837df76c
commit 5171088fca
13 changed files with 324 additions and 124 deletions

View File

@ -21,6 +21,8 @@ ext {
imageioVersion = "3.12.0"
lombokVersion = "1.18.36"
bouncycastleVersion = "1.79"
springSecuritySamlVersion = "6.4.1"
openSamlVersion = "4.3.2"
}
group = "stirling.software"
@ -144,17 +146,18 @@ dependencies {
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
implementation 'org.springframework.security:spring-security-saml2-service-provider:6.4.1'
implementation "org.springframework.session:spring-session-core:$springBootVersion"
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
// Don't upgrade h2database
runtimeOnly "com.h2database:h2:2.3.232"
constraints {
implementation "org.opensaml:opensaml-core"
implementation "org.opensaml:opensaml-saml-api"
implementation "org.opensaml:opensaml-saml-impl"
implementation "org.opensaml:opensaml-core:$openSamlVersion"
implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion"
}
implementation "org.springframework.security:spring-security-saml2-service-provider"
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'

View File

@ -3,13 +3,14 @@ package stirling.software.SPDF.EE;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties;
@Configuration
@Lazy
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class EEAppConfig {

View File

@ -25,9 +25,10 @@ public class LicenseKeyChecker {
KeygenLicenseVerifier licenseService, ApplicationProperties applicationProperties) {
this.licenseService = licenseService;
this.applicationProperties = applicationProperties;
this.checkLicense();
}
@Scheduled(fixedRate = 604800000, initialDelay = 1000) // 7 days in milliseconds
@Scheduled(fixedRate = 604800000) // 7 days in milliseconds
public void checkLicensePeriodically() {
checkLicense();
}

View File

@ -7,7 +7,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
import stirling.software.SPDF.model.ApplicationProperties;
@ -77,7 +76,4 @@ public class InitialSecuritySetup {
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
}
}
}

View File

@ -1,17 +1,15 @@
package stirling.software.SPDF.config.security;
import java.io.BufferedReader;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.*;
import org.opensaml.saml.common.assertion.ValidationContext;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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.core.io.Resource;
import org.springframework.security.authentication.AuthenticationProvider;
@ -31,8 +29,6 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import org.springframework.security.saml2.core.Saml2ErrorCodes;
import org.springframework.security.saml2.core.Saml2ResponseValidatorResult;
import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
@ -41,23 +37,17 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
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.Saml2WebSsoAuthenticationRequestFilter;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
@ -81,6 +71,7 @@ import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
@EnableWebSecurity
@EnableMethodSecurity
@Slf4j
@DependsOn("runningEE")
public class SecurityConfiguration {
@Autowired private CustomUserDetailsService userDetailsService;
@ -100,7 +91,6 @@ public class SecurityConfiguration {
@Qualifier("runningEE")
public boolean runningEE;
@Autowired ApplicationProperties applicationProperties;
@Autowired private UserAuthenticationFilter userAuthenticationFilter;
@ -112,10 +102,10 @@ public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
if (applicationProperties.getSecurity().getCsrfDisabled()) {
if (applicationProperties.getSecurity().getCsrfDisabled()) {
http.csrf(csrf -> csrf.disable());
}
}
if (loginEnabledValue) {
http.addFilterBefore(
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
@ -161,6 +151,9 @@ public class SecurityConfiguration {
sessionManagement ->
sessionManagement
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.sessionAuthenticationStrategy(
new RegisterSessionAuthenticationStrategy(
sessionRegistry)) // ?
.maximumSessions(10)
.maxSessionsPreventsLogin(false)
.sessionRegistry(sessionRegistry)
@ -269,28 +262,38 @@ public class SecurityConfiguration {
// Handle SAML
if (applicationProperties.getSecurity().isSaml2Activ() && runningEE) {
http.authenticationProvider(samlAuthenticationProvider())
.saml2Login(saml2 -> {
try {
saml2
.loginPage("/saml2")
.relyingPartyRegistrationRepository(relyingPartyRegistrations())
//.authenticationRequestResolver(new OpenSaml4AuthenticationRequestResolver(
// relyingPartyRegistrations()
// ))
.successHandler(
new CustomSaml2AuthenticationSuccessHandler(
loginAttemptService,
applicationProperties,
userService))
.failureHandler(
new CustomSaml2AuthenticationFailureHandler())
.permitAll();
} catch (Exception e) {
e.printStackTrace();
}
});
// Configure the authentication provider
OpenSaml4AuthenticationProvider authenticationProvider =
new OpenSaml4AuthenticationProvider();
authenticationProvider.setResponseAuthenticationConverter(
new CustomSaml2ResponseAuthenticationConverter(userService));
http.authenticationProvider(authenticationProvider)
.saml2Login(
saml2 -> {
try {
saml2.loginPage("/saml2")
.relyingPartyRegistrationRepository(
relyingPartyRegistrations())
.authenticationManager(
new ProviderManager(authenticationProvider))
.successHandler(
new CustomSaml2AuthenticationSuccessHandler(
loginAttemptService,
applicationProperties,
userService))
.failureHandler(
new CustomSaml2AuthenticationFailureHandler())
.authenticationRequestResolver(
authenticationRequestResolver(
relyingPartyRegistrations()));
} catch (Exception e) {
log.error("Error configuring SAML2 login", e);
throw new RuntimeException(e);
}
});
}
} else {
if (!applicationProperties.getSecurity().getCsrfDisabled()) {
CookieCsrfTokenRepository cookieRepo =
@ -308,17 +311,29 @@ public class SecurityConfiguration {
return http.build();
}
// @Bean
// public Saml2WebSsoAuthenticationRequestFilter saml2WebSsoAuthenticationRequestFilter(
// RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
// OpenSaml4AuthenticationRequestResolver authenticationRequestResolver =
// new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository);
//
// Saml2WebSsoAuthenticationRequestFilter filter =
// new Saml2WebSsoAuthenticationRequestFilter(
// authenticationRequestResolver
// );
// return filter;
// }
//
@Bean
@ConditionalOnProperty(
value = "security.oauth2.enabled",
value = "security.saml2.enabled",
havingValue = "true",
matchIfMissing = false)
public AuthenticationProvider samlAuthenticationProvider() {
OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
provider.setResponseAuthenticationConverter(
new CustomSaml2ResponseAuthenticationConverter(userService));
new CustomSaml2ResponseAuthenticationConverter(userService));
return provider;
}
@ -452,6 +467,11 @@ public class SecurityConfiguration {
.build());
}
@Bean
public HttpSessionSaml2AuthenticationRequestRepository saml2AuthenticationRequestRepository() {
return new HttpSessionSaml2AuthenticationRequestRepository();
}
@Bean
@ConditionalOnProperty(
name = "security.saml2.enabled",
@ -465,28 +485,103 @@ public class SecurityConfiguration {
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())
.assertionConsumerServiceLocation("{baseUrl}/login/saml2/sso/stirlingpdf-saml")
.entityId("http://localhost:8080/saml2/service-provider-metadata/stirlingpdf-saml")
.signingX509Credentials(c -> c.add(signingCredential))
.assertingPartyDetails(party -> party
.entityId(samlConf.getIdpIssuer())
.singleSignOnServiceLocation(samlConf.getIdpSingleLoginUrl())
.verificationX509Credentials(c -> c.add(verificationCredential))
.singleSignOnServiceBinding(Saml2MessageBinding.POST)
.wantAuthnRequestsSigned(true)
)
.build();
Saml2X509Credential signingCredential =
new Saml2X509Credential(
CertificateUtils.readPrivateKey(privateKeyResource),
CertificateUtils.readCertificate(certificateResource),
Saml2X509CredentialType.SIGNING);
RelyingPartyRegistration rp =
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
.assertionConsumerServiceLocation(
"{baseUrl}/login/saml2/sso/stirlingpdf-saml")
.entityId(
"http://localhost:8080/saml2/service-provider-metadata/stirlingpdf-saml")
.signingX509Credentials(c -> c.add(signingCredential))
.assertingPartyMetadata(
metadata ->
metadata.entityId(samlConf.getIdpIssuer())
.singleSignOnServiceLocation(
samlConf.getIdpSingleLoginUrl())
.verificationX509Credentials(
c -> c.add(verificationCredential))
.singleSignOnServiceBinding(
Saml2MessageBinding.POST)
.wantAuthnRequestsSigned(true))
.build();
return new InMemoryRelyingPartyRegistrationRepository(rp);
}
@Bean
@ConditionalOnProperty(
name = "security.saml2.enabled",
havingValue = "true",
matchIfMissing = false)
public OpenSaml4AuthenticationRequestResolver authenticationRequestResolver(
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
OpenSaml4AuthenticationRequestResolver resolver =
new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository);
resolver.setAuthnRequestCustomizer(
customizer -> {
log.info("Customizing SAML Authentication request");
AuthnRequest authnRequest = customizer.getAuthnRequest();
log.info("AuthnRequest ID: {}", authnRequest.getID());
log.info("AuthnRequest IssueInstant: {}", authnRequest.getIssueInstant());
log.info(
"AuthnRequest Issuer: {}",
authnRequest.getIssuer() != null
? authnRequest.getIssuer().getValue()
: "null");
HttpServletRequest request = customizer.getRequest();
// Log HTTP request details
log.info("HTTP Request Method: {}", request.getMethod());
log.info("Request URI: {}", request.getRequestURI());
log.info("Request URL: {}", request.getRequestURL().toString());
log.info("Query String: {}", request.getQueryString());
log.info("Remote Address: {}", request.getRemoteAddr());
// Log headers
Collections.list(request.getHeaderNames())
.forEach(
headerName -> {
log.info(
"Header - {}: {}",
headerName,
request.getHeader(headerName));
});
// Log SAML specific parameters
log.info("SAML Request Parameters:");
log.info("SAMLRequest: {}", request.getParameter("SAMLRequest"));
log.info("RelayState: {}", request.getParameter("RelayState"));
// Log session information if exists
if (request.getSession(false) != null) {
log.info("Session ID: {}", request.getSession().getId());
}
// Log any assertions consumer service details if present
if (authnRequest.getAssertionConsumerServiceURL() != null) {
log.info(
"AssertionConsumerServiceURL: {}",
authnRequest.getAssertionConsumerServiceURL());
}
// Log NameID policy if present
if (authnRequest.getNameIDPolicy() != null) {
log.info(
"NameIDPolicy Format: {}",
authnRequest.getNameIDPolicy().getFormat());
}
});
return resolver;
}
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);

View File

@ -53,13 +53,15 @@ public class UserService implements UserServiceInterface {
@Transactional
public void migrateOauth2ToSSO() {
userRepository.findByAuthenticationTypeIgnoreCase("OAUTH2")
.forEach(user -> {
user.setAuthenticationType(AuthenticationType.SSO);
userRepository.save(user);
});
userRepository
.findByAuthenticationTypeIgnoreCase("OAUTH2")
.forEach(
user -> {
user.setAuthenticationType(AuthenticationType.SSO);
userRepository.save(user);
});
}
// Handle OAUTH2 login and user auto creation.
public boolean processSSOPostLogin(String username, boolean autoCreateUser)
throws IllegalArgumentException, IOException {

View File

@ -82,8 +82,7 @@ public class CustomOAuth2AuthenticationSuccessHandler
}
if (userService.usernameExistsIgnoreCase(username)
&& userService.hasPassword(username)
&& !userService.isAuthenticationTypeByUsername(
username, AuthenticationType.SSO)
&& !userService.isAuthenticationTypeByUsername(username, AuthenticationType.SSO)
&& oAuth.getAutoCreateUser()) {
response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
return;

View File

@ -3,12 +3,14 @@ package stirling.software.SPDF.config.security.saml2;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.springframework.core.io.Resource;
@ -28,15 +30,26 @@ public class CertificateUtils {
}
public static RSAPrivateKey readPrivateKey(Resource privateKeyResource) throws Exception {
try (PemReader pemReader =
new PemReader(
try (PEMParser pemParser =
new PEMParser(
new InputStreamReader(
privateKeyResource.getInputStream(), StandardCharsets.UTF_8))) {
PemObject pemObject = pemReader.readPemObject();
byte[] decodedKey = pemObject.getContent();
return (RSAPrivateKey)
KeyFactory.getInstance("RSA")
.generatePrivate(new PKCS8EncodedKeySpec(decodedKey));
Object object = pemParser.readObject();
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
if (object instanceof PEMKeyPair) {
// Handle traditional RSA private key format
PEMKeyPair keypair = (PEMKeyPair) object;
return (RSAPrivateKey) converter.getPrivateKey(keypair.getPrivateKeyInfo());
} else if (object instanceof PrivateKeyInfo) {
// Handle PKCS#8 format
return (RSAPrivateKey) converter.getPrivateKey((PrivateKeyInfo) object);
} else {
throw new IllegalArgumentException(
"Unsupported key format: "
+ (object != null ? object.getClass().getName() : "null"));
}
}
}
}

View File

@ -0,0 +1,64 @@
package stirling.software.SPDF.config.security.saml2;
import java.util.Enumeration;
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
@Component
public class CustomSaml2AuthenticationRequestRepository
implements Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> {
private static final String AUTHENTICATION_REQUEST_KEY_PREFIX = "SAML2_AUTHENTICATION_REQUEST_";
@Override
public void saveAuthenticationRequest(
AbstractSaml2AuthenticationRequest authenticationRequest,
HttpServletRequest request,
HttpServletResponse response) {
HttpSession session = request.getSession(true);
String requestId = authenticationRequest.getId();
session.setAttribute(AUTHENTICATION_REQUEST_KEY_PREFIX + requestId, authenticationRequest);
}
@Override
public AbstractSaml2AuthenticationRequest loadAuthenticationRequest(
HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
Enumeration<String> attributeNames = session.getAttributeNames();
while (attributeNames.hasMoreElements()) {
String attributeName = attributeNames.nextElement();
if (attributeName.startsWith(AUTHENTICATION_REQUEST_KEY_PREFIX)) {
return (AbstractSaml2AuthenticationRequest) session.getAttribute(attributeName);
}
}
}
return null;
}
@Override
public AbstractSaml2AuthenticationRequest removeAuthenticationRequest(
HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession(false);
if (session != null) {
Enumeration<String> attributeNames = session.getAttributeNames();
while (attributeNames.hasMoreElements()) {
String attributeName = attributeNames.nextElement();
if (attributeName.startsWith(AUTHENTICATION_REQUEST_KEY_PREFIX)) {
AbstractSaml2AuthenticationRequest auth =
(AbstractSaml2AuthenticationRequest)
session.getAttribute(attributeName);
session.removeAttribute(attributeName);
return auth;
}
}
}
return null;
}
}

View File

@ -12,6 +12,7 @@ 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;
@ -20,11 +21,11 @@ import stirling.software.SPDF.model.AuthenticationType;
import stirling.software.SPDF.utils.RequestUriUtils;
@AllArgsConstructor
@Slf4j
public class CustomSaml2AuthenticationSuccessHandler
extends SavedRequestAwareAuthenticationSuccessHandler {
private LoginAttemptService loginAttemptService;
private ApplicationProperties applicationProperties;
private UserService userService;
@ -34,10 +35,12 @@ public class CustomSaml2AuthenticationSuccessHandler
throws ServletException, IOException {
Object principal = authentication.getPrincipal();
log.info("Starting SAML2 authentication success handling");
if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
String username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
// Get the saved request
log.info("Authenticated principal found for user: {}", username);
HttpSession session = request.getSession(false);
String contextPath = request.getContextPath();
SavedRequest savedRequest =
@ -45,46 +48,77 @@ public class CustomSaml2AuthenticationSuccessHandler
? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
: null;
log.info(
"Session exists: {}, Saved request exists: {}",
session != null,
savedRequest != null);
if (savedRequest != null
&& !RequestUriUtils.isStaticResource(
contextPath, savedRequest.getRedirectUrl())) {
// Redirect to the original destination
log.info(
"Valid saved request found, redirecting to original destination: {}",
savedRequest.getRedirectUrl());
super.onAuthenticationSuccess(request, response, authentication);
} else {
SAML2 saml2 = applicationProperties.getSecurity().getSaml2();
log.info(
"Processing SAML2 authentication with autoCreateUser: {}",
saml2.getAutoCreateUser());
if (loginAttemptService.isBlocked(username)) {
log.info("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.");
}
if (userService.usernameExistsIgnoreCase(username)
&& userService.hasPassword(username)
&& !userService.isAuthenticationTypeByUsername(
username, AuthenticationType.SSO)
&& saml2.getAutoCreateUser()) {
boolean userExists = userService.usernameExistsIgnoreCase(username);
boolean hasPassword = userExists && userService.hasPassword(username);
boolean isSSOUser =
userExists
&& userService.isAuthenticationTypeByUsername(
username, AuthenticationType.SSO);
log.info(
"User status - Exists: {}, Has password: {}, Is SSO user: {}",
userExists,
hasPassword,
isSSOUser);
if (userExists && hasPassword && !isSSOUser && saml2.getAutoCreateUser()) {
log.info(
"User {} exists with password but is not SSO user, redirecting to logout",
username);
response.sendRedirect(
contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
return;
}
try {
if (saml2.getBlockRegistration()
&& !userService.usernameExistsIgnoreCase(username)) {
if (saml2.getBlockRegistration() && !userExists) {
log.info("Registration blocked for new user: {}", username);
response.sendRedirect(
contextPath + "/login?erroroauth=oauth2_admin_blocked_user");
return;
}
log.info("Processing SSO post-login for user: {}", username);
userService.processSSOPostLogin(username, saml2.getAutoCreateUser());
log.info("Successfully processed authentication for user: {}", username);
response.sendRedirect(contextPath + "/");
return;
} catch (IllegalArgumentException e) {
log.info(
"Invalid username detected for user: {}, redirecting to logout",
username);
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
return;
}
}
} else {
log.info("Non-SAML2 principal detected, delegating to parent handler");
super.onAuthenticationSuccess(request, response, authentication);
}
}

View File

@ -3,8 +3,6 @@ package stirling.software.SPDF.config.security.saml2;
import java.util.*;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.schema.XSBoolean;
import org.opensaml.core.xml.schema.XSString;
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.Attribute;
import org.opensaml.saml.saml2.core.AttributeStatement;
@ -32,12 +30,12 @@ public class CustomSaml2ResponseAuthenticationConverter
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();
@ -45,7 +43,7 @@ public class CustomSaml2ResponseAuthenticationConverter
values.add(value);
}
}
if (!values.isEmpty()) {
// Store with both full URI and last part of the URI
attributes.put(attributeName, values);
@ -54,7 +52,7 @@ public class CustomSaml2ResponseAuthenticationConverter
}
}
}
return attributes;
}
@ -62,10 +60,10 @@ public class CustomSaml2ResponseAuthenticationConverter
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 = null;
if (hasAttribute(attributes, "username")) {
@ -88,7 +86,8 @@ public class CustomSaml2ResponseAuthenticationConverter
if (userOpt.isPresent()) {
User user = userOpt.get();
if (user != null) {
simpleGrantedAuthority = new SimpleGrantedAuthority(userService.findRole(user).getAuthority());
simpleGrantedAuthority =
new SimpleGrantedAuthority(userService.findRole(user).getAuthority());
}
}
@ -97,11 +96,9 @@ public class CustomSaml2ResponseAuthenticationConverter
sessionIndexes.add(authnStatement.getSessionIndex());
}
CustomSaml2AuthenticatedPrincipal principal = new CustomSaml2AuthenticatedPrincipal(
userIdentifier,
attributes,
userIdentifier,
sessionIndexes);
CustomSaml2AuthenticatedPrincipal principal =
new CustomSaml2AuthenticatedPrincipal(
userIdentifier, attributes, userIdentifier, sessionIndexes);
return new Saml2Authentication(
principal,

View File

@ -20,7 +20,6 @@ public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByApiKey(String apiKey);
List<User> findByAuthenticationTypeIgnoreCase(String authenticationType);
}

View File

@ -18,7 +18,7 @@ public class PdfMetadataService {
private final String stirlingPDFLabel;
private final UserServiceInterface userService;
private final boolean runningEE;
@Autowired
public PdfMetadataService(
ApplicationProperties applicationProperties,
@ -64,10 +64,8 @@ public class PdfMetadataService {
String creator = stirlingPDFLabel;
if (applicationProperties
.getEnterpriseEdition()
.getCustomMetadata()
.isAutoUpdateMetadata() && runningEE) {
if (applicationProperties.getEnterpriseEdition().getCustomMetadata().isAutoUpdateMetadata()
&& runningEE) {
creator = applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator();
pdf.getDocumentInformation().setProducer(stirlingPDFLabel);
@ -86,10 +84,8 @@ public class PdfMetadataService {
pdf.getDocumentInformation().setModificationDate(Calendar.getInstance());
String author = pdfMetadata.getAuthor();
if (applicationProperties
.getEnterpriseEdition()
.getCustomMetadata()
.isAutoUpdateMetadata() && runningEE) {
if (applicationProperties.getEnterpriseEdition().getCustomMetadata().isAutoUpdateMetadata()
&& runningEE) {
author = applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor();
if (userService != null) {