mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2024-12-21 19:08:24 +01:00
more fixes
This commit is contained in:
parent
b4837df76c
commit
5171088fca
15
build.gradle
15
build.gradle
@ -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'
|
||||
|
||||
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user