ldap testing

This commit is contained in:
Anthony Stirling 2025-03-31 10:41:38 +01:00
parent f5eacceaab
commit 27604247ec
7 changed files with 139 additions and 7 deletions

View File

@ -397,6 +397,9 @@ dependencies {
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
implementation 'org.springframework.security:spring-security-ldap'
implementation 'org.springframework.ldap:spring-ldap-core'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'

View File

@ -43,6 +43,7 @@ public class EEAppConfig {
return applicationProperties.getPremium().getProFeatures().isSsoAutoLogin();
}
// TODO: Remove post migration
public void migrateEnterpriseSettingsToPremium(ApplicationProperties applicationProperties) {
EnterpriseEdition enterpriseEdition = applicationProperties.getEnterpriseEdition();

View File

@ -8,6 +8,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@ -63,6 +64,7 @@ public class SecurityConfiguration {
private final GrantedAuthoritiesMapper oAuth2userAuthoritiesMapper;
private final RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations;
private final OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver;
private final AuthenticationProvider ldapAuthenticationProvider;
public SecurityConfiguration(
PersistentLoginRepository persistentLoginRepository,
@ -79,7 +81,7 @@ public class SecurityConfiguration {
@Autowired(required = false)
RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations,
@Autowired(required = false)
OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver) {
OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver, @Autowired(required = false) AuthenticationProvider ldapAuthenticationProvider) {
this.userDetailsService = userDetailsService;
this.userService = userService;
this.loginEnabledValue = loginEnabledValue;
@ -93,6 +95,7 @@ public class SecurityConfiguration {
this.oAuth2userAuthoritiesMapper = oAuth2userAuthoritiesMapper;
this.saml2RelyingPartyRegistrations = saml2RelyingPartyRegistrations;
this.saml2AuthenticationRequestResolver = saml2AuthenticationRequestResolver;
this.ldapAuthenticationProvider = ldapAuthenticationProvider;
}
@Bean
@ -284,10 +287,20 @@ public class SecurityConfiguration {
}
});
}
} else {
log.debug("SAML 2 login is not enabled. Using default.");
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
}
if (applicationProperties.getSecurity().getLdap().getEnabled()) {
if (ldapAuthenticationProvider != null) {
http.authenticationProvider(ldapAuthenticationProvider);
}
}
return http.build();
}

View File

@ -0,0 +1,86 @@
package stirling.software.SPDF.config.security.ldap;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.ldap.authentication.BindAuthenticator;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.LdapAuthenticator;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import stirling.software.SPDF.model.ApplicationProperties;
@Configuration
@ConditionalOnProperty(name = "security.ldap.enabled", havingValue = "true")
public class LDAPConfiguration {
private final ApplicationProperties applicationProperties;
public LDAPConfiguration(ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
}
@Bean
public LdapContextSource ldapContextSource() {
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl(applicationProperties.getSecurity().getLdap().getUrl());
contextSource.setBase(applicationProperties.getSecurity().getLdap().getBaseDn());
String managerDn = applicationProperties.getSecurity().getLdap().getManagerDn();
String managerPassword = applicationProperties.getSecurity().getLdap().getManagerPassword();
if (managerDn != null && !managerDn.isEmpty() && managerPassword != null) {
contextSource.setUserDn(managerDn);
contextSource.setPassword(managerPassword);
}
contextSource.afterPropertiesSet();
return contextSource;
}
@Bean
public LdapAuthoritiesPopulator ldapAuthoritiesPopulator(LdapContextSource contextSource) {
DefaultLdapAuthoritiesPopulator authoritiesPopulator = new DefaultLdapAuthoritiesPopulator(
contextSource,
applicationProperties.getSecurity().getLdap().getGroupSearchBase()
);
authoritiesPopulator.setGroupSearchFilter(applicationProperties.getSecurity().getLdap().getGroupSearchFilter());
authoritiesPopulator.setRolePrefix("ROLE_");
return authoritiesPopulator;
}
@Bean
public LdapAuthenticator ldapAuthenticator(LdapContextSource contextSource) {
BindAuthenticator authenticator = new BindAuthenticator(contextSource);
String userDnPattern = applicationProperties.getSecurity().getLdap().getUserDnPattern();
if (userDnPattern != null && !userDnPattern.isEmpty()) {
authenticator.setUserDnPatterns(new String[]{userDnPattern});
} else {
String userSearchBase = applicationProperties.getSecurity().getLdap().getUserSearchBase();
String userSearchFilter = applicationProperties.getSecurity().getLdap().getUserSearchFilter();
if (userSearchBase != null && userSearchFilter != null) {
FilterBasedLdapUserSearch userSearch = new FilterBasedLdapUserSearch(
userSearchBase,
userSearchFilter,
contextSource
);
authenticator.setUserSearch(userSearch);
} else {
throw new IllegalStateException("LDAP configuration requires either userDnPattern or userSearchBase and userSearchFilter.");
}
}
return authenticator;
}
@Bean
public AuthenticationProvider ldapAuthenticationProvider(
LdapAuthenticator authenticator,
LdapAuthoritiesPopulator authoritiesPopulator) {
return new LdapAuthenticationProvider(authenticator, authoritiesPopulator);
}
}

View File

@ -122,7 +122,8 @@ public class OtherWebController {
return Arrays.stream(files)
.filter(file -> file.getName().endsWith(".traineddata"))
.map(file -> file.getName().replace(".traineddata", ""))
.filter(lang -> !lang.equalsIgnoreCase("osd")).sorted()
.filter(lang -> !lang.equalsIgnoreCase("osd"))
.sorted()
.toList();
}

View File

@ -108,6 +108,7 @@ public class ApplicationProperties {
private InitialLogin initialLogin = new InitialLogin();
private OAUTH2 oauth2 = new OAUTH2();
private SAML2 saml2 = new SAML2();
private LDAP ldap = new LDAP();
private int loginAttemptCount;
private long loginResetTimeMinutes;
private String loginMethod = "all";
@ -218,6 +219,25 @@ public class ApplicationProperties {
}
}
@Data
@ToString
public static class LDAP {
private Boolean enabled = false;
private String url;
private String baseDn;
private String userDnPattern;
private String groupSearchBase;
private String managerDn;
@ToString.Exclude private String managerPassword;
public boolean isSettingsValid() {
return enabled
&& !isStringEmpty(url)
&& !isStringEmpty(baseDn)
&& !isStringEmpty(userDnPattern);
}
}
@Data
public static class OAUTH2 {
private Boolean enabled = false;

View File

@ -60,7 +60,14 @@ security:
idpCert: classpath:okta.cert # The certificate your Provider will use to authenticate your app's SAML authentication requests. Provided by your Provider
privateKey: classpath:saml-private-key.key # Your private key. Generated from your keypair
spCert: classpath:saml-public-cert.crt # Your signing certificate. Generated from your keypair
ldap:
enabled: true
url: ldap://your-ldap-server:389
base-dn: dc=example,dc=com
user-dn-pattern: uid={0},ou=people
group-search-base: ou=groups
manager-dn: cn=admin,dc=example,dc=com # If needed
manager-password: admin-password # If needed
premium:
key: 00000000-0000-0000-0000-000000000000
@ -70,8 +77,9 @@ premium:
CustomMetadata:
autoUpdateMetadata: false
author: username
creator: Stirling-PDF
producer: Stirling-PDF
creator: Team1
producer:
legal:
termsAndConditions: https://www.stirlingpdf.com/terms-and-conditions # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder