Skip to content

Commit

Permalink
Impl AD role extractor, refactor configs
Browse files Browse the repository at this point in the history
  • Loading branch information
Haarolean committed Dec 26, 2024
1 parent 9d257c9 commit 50bae9f
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 56 deletions.
105 changes: 53 additions & 52 deletions api/src/main/java/io/kafbat/ui/config/auth/LdapSecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package io.kafbat.ui.config.auth;

import static io.kafbat.ui.config.auth.AbstractAuthSecurityConfig.AUTH_WHITELIST;

import io.kafbat.ui.service.rbac.AccessControlService;
import io.kafbat.ui.service.rbac.extractor.RbacActiveDirectoryAuthoritiesExtractor;
import io.kafbat.ui.service.rbac.extractor.RbacLdapAuthoritiesExtractor;
import java.util.Collection;
import java.util.List;
Expand All @@ -17,7 +16,6 @@
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManagerAdapter;
Expand All @@ -29,10 +27,11 @@
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.BindAuthenticator;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.NullLdapAuthoritiesPopulator;
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.ad.DefaultActiveDirectoryAuthoritiesPopulator;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.security.ldap.search.LdapUserSearch;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
import org.springframework.security.web.server.SecurityWebFilterChain;
Expand All @@ -43,52 +42,55 @@
@EnableConfigurationProperties(LdapProperties.class)
@RequiredArgsConstructor
@Slf4j
public class LdapSecurityConfig {
public class LdapSecurityConfig extends AbstractAuthSecurityConfig {

private final LdapProperties props;

@Bean
public ReactiveAuthenticationManager authenticationManager(LdapContextSource ldapContextSource,
LdapAuthoritiesPopulator authoritiesExtractor,
AccessControlService acs) {
public ReactiveAuthenticationManager authenticationManager(AbstractLdapAuthenticationProvider authProvider) {
return new ReactiveAuthenticationManagerAdapter(new ProviderManager(List.of(authProvider)));
}

@Bean
public AbstractLdapAuthenticationProvider authenticationProvider(LdapAuthoritiesPopulator authoritiesExtractor,
BindAuthenticator bindAuthenticator,
AccessControlService acs) {
var rbacEnabled = acs.isRbacEnabled();

AbstractLdapAuthenticationProvider authProvider;

if (!props.isActiveDirectory()) {
authProvider = new LdapAuthenticationProvider(bindAuthenticator, authoritiesExtractor);
} else {
authProvider = new ActiveDirectoryLdapAuthenticationProvider(props.getActiveDirectoryDomain(),
props.getUrls());
authProvider.setUseAuthenticationRequestCredentials(true);
((ActiveDirectoryLdapAuthenticationProvider) authProvider).setAuthoritiesPopulator(authoritiesExtractor);
}

if (rbacEnabled) {
authProvider.setUserDetailsContextMapper(new RbacUserDetailsMapper());
}

return authProvider;
}

@Bean
public BindAuthenticator ldapBindAuthentication(LdapContextSource ldapContextSource) {
BindAuthenticator ba = new BindAuthenticator(ldapContextSource);

if (props.getBase() != null) {
ba.setUserDnPatterns(new String[] {props.getBase()});
}

if (props.getUserFilterSearchFilter() != null) {
LdapUserSearch userSearch =
new FilterBasedLdapUserSearch(props.getUserFilterSearchBase(), props.getUserFilterSearchFilter(),
ldapContextSource);
ba.setUserSearch(userSearch);
}

var authenticationProvider = getAuthenticationProvider(authoritiesExtractor, rbacEnabled, ba);

AuthenticationManager am = new ProviderManager(List.of(authenticationProvider));

return new ReactiveAuthenticationManagerAdapter(am);
}

private AbstractLdapAuthenticationProvider getAuthenticationProvider(LdapAuthoritiesPopulator authoritiesExtractor,
boolean rbacEnabled,
BindAuthenticator bindAuthenticator) {
AbstractLdapAuthenticationProvider authenticationProvider;

if (!props.isActiveDirectory()) {
authenticationProvider = rbacEnabled
? new LdapAuthenticationProvider(bindAuthenticator, authoritiesExtractor)
: new LdapAuthenticationProvider(bindAuthenticator);
} else {
authenticationProvider = new ActiveDirectoryLdapAuthenticationProvider(props.getActiveDirectoryDomain(),
props.getUrls());
authenticationProvider.setUseAuthenticationRequestCredentials(true);
}

if (rbacEnabled) {
authenticationProvider.setUserDetailsContextMapper(new UserDetailsMapper());
}
return authenticationProvider;
return ba;
}

@Bean
Expand All @@ -102,28 +104,27 @@ public LdapContextSource ldapContextSource() {
}

@Bean
public DefaultLdapAuthoritiesPopulator ldapAuthoritiesExtractor(ApplicationContext context,
BaseLdapPathContextSource contextSource,
AccessControlService acs) {
if (props.isActiveDirectory()) {
return null;
}
public LdapAuthoritiesPopulator authoritiesExtractor(ApplicationContext ctx,
BaseLdapPathContextSource ldapCtx,
AccessControlService acs) {
if (!props.isActiveDirectory()) {
if (!acs.isRbacEnabled()) {
return new NullLdapAuthoritiesPopulator();
}

var rbacEnabled = acs != null && acs.isRbacEnabled();
var extractor = new RbacLdapAuthoritiesExtractor(ctx, ldapCtx, props.getGroupFilterSearchBase());

DefaultLdapAuthoritiesPopulator extractor;
Optional.ofNullable(props.getGroupFilterSearchFilter()).ifPresent(extractor::setGroupSearchFilter);
extractor.setRolePrefix("");
extractor.setConvertToUpperCase(false);
extractor.setSearchSubtree(true);

if (rbacEnabled) {
extractor = new RbacLdapAuthoritiesExtractor(context, contextSource, props.getGroupFilterSearchBase());
return extractor;
} else {
extractor = new DefaultLdapAuthoritiesPopulator(contextSource, props.getGroupFilterSearchBase());
return acs.isRbacEnabled()
? new RbacActiveDirectoryAuthoritiesExtractor(ctx)
: new DefaultActiveDirectoryAuthoritiesPopulator();
}

Optional.ofNullable(props.getGroupFilterSearchFilter()).ifPresent(extractor::setGroupSearchFilter);
extractor.setRolePrefix("");
extractor.setConvertToUpperCase(false);
extractor.setSearchSubtree(true);
return extractor;
}

@Bean
Expand All @@ -145,7 +146,7 @@ public SecurityWebFilterChain configureLdap(ServerHttpSecurity http) {
.build();
}

private static class UserDetailsMapper extends LdapUserDetailsMapper {
private static class RbacUserDetailsMapper extends LdapUserDetailsMapper {
@Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.kafbat.ui.service.rbac.extractor;

import io.kafbat.ui.model.rbac.Role;
import io.kafbat.ui.model.rbac.provider.Provider;
import io.kafbat.ui.service.rbac.AccessControlService;
import java.util.Collection;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.ldap.authentication.ad.DefaultActiveDirectoryAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;

@Slf4j
public class RbacActiveDirectoryAuthoritiesExtractor implements LdapAuthoritiesPopulator {

private final DefaultActiveDirectoryAuthoritiesPopulator populator = new DefaultActiveDirectoryAuthoritiesPopulator();
private final AccessControlService acs;

public RbacActiveDirectoryAuthoritiesExtractor(ApplicationContext context) {
this.acs = context.getBean(AccessControlService.class);
}

@Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
var adGroups = populator.getGrantedAuthorities(userData, username)
.stream()
.map(GrantedAuthority::getAuthority)
.peek(group -> log.trace("Found AD group [{}] for user [{}]", group, username))
.collect(Collectors.toSet());

return acs.getRoles()
.stream()
.filter(r -> r.getSubjects()
.stream()
.filter(subject -> subject.getProvider().equals(Provider.LDAP_AD))
.filter(subject -> subject.getType().equals("group"))
.anyMatch(subject -> adGroups.contains(subject.getValue()))
)
.map(Role::getName)
.peek(role -> log.trace("Mapped role [{}] for user [{}]", role, username))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@
@Slf4j
public class RbacLdapAuthoritiesExtractor extends NestedLdapAuthoritiesPopulator {

private static final Set<Provider> SUPPORTED_PROVIDERS = Set.of(Provider.LDAP, Provider.LDAP_AD);

private final AccessControlService acs;

public RbacLdapAuthoritiesExtractor(ApplicationContext context,
BaseLdapPathContextSource contextSource, String groupFilterSearchBase) {
BaseLdapPathContextSource contextSource,
String groupFilterSearchBase) {
super(contextSource, groupFilterSearchBase);
this.acs = context.getBean(AccessControlService.class);
}
Expand All @@ -38,7 +37,7 @@ protected Set<GrantedAuthority> getAdditionalRoles(DirContextOperations user, St
.stream()
.filter(r -> r.getSubjects()
.stream()
.filter(subject -> SUPPORTED_PROVIDERS.contains(subject.getProvider()))
.filter(subject -> subject.getProvider().equals(Provider.LDAP))
.filter(subject -> subject.getType().equals("group"))
.anyMatch(subject -> ldapGroups.contains(subject.getValue()))
)
Expand Down

0 comments on commit 50bae9f

Please sign in to comment.