format and cleanups
This commit is contained in:
@@ -27,7 +27,7 @@ public class EEAppConfig {
|
|||||||
public boolean runningEnterpriseEdition() {
|
public boolean runningEnterpriseEdition() {
|
||||||
return licenseKeyChecker.getEnterpriseEnabledResult();
|
return licenseKeyChecker.getEnterpriseEnabledResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "SSOAutoLogin")
|
@Bean(name = "SSOAutoLogin")
|
||||||
public boolean ssoAutoLogin() {
|
public boolean ssoAutoLogin() {
|
||||||
return applicationProperties.getEnterpriseEdition().isSsoAutoLogin();
|
return applicationProperties.getEnterpriseEdition().isSsoAutoLogin();
|
||||||
|
|||||||
@@ -1,39 +1,24 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.DependsOn;
|
import org.springframework.context.annotation.DependsOn;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.security.authentication.ProviderManager;
|
import org.springframework.security.authentication.ProviderManager;
|
||||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
|
||||||
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.Saml2X509Credential;
|
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
||||||
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.RelyingPartyRegistrationRepository;
|
||||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
|
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
@@ -43,24 +28,16 @@ import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
|
|||||||
import org.springframework.security.web.savedrequest.NullRequestCache;
|
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
||||||
import stirling.software.SPDF.config.security.saml2.CertificateUtils;
|
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationFailureHandler;
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationFailureHandler;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationSuccessHandler;
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2ResponseAuthenticationConverter;
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2ResponseAuthenticationConverter;
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
import stirling.software.SPDF.model.provider.GithubProvider;
|
|
||||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
|
||||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||||
import stirling.software.SPDF.repository.PersistentLoginRepository;
|
import stirling.software.SPDF.repository.PersistentLoginRepository;
|
||||||
|
|
||||||
@@ -72,7 +49,7 @@ import stirling.software.SPDF.repository.PersistentLoginRepository;
|
|||||||
public class SecurityConfiguration {
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
private final CustomUserDetailsService userDetailsService;
|
private final CustomUserDetailsService userDetailsService;
|
||||||
@Lazy private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
@Qualifier("loginEnabled")
|
@Qualifier("loginEnabled")
|
||||||
private final boolean loginEnabledValue;
|
private final boolean loginEnabledValue;
|
||||||
@@ -86,16 +63,10 @@ public class SecurityConfiguration {
|
|||||||
private final FirstLoginFilter firstLoginFilter;
|
private final FirstLoginFilter firstLoginFilter;
|
||||||
private final SessionPersistentRegistry sessionRegistry;
|
private final SessionPersistentRegistry sessionRegistry;
|
||||||
private final PersistentLoginRepository persistentLoginRepository;
|
private final PersistentLoginRepository persistentLoginRepository;
|
||||||
|
private final GrantedAuthoritiesMapper oAuth2userAuthoritiesMapper;
|
||||||
|
private final RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations;
|
||||||
|
private final OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver;
|
||||||
|
|
||||||
// // Only Dev test
|
|
||||||
// @Bean
|
|
||||||
// public WebSecurityCustomizer webSecurityCustomizer() {
|
|
||||||
// return (web) ->
|
|
||||||
// web.ignoring()
|
|
||||||
// .requestMatchers(
|
|
||||||
// "/css/**", "/images/**", "/js/**", "/**.svg",
|
|
||||||
// "/pdfjs-legacy/**");
|
|
||||||
// }
|
|
||||||
public SecurityConfiguration(
|
public SecurityConfiguration(
|
||||||
PersistentLoginRepository persistentLoginRepository,
|
PersistentLoginRepository persistentLoginRepository,
|
||||||
CustomUserDetailsService userDetailsService,
|
CustomUserDetailsService userDetailsService,
|
||||||
@@ -106,7 +77,12 @@ public class SecurityConfiguration {
|
|||||||
UserAuthenticationFilter userAuthenticationFilter,
|
UserAuthenticationFilter userAuthenticationFilter,
|
||||||
LoginAttemptService loginAttemptService,
|
LoginAttemptService loginAttemptService,
|
||||||
FirstLoginFilter firstLoginFilter,
|
FirstLoginFilter firstLoginFilter,
|
||||||
SessionPersistentRegistry sessionRegistry) {
|
SessionPersistentRegistry sessionRegistry,
|
||||||
|
@Autowired(required = false) GrantedAuthoritiesMapper oAuth2userAuthoritiesMapper,
|
||||||
|
@Autowired(required = false)
|
||||||
|
RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations,
|
||||||
|
@Autowired(required = false)
|
||||||
|
OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver) {
|
||||||
this.userDetailsService = userDetailsService;
|
this.userDetailsService = userDetailsService;
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
this.loginEnabledValue = loginEnabledValue;
|
this.loginEnabledValue = loginEnabledValue;
|
||||||
@@ -117,6 +93,9 @@ public class SecurityConfiguration {
|
|||||||
this.firstLoginFilter = firstLoginFilter;
|
this.firstLoginFilter = firstLoginFilter;
|
||||||
this.sessionRegistry = sessionRegistry;
|
this.sessionRegistry = sessionRegistry;
|
||||||
this.persistentLoginRepository = persistentLoginRepository;
|
this.persistentLoginRepository = persistentLoginRepository;
|
||||||
|
this.oAuth2userAuthoritiesMapper = oAuth2userAuthoritiesMapper;
|
||||||
|
this.saml2RelyingPartyRegistrations = saml2RelyingPartyRegistrations;
|
||||||
|
this.saml2AuthenticationRequestResolver = saml2AuthenticationRequestResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -274,7 +253,7 @@ public class SecurityConfiguration {
|
|||||||
userService,
|
userService,
|
||||||
loginAttemptService))
|
loginAttemptService))
|
||||||
.userAuthoritiesMapper(
|
.userAuthoritiesMapper(
|
||||||
userAuthoritiesMapper()))
|
oAuth2userAuthoritiesMapper))
|
||||||
.permitAll());
|
.permitAll());
|
||||||
}
|
}
|
||||||
// Handle SAML
|
// Handle SAML
|
||||||
@@ -291,7 +270,7 @@ public class SecurityConfiguration {
|
|||||||
try {
|
try {
|
||||||
saml2.loginPage("/saml2")
|
saml2.loginPage("/saml2")
|
||||||
.relyingPartyRegistrationRepository(
|
.relyingPartyRegistrationRepository(
|
||||||
relyingPartyRegistrations())
|
saml2RelyingPartyRegistrations)
|
||||||
.authenticationManager(
|
.authenticationManager(
|
||||||
new ProviderManager(authenticationProvider))
|
new ProviderManager(authenticationProvider))
|
||||||
.successHandler(
|
.successHandler(
|
||||||
@@ -302,8 +281,7 @@ public class SecurityConfiguration {
|
|||||||
.failureHandler(
|
.failureHandler(
|
||||||
new CustomSaml2AuthenticationFailureHandler())
|
new CustomSaml2AuthenticationFailureHandler())
|
||||||
.authenticationRequestResolver(
|
.authenticationRequestResolver(
|
||||||
authenticationRequestResolver(
|
saml2AuthenticationRequestResolver);
|
||||||
relyingPartyRegistrations()));
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error configuring SAML2 login", e);
|
log.error("Error configuring SAML2 login", e);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
@@ -311,244 +289,11 @@ public class SecurityConfiguration {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if (!applicationProperties.getSecurity().getCsrfDisabled()) {
|
|
||||||
// CookieCsrfTokenRepository cookieRepo =
|
|
||||||
// CookieCsrfTokenRepository.withHttpOnlyFalse();
|
|
||||||
// CsrfTokenRequestAttributeHandler requestHandler =
|
|
||||||
// new CsrfTokenRequestAttributeHandler();
|
|
||||||
// requestHandler.setCsrfRequestAttributeName(null);
|
|
||||||
// http.csrf(
|
|
||||||
// csrf ->
|
|
||||||
// csrf.csrfTokenRepository(cookieRepo)
|
|
||||||
// .csrfTokenRequestHandler(requestHandler));
|
|
||||||
// }
|
|
||||||
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||||
}
|
}
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(
|
|
||||||
value = "security.oauth2.enabled",
|
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
|
||||||
List<ClientRegistration> registrations = new ArrayList<>();
|
|
||||||
githubClientRegistration().ifPresent(registrations::add);
|
|
||||||
oidcClientRegistration().ifPresent(registrations::add);
|
|
||||||
googleClientRegistration().ifPresent(registrations::add);
|
|
||||||
keycloakClientRegistration().ifPresent(registrations::add);
|
|
||||||
if (registrations.isEmpty()) {
|
|
||||||
log.error("At least one OAuth2 provider must be configured");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
return new InMemoryClientRegistrationRepository(registrations);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> googleClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
GoogleProvider google = client.getGoogle();
|
|
||||||
return google != null && google.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistration.withRegistrationId(google.getName())
|
|
||||||
.clientId(google.getClientId())
|
|
||||||
.clientSecret(google.getClientSecret())
|
|
||||||
.scope(google.getScopes())
|
|
||||||
.authorizationUri(google.getAuthorizationuri())
|
|
||||||
.tokenUri(google.getTokenuri())
|
|
||||||
.userInfoUri(google.getUserinfouri())
|
|
||||||
.userNameAttributeName(google.getUseAsUsername())
|
|
||||||
.clientName(google.getClientName())
|
|
||||||
.redirectUri("{baseUrl}/login/oauth2/code/" + google.getName())
|
|
||||||
.authorizationGrantType(
|
|
||||||
org.springframework.security.oauth2.core
|
|
||||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> keycloakClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
KeycloakProvider keycloak = client.getKeycloak();
|
|
||||||
return keycloak != null && keycloak.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
|
||||||
.registrationId(keycloak.getName())
|
|
||||||
.clientId(keycloak.getClientId())
|
|
||||||
.clientSecret(keycloak.getClientSecret())
|
|
||||||
.scope(keycloak.getScopes())
|
|
||||||
.userNameAttributeName(keycloak.getUseAsUsername())
|
|
||||||
.clientName(keycloak.getClientName())
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> githubClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
GithubProvider github = client.getGithub();
|
|
||||||
return github != null && github.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistration.withRegistrationId(github.getName())
|
|
||||||
.clientId(github.getClientId())
|
|
||||||
.clientSecret(github.getClientSecret())
|
|
||||||
.scope(github.getScopes())
|
|
||||||
.authorizationUri(github.getAuthorizationuri())
|
|
||||||
.tokenUri(github.getTokenuri())
|
|
||||||
.userInfoUri(github.getUserinfouri())
|
|
||||||
.userNameAttributeName(github.getUseAsUsername())
|
|
||||||
.clientName(github.getClientName())
|
|
||||||
.redirectUri("{baseUrl}/login/oauth2/code/" + github.getName())
|
|
||||||
.authorizationGrantType(
|
|
||||||
org.springframework.security.oauth2.core
|
|
||||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> oidcClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null
|
|
||||||
|| oauth.getIssuer() == null
|
|
||||||
|| oauth.getIssuer().isEmpty()
|
|
||||||
|| oauth.getClientId() == null
|
|
||||||
|| oauth.getClientId().isEmpty()
|
|
||||||
|| oauth.getClientSecret() == null
|
|
||||||
|| oauth.getClientSecret().isEmpty()
|
|
||||||
|| oauth.getScopes() == null
|
|
||||||
|| oauth.getScopes().isEmpty()
|
|
||||||
|| oauth.getUseAsUsername() == null
|
|
||||||
|| oauth.getUseAsUsername().isEmpty()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
return Optional.of(
|
|
||||||
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
|
||||||
.registrationId("oidc")
|
|
||||||
.clientId(oauth.getClientId())
|
|
||||||
.clientSecret(oauth.getClientSecret())
|
|
||||||
.scope(oauth.getScopes())
|
|
||||||
.userNameAttributeName(oauth.getUseAsUsername())
|
|
||||||
.clientName("OIDC")
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(
|
|
||||||
name = "security.saml2.enabled",
|
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
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))
|
|
||||||
.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.debug("Customizing SAML Authentication request");
|
|
||||||
AuthnRequest authnRequest = customizer.getAuthnRequest();
|
|
||||||
log.debug("AuthnRequest ID: {}", authnRequest.getID());
|
|
||||||
if (authnRequest.getID() == null) {
|
|
||||||
authnRequest.setID("ARQ" + UUID.randomUUID().toString());
|
|
||||||
}
|
|
||||||
log.debug("AuthnRequest new ID after set: {}", authnRequest.getID());
|
|
||||||
log.debug("AuthnRequest IssueInstant: {}", authnRequest.getIssueInstant());
|
|
||||||
log.debug(
|
|
||||||
"AuthnRequest Issuer: {}",
|
|
||||||
authnRequest.getIssuer() != null
|
|
||||||
? authnRequest.getIssuer().getValue()
|
|
||||||
: "null");
|
|
||||||
HttpServletRequest request = customizer.getRequest();
|
|
||||||
// Log HTTP request details
|
|
||||||
log.debug("HTTP Request Method: {}", request.getMethod());
|
|
||||||
log.debug("Request URI: {}", request.getRequestURI());
|
|
||||||
log.debug("Request URL: {}", request.getRequestURL().toString());
|
|
||||||
log.debug("Query String: {}", request.getQueryString());
|
|
||||||
log.debug("Remote Address: {}", request.getRemoteAddr());
|
|
||||||
// Log headers
|
|
||||||
Collections.list(request.getHeaderNames())
|
|
||||||
.forEach(
|
|
||||||
headerName -> {
|
|
||||||
log.debug(
|
|
||||||
"Header - {}: {}",
|
|
||||||
headerName,
|
|
||||||
request.getHeader(headerName));
|
|
||||||
});
|
|
||||||
// Log SAML specific parameters
|
|
||||||
log.debug("SAML Request Parameters:");
|
|
||||||
log.debug("SAMLRequest: {}", request.getParameter("SAMLRequest"));
|
|
||||||
log.debug("RelayState: {}", request.getParameter("RelayState"));
|
|
||||||
// Log session debugrmation if exists
|
|
||||||
if (request.getSession(false) != null) {
|
|
||||||
log.debug("Session ID: {}", request.getSession().getId());
|
|
||||||
}
|
|
||||||
// Log any assertions consumer service details if present
|
|
||||||
if (authnRequest.getAssertionConsumerServiceURL() != null) {
|
|
||||||
log.debug(
|
|
||||||
"AssertionConsumerServiceURL: {}",
|
|
||||||
authnRequest.getAssertionConsumerServiceURL());
|
|
||||||
}
|
|
||||||
// Log NameID policy if present
|
|
||||||
if (authnRequest.getNameIDPolicy() != null) {
|
|
||||||
log.debug(
|
|
||||||
"NameIDPolicy Format: {}",
|
|
||||||
authnRequest.getNameIDPolicy().getFormat());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return resolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DaoAuthenticationProvider daoAuthenticationProvider() {
|
public DaoAuthenticationProvider daoAuthenticationProvider() {
|
||||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||||
provider.setUserDetailsService(userDetailsService);
|
provider.setUserDetailsService(userDetailsService);
|
||||||
@@ -556,46 +301,6 @@ public class SecurityConfiguration {
|
|||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
This following function is to grant Authorities to the OAUTH2 user from the values stored in the database.
|
|
||||||
This is required for the internal; 'hasRole()' function to give out the correct role.
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(
|
|
||||||
value = "security.oauth2.enabled",
|
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
|
||||||
return (authorities) -> {
|
|
||||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
|
||||||
authorities.forEach(
|
|
||||||
authority -> {
|
|
||||||
// Add existing OAUTH2 Authorities
|
|
||||||
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
|
|
||||||
// Add Authorities from database for existing user, if user is present.
|
|
||||||
if (authority instanceof OAuth2UserAuthority oauth2Auth) {
|
|
||||||
String useAsUsername =
|
|
||||||
applicationProperties
|
|
||||||
.getSecurity()
|
|
||||||
.getOauth2()
|
|
||||||
.getUseAsUsername();
|
|
||||||
Optional<User> userOpt =
|
|
||||||
userService.findByUsernameIgnoreCase(
|
|
||||||
(String) oauth2Auth.getAttributes().get(useAsUsername));
|
|
||||||
if (userOpt.isPresent()) {
|
|
||||||
User user = userOpt.get();
|
|
||||||
if (user != null) {
|
|
||||||
mappedAuthorities.add(
|
|
||||||
new SimpleGrantedAuthority(
|
|
||||||
userService.findRole(user).getAuthority()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return mappedAuthorities;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public IPRateLimitingFilter rateLimitingFilter() {
|
public IPRateLimitingFilter rateLimitingFilter() {
|
||||||
// Example limit TODO add config level
|
// Example limit TODO add config level
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ public class DatabaseConfig {
|
|||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
private final boolean runningEE;
|
private final boolean runningEE;
|
||||||
|
|
||||||
public DatabaseConfig(ApplicationProperties applicationProperties, @Qualifier("runningEE") boolean runningEE) {
|
public DatabaseConfig(
|
||||||
|
ApplicationProperties applicationProperties,
|
||||||
|
@Qualifier("runningEE") boolean runningEE) {
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
this.runningEE = runningEE;
|
this.runningEE = runningEE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
package stirling.software.SPDF.config.security.oauth2;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
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 lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||||
|
import stirling.software.SPDF.model.User;
|
||||||
|
import stirling.software.SPDF.model.provider.GithubProvider;
|
||||||
|
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||||
|
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Slf4j
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
value = "security.oauth2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public class OAuth2Configuration {
|
||||||
|
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
|
@Lazy private final UserService userService;
|
||||||
|
|
||||||
|
public OAuth2Configuration(
|
||||||
|
ApplicationProperties applicationProperties, @Lazy UserService userService) {
|
||||||
|
this.userService = userService;
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
value = "security.oauth2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public ClientRegistrationRepository clientRegistrationRepository() {
|
||||||
|
List<ClientRegistration> registrations = new ArrayList<>();
|
||||||
|
githubClientRegistration().ifPresent(registrations::add);
|
||||||
|
oidcClientRegistration().ifPresent(registrations::add);
|
||||||
|
googleClientRegistration().ifPresent(registrations::add);
|
||||||
|
keycloakClientRegistration().ifPresent(registrations::add);
|
||||||
|
if (registrations.isEmpty()) {
|
||||||
|
log.error("At least one OAuth2 provider must be configured");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
return new InMemoryClientRegistrationRepository(registrations);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ClientRegistration> googleClientRegistration() {
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
if (oauth == null || !oauth.getEnabled()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
Client client = oauth.getClient();
|
||||||
|
if (client == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
GoogleProvider google = client.getGoogle();
|
||||||
|
return google != null && google.isSettingsValid()
|
||||||
|
? Optional.of(
|
||||||
|
ClientRegistration.withRegistrationId(google.getName())
|
||||||
|
.clientId(google.getClientId())
|
||||||
|
.clientSecret(google.getClientSecret())
|
||||||
|
.scope(google.getScopes())
|
||||||
|
.authorizationUri(google.getAuthorizationuri())
|
||||||
|
.tokenUri(google.getTokenuri())
|
||||||
|
.userInfoUri(google.getUserinfouri())
|
||||||
|
.userNameAttributeName(google.getUseAsUsername())
|
||||||
|
.clientName(google.getClientName())
|
||||||
|
.redirectUri("{baseUrl}/login/oauth2/code/" + google.getName())
|
||||||
|
.authorizationGrantType(
|
||||||
|
org.springframework.security.oauth2.core
|
||||||
|
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
|
.build())
|
||||||
|
: Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ClientRegistration> keycloakClientRegistration() {
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
if (oauth == null || !oauth.getEnabled()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
Client client = oauth.getClient();
|
||||||
|
if (client == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
KeycloakProvider keycloak = client.getKeycloak();
|
||||||
|
return keycloak != null && keycloak.isSettingsValid()
|
||||||
|
? Optional.of(
|
||||||
|
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
||||||
|
.registrationId(keycloak.getName())
|
||||||
|
.clientId(keycloak.getClientId())
|
||||||
|
.clientSecret(keycloak.getClientSecret())
|
||||||
|
.scope(keycloak.getScopes())
|
||||||
|
.userNameAttributeName(keycloak.getUseAsUsername())
|
||||||
|
.clientName(keycloak.getClientName())
|
||||||
|
.build())
|
||||||
|
: Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ClientRegistration> githubClientRegistration() {
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
if (oauth == null || !oauth.getEnabled()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
Client client = oauth.getClient();
|
||||||
|
if (client == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
GithubProvider github = client.getGithub();
|
||||||
|
return github != null && github.isSettingsValid()
|
||||||
|
? Optional.of(
|
||||||
|
ClientRegistration.withRegistrationId(github.getName())
|
||||||
|
.clientId(github.getClientId())
|
||||||
|
.clientSecret(github.getClientSecret())
|
||||||
|
.scope(github.getScopes())
|
||||||
|
.authorizationUri(github.getAuthorizationuri())
|
||||||
|
.tokenUri(github.getTokenuri())
|
||||||
|
.userInfoUri(github.getUserinfouri())
|
||||||
|
.userNameAttributeName(github.getUseAsUsername())
|
||||||
|
.clientName(github.getClientName())
|
||||||
|
.redirectUri("{baseUrl}/login/oauth2/code/" + github.getName())
|
||||||
|
.authorizationGrantType(
|
||||||
|
org.springframework.security.oauth2.core
|
||||||
|
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
|
.build())
|
||||||
|
: Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ClientRegistration> oidcClientRegistration() {
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
if (oauth == null
|
||||||
|
|| oauth.getIssuer() == null
|
||||||
|
|| oauth.getIssuer().isEmpty()
|
||||||
|
|| oauth.getClientId() == null
|
||||||
|
|| oauth.getClientId().isEmpty()
|
||||||
|
|| oauth.getClientSecret() == null
|
||||||
|
|| oauth.getClientSecret().isEmpty()
|
||||||
|
|| oauth.getScopes() == null
|
||||||
|
|| oauth.getScopes().isEmpty()
|
||||||
|
|| oauth.getUseAsUsername() == null
|
||||||
|
|| oauth.getUseAsUsername().isEmpty()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(
|
||||||
|
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
||||||
|
.registrationId("oidc")
|
||||||
|
.clientId(oauth.getClientId())
|
||||||
|
.clientSecret(oauth.getClientSecret())
|
||||||
|
.scope(oauth.getScopes())
|
||||||
|
.userNameAttributeName(oauth.getUseAsUsername())
|
||||||
|
.clientName("OIDC")
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This following function is to grant Authorities to the OAUTH2 user from the values stored in the database.
|
||||||
|
This is required for the internal; 'hasRole()' function to give out the correct role.
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
value = "security.oauth2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
||||||
|
return (authorities) -> {
|
||||||
|
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||||
|
authorities.forEach(
|
||||||
|
authority -> {
|
||||||
|
// Add existing OAUTH2 Authorities
|
||||||
|
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
|
||||||
|
// Add Authorities from database for existing user, if user is present.
|
||||||
|
if (authority instanceof OAuth2UserAuthority oauth2Auth) {
|
||||||
|
String useAsUsername =
|
||||||
|
applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getOauth2()
|
||||||
|
.getUseAsUsername();
|
||||||
|
Optional<User> userOpt =
|
||||||
|
userService.findByUsernameIgnoreCase(
|
||||||
|
(String) oauth2Auth.getAttributes().get(useAsUsername));
|
||||||
|
if (userOpt.isPresent()) {
|
||||||
|
User user = userOpt.get();
|
||||||
|
if (user != null) {
|
||||||
|
mappedAuthorities.add(
|
||||||
|
new SimpleGrantedAuthority(
|
||||||
|
userService.findRole(user).getAuthority()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return mappedAuthorities;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
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.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.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",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public class SAML2Configuration {
|
||||||
|
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
public SAML2Configuration(ApplicationProperties applicationProperties) {
|
||||||
|
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
name = "security.saml2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
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))
|
||||||
|
.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.debug("Customizing SAML Authentication request");
|
||||||
|
AuthnRequest authnRequest = customizer.getAuthnRequest();
|
||||||
|
log.debug("AuthnRequest ID: {}", authnRequest.getID());
|
||||||
|
if (authnRequest.getID() == null) {
|
||||||
|
authnRequest.setID("ARQ" + UUID.randomUUID().toString());
|
||||||
|
}
|
||||||
|
log.debug("AuthnRequest new ID after set: {}", authnRequest.getID());
|
||||||
|
log.debug("AuthnRequest IssueInstant: {}", authnRequest.getIssueInstant());
|
||||||
|
log.debug(
|
||||||
|
"AuthnRequest Issuer: {}",
|
||||||
|
authnRequest.getIssuer() != null
|
||||||
|
? authnRequest.getIssuer().getValue()
|
||||||
|
: "null");
|
||||||
|
HttpServletRequest request = customizer.getRequest();
|
||||||
|
// Log HTTP request details
|
||||||
|
log.debug("HTTP Request Method: {}", request.getMethod());
|
||||||
|
log.debug("Request URI: {}", request.getRequestURI());
|
||||||
|
log.debug("Request URL: {}", request.getRequestURL().toString());
|
||||||
|
log.debug("Query String: {}", request.getQueryString());
|
||||||
|
log.debug("Remote Address: {}", request.getRemoteAddr());
|
||||||
|
// Log headers
|
||||||
|
Collections.list(request.getHeaderNames())
|
||||||
|
.forEach(
|
||||||
|
headerName -> {
|
||||||
|
log.debug(
|
||||||
|
"Header - {}: {}",
|
||||||
|
headerName,
|
||||||
|
request.getHeader(headerName));
|
||||||
|
});
|
||||||
|
// Log SAML specific parameters
|
||||||
|
log.debug("SAML Request Parameters:");
|
||||||
|
log.debug("SAMLRequest: {}", request.getParameter("SAMLRequest"));
|
||||||
|
log.debug("RelayState: {}", request.getParameter("RelayState"));
|
||||||
|
// Log session debugrmation if exists
|
||||||
|
if (request.getSession(false) != null) {
|
||||||
|
log.debug("Session ID: {}", request.getSession().getId());
|
||||||
|
}
|
||||||
|
// Log any assertions consumer service details if present
|
||||||
|
if (authnRequest.getAssertionConsumerServiceURL() != null) {
|
||||||
|
log.debug(
|
||||||
|
"AssertionConsumerServiceURL: {}",
|
||||||
|
authnRequest.getAssertionConsumerServiceURL());
|
||||||
|
}
|
||||||
|
// Log NameID policy if present
|
||||||
|
if (authnRequest.getNameIDPolicy() != null) {
|
||||||
|
log.debug(
|
||||||
|
"NameIDPolicy Format: {}",
|
||||||
|
authnRequest.getNameIDPolicy().getFormat());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return resolver;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,6 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.PDFText;
|
import stirling.software.SPDF.model.PDFText;
|
||||||
import stirling.software.SPDF.model.api.security.ManualRedactPdfRequest;
|
import stirling.software.SPDF.model.api.security.ManualRedactPdfRequest;
|
||||||
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
|
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
|
||||||
@@ -53,12 +52,17 @@ public class RedactController {
|
|||||||
|
|
||||||
@InitBinder
|
@InitBinder
|
||||||
public void initBinder(WebDataBinder binder) {
|
public void initBinder(WebDataBinder binder) {
|
||||||
binder.registerCustomEditor(List.class, "redactions", new StringToArrayListPropertyEditor());
|
binder.registerCustomEditor(
|
||||||
|
List.class, "redactions", new StringToArrayListPropertyEditor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/redact", consumes = "multipart/form-data")
|
@PostMapping(value = "/redact", consumes = "multipart/form-data")
|
||||||
@Operation(summary = "Redacts areas and pages in a PDF document", description = "This operation takes an input PDF file with a list of areas, page number(s)/range(s)/function(s) to redact. Input:PDF, Output:PDF, Type:SISO")
|
@Operation(
|
||||||
public ResponseEntity<byte[]> redactPDF(@ModelAttribute ManualRedactPdfRequest request) throws IOException {
|
summary = "Redacts areas and pages in a PDF document",
|
||||||
|
description =
|
||||||
|
"This operation takes an input PDF file with a list of areas, page number(s)/range(s)/function(s) to redact. Input:PDF, Output:PDF, Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> redactPDF(@ModelAttribute ManualRedactPdfRequest request)
|
||||||
|
throws IOException {
|
||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
List<RedactionArea> redactionAreas = request.getRedactions();
|
List<RedactionArea> redactionAreas = request.getRedactions();
|
||||||
|
|
||||||
@@ -86,18 +90,22 @@ public class RedactController {
|
|||||||
+ "_redacted.pdf");
|
+ "_redacted.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void redactAreas(List<RedactionArea> redactionAreas, PDDocument document, PDPageTree allPages)
|
private void redactAreas(
|
||||||
|
List<RedactionArea> redactionAreas, PDDocument document, PDPageTree allPages)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Color redactColor = null;
|
Color redactColor = null;
|
||||||
for (RedactionArea redactionArea : redactionAreas) {
|
for (RedactionArea redactionArea : redactionAreas) {
|
||||||
if (redactionArea.getPage() == null || redactionArea.getPage() <= 0
|
if (redactionArea.getPage() == null
|
||||||
|| redactionArea.getHeight() == null || redactionArea.getHeight() <= 0.0D
|
|| redactionArea.getPage() <= 0
|
||||||
|| redactionArea.getWidth() == null || redactionArea.getWidth() <= 0.0D)
|
|| redactionArea.getHeight() == null
|
||||||
continue;
|
|| redactionArea.getHeight() <= 0.0D
|
||||||
|
|| redactionArea.getWidth() == null
|
||||||
|
|| redactionArea.getWidth() <= 0.0D) continue;
|
||||||
PDPage page = allPages.get(redactionArea.getPage() - 1);
|
PDPage page = allPages.get(redactionArea.getPage() - 1);
|
||||||
|
|
||||||
PDPageContentStream contentStream = new PDPageContentStream(
|
PDPageContentStream contentStream =
|
||||||
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
new PDPageContentStream(
|
||||||
|
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||||
redactColor = decodeOrDefault(redactionArea.getColor(), Color.BLACK);
|
redactColor = decodeOrDefault(redactionArea.getColor(), Color.BLACK);
|
||||||
contentStream.setNonStrokingColor(redactColor);
|
contentStream.setNonStrokingColor(redactColor);
|
||||||
|
|
||||||
@@ -114,15 +122,17 @@ public class RedactController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void redactPages(ManualRedactPdfRequest request, PDDocument document, PDPageTree allPages)
|
private void redactPages(
|
||||||
|
ManualRedactPdfRequest request, PDDocument document, PDPageTree allPages)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Color redactColor = decodeOrDefault(request.getPageRedactionColor(), Color.BLACK);
|
Color redactColor = decodeOrDefault(request.getPageRedactionColor(), Color.BLACK);
|
||||||
List<Integer> pageNumbers = getPageNumbers(request, allPages.getCount());
|
List<Integer> pageNumbers = getPageNumbers(request, allPages.getCount());
|
||||||
for (Integer pageNumber : pageNumbers) {
|
for (Integer pageNumber : pageNumbers) {
|
||||||
PDPage page = allPages.get(pageNumber);
|
PDPage page = allPages.get(pageNumber);
|
||||||
|
|
||||||
PDPageContentStream contentStream = new PDPageContentStream(
|
PDPageContentStream contentStream =
|
||||||
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
new PDPageContentStream(
|
||||||
|
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||||
contentStream.setNonStrokingColor(redactColor);
|
contentStream.setNonStrokingColor(redactColor);
|
||||||
|
|
||||||
PDRectangle box = page.getBBox();
|
PDRectangle box = page.getBBox();
|
||||||
@@ -146,8 +156,10 @@ public class RedactController {
|
|||||||
|
|
||||||
private List<Integer> getPageNumbers(ManualRedactPdfRequest request, int pagesCount) {
|
private List<Integer> getPageNumbers(ManualRedactPdfRequest request, int pagesCount) {
|
||||||
String pageNumbersInput = request.getPageNumbers();
|
String pageNumbersInput = request.getPageNumbers();
|
||||||
String[] parsedPageNumbers = pageNumbersInput != null ? pageNumbersInput.split(",") : new String[0];
|
String[] parsedPageNumbers =
|
||||||
List<Integer> pageNumbers = GeneralUtils.parsePageList(parsedPageNumbers, pagesCount, false);
|
pageNumbersInput != null ? pageNumbersInput.split(",") : new String[0];
|
||||||
|
List<Integer> pageNumbers =
|
||||||
|
GeneralUtils.parsePageList(parsedPageNumbers, pagesCount, false);
|
||||||
Collections.sort(pageNumbers);
|
Collections.sort(pageNumbers);
|
||||||
return pageNumbers;
|
return pageNumbers;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package stirling.software.SPDF.model.api.security;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
package stirling.software.SPDF.model.api.security;
|
package stirling.software.SPDF.model.api.security;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class RedactionArea {
|
public class RedactionArea {
|
||||||
@Schema(description = "The left edge point of the area to be redacted.")
|
@Schema(description = "The left edge point of the area to be redacted.")
|
||||||
private Double x;
|
private Double x;
|
||||||
|
|
||||||
@Schema(description = "The top edge point of the area to be redacted.")
|
@Schema(description = "The top edge point of the area to be redacted.")
|
||||||
private Double y;
|
private Double y;
|
||||||
|
|
||||||
@Schema(description = "The height of the area to be redacted.")
|
@Schema(description = "The height of the area to be redacted.")
|
||||||
private Double height;
|
private Double height;
|
||||||
|
|
||||||
@Schema(description = "The width of the area to be redacted.")
|
@Schema(description = "The width of the area to be redacted.")
|
||||||
private Double width;
|
private Double width;
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ public class StringToArrayListPropertyEditor extends PropertyEditorSupport {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
|
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
|
||||||
TypeReference<ArrayList<RedactionArea>> typeRef = new TypeReference<ArrayList<RedactionArea>>() {
|
TypeReference<ArrayList<RedactionArea>> typeRef =
|
||||||
};
|
new TypeReference<ArrayList<RedactionArea>>() {};
|
||||||
List<RedactionArea> list = objectMapper.readValue(text, typeRef);
|
List<RedactionArea> list = objectMapper.readValue(text, typeRef);
|
||||||
setValue(list);
|
setValue(list);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
10
test.sh
10
test.sh
@@ -73,15 +73,15 @@ main() {
|
|||||||
|
|
||||||
|
|
||||||
# Building Docker images
|
# Building Docker images
|
||||||
# docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest -f ./Dockerfile .
|
# docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest -f ./Dockerfile .
|
||||||
# docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite .
|
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite .
|
||||||
|
|
||||||
# Test each configuration
|
# Test each configuration
|
||||||
#run_tests "Stirling-PDF-Ultra-Lite" "./exampleYmlFiles/docker-compose-latest-ultra-lite.yml"
|
run_tests "Stirling-PDF-Ultra-Lite" "./exampleYmlFiles/docker-compose-latest-ultra-lite.yml"
|
||||||
#docker-compose -f "./exampleYmlFiles/docker-compose-latest-ultra-lite.yml" down
|
docker-compose -f "./exampleYmlFiles/docker-compose-latest-ultra-lite.yml" down
|
||||||
|
|
||||||
|
|
||||||
# run_tests "Stirling-PDF" "./exampleYmlFiles/docker-compose-latest.yml"
|
#run_tests "Stirling-PDF" "./exampleYmlFiles/docker-compose-latest.yml"
|
||||||
#docker-compose -f "./exampleYmlFiles/docker-compose-latest.yml" down
|
#docker-compose -f "./exampleYmlFiles/docker-compose-latest.yml" down
|
||||||
|
|
||||||
export DOCKER_ENABLE_SECURITY=true
|
export DOCKER_ENABLE_SECURITY=true
|
||||||
|
|||||||
Reference in New Issue
Block a user