Skip to content

Commit 9f79a56

Browse files
authored
BE: RBAC: Impl Active Directory populator (#717)
+ BE: RBAC: LDAP: Implement user subject type for LDAP & AD. Resolves #54, resolves #730
1 parent d093752 commit 9f79a56

File tree

4 files changed

+113
-42
lines changed

4 files changed

+113
-42
lines changed

api/src/main/java/io/kafbat/ui/config/auth/LdapSecurityConfig.java

Lines changed: 55 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package io.kafbat.ui.config.auth;
22

33
import io.kafbat.ui.service.rbac.AccessControlService;
4+
import io.kafbat.ui.service.rbac.extractor.RbacActiveDirectoryAuthoritiesExtractor;
45
import io.kafbat.ui.service.rbac.extractor.RbacLdapAuthoritiesExtractor;
56
import io.kafbat.ui.util.StaticFileWebFilter;
67
import java.util.Collection;
78
import java.util.List;
89
import java.util.Optional;
910
import lombok.RequiredArgsConstructor;
1011
import lombok.extern.slf4j.Slf4j;
12+
import org.springframework.beans.factory.annotation.Autowired;
1113
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
1214
import org.springframework.boot.context.properties.EnableConfigurationProperties;
1315
import org.springframework.context.ApplicationContext;
@@ -17,7 +19,6 @@
1719
import org.springframework.ldap.core.DirContextOperations;
1820
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
1921
import org.springframework.ldap.core.support.LdapContextSource;
20-
import org.springframework.security.authentication.AuthenticationManager;
2122
import org.springframework.security.authentication.ProviderManager;
2223
import org.springframework.security.authentication.ReactiveAuthenticationManager;
2324
import org.springframework.security.authentication.ReactiveAuthenticationManagerAdapter;
@@ -29,10 +30,11 @@
2930
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
3031
import org.springframework.security.ldap.authentication.BindAuthenticator;
3132
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
33+
import org.springframework.security.ldap.authentication.NullLdapAuthoritiesPopulator;
3234
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
35+
import org.springframework.security.ldap.authentication.ad.DefaultActiveDirectoryAuthoritiesPopulator;
3336
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
3437
import org.springframework.security.ldap.search.LdapUserSearch;
35-
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
3638
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
3739
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
3840
import org.springframework.security.web.server.SecurityWebFilterChain;
@@ -49,39 +51,51 @@ public class LdapSecurityConfig extends AbstractAuthSecurityConfig {
4951
private final LdapProperties props;
5052

5153
@Bean
52-
public ReactiveAuthenticationManager authenticationManager(LdapContextSource ldapContextSource,
53-
LdapAuthoritiesPopulator authoritiesExtractor,
54-
AccessControlService acs) {
54+
public ReactiveAuthenticationManager authenticationManager(AbstractLdapAuthenticationProvider authProvider) {
55+
return new ReactiveAuthenticationManagerAdapter(new ProviderManager(List.of(authProvider)));
56+
}
57+
58+
@Bean
59+
public AbstractLdapAuthenticationProvider authenticationProvider(LdapAuthoritiesPopulator authoritiesExtractor,
60+
@Autowired(required = false) BindAuthenticator ba,
61+
AccessControlService acs) {
5562
var rbacEnabled = acs.isRbacEnabled();
63+
64+
AbstractLdapAuthenticationProvider authProvider;
65+
66+
if (!props.isActiveDirectory()) {
67+
authProvider = new LdapAuthenticationProvider(ba, authoritiesExtractor);
68+
} else {
69+
authProvider = new ActiveDirectoryLdapAuthenticationProvider(props.getActiveDirectoryDomain(),
70+
props.getUrls());
71+
authProvider.setUseAuthenticationRequestCredentials(true);
72+
((ActiveDirectoryLdapAuthenticationProvider) authProvider).setAuthoritiesPopulator(authoritiesExtractor);
73+
}
74+
75+
if (rbacEnabled) {
76+
authProvider.setUserDetailsContextMapper(new RbacUserDetailsMapper());
77+
}
78+
79+
return authProvider;
80+
}
81+
82+
@Bean
83+
@ConditionalOnProperty(value = "oauth2.ldap.activeDirectory", havingValue = "false")
84+
public BindAuthenticator ldapBindAuthentication(LdapContextSource ldapContextSource) {
5685
BindAuthenticator ba = new BindAuthenticator(ldapContextSource);
86+
5787
if (props.getBase() != null) {
5888
ba.setUserDnPatterns(new String[] {props.getBase()});
5989
}
90+
6091
if (props.getUserFilterSearchFilter() != null) {
6192
LdapUserSearch userSearch =
6293
new FilterBasedLdapUserSearch(props.getUserFilterSearchBase(), props.getUserFilterSearchFilter(),
6394
ldapContextSource);
6495
ba.setUserSearch(userSearch);
6596
}
6697

67-
AbstractLdapAuthenticationProvider authenticationProvider;
68-
if (!props.isActiveDirectory()) {
69-
authenticationProvider = rbacEnabled
70-
? new LdapAuthenticationProvider(ba, authoritiesExtractor)
71-
: new LdapAuthenticationProvider(ba);
72-
} else {
73-
authenticationProvider = new ActiveDirectoryLdapAuthenticationProvider(props.getActiveDirectoryDomain(),
74-
props.getUrls()); // TODO Issue #3741
75-
authenticationProvider.setUseAuthenticationRequestCredentials(true);
76-
}
77-
78-
if (rbacEnabled) {
79-
authenticationProvider.setUserDetailsContextMapper(new UserDetailsMapper());
80-
}
81-
82-
AuthenticationManager am = new ProviderManager(List.of(authenticationProvider));
83-
84-
return new ReactiveAuthenticationManagerAdapter(am);
98+
return ba;
8599
}
86100

87101
@Bean
@@ -95,24 +109,27 @@ public LdapContextSource ldapContextSource() {
95109
}
96110

97111
@Bean
98-
public DefaultLdapAuthoritiesPopulator ldapAuthoritiesExtractor(ApplicationContext context,
99-
BaseLdapPathContextSource contextSource,
100-
AccessControlService acs) {
101-
var rbacEnabled = acs != null && acs.isRbacEnabled();
112+
public LdapAuthoritiesPopulator authoritiesExtractor(ApplicationContext ctx,
113+
BaseLdapPathContextSource ldapCtx,
114+
AccessControlService acs) {
115+
if (!props.isActiveDirectory()) {
116+
if (!acs.isRbacEnabled()) {
117+
return new NullLdapAuthoritiesPopulator();
118+
}
102119

103-
DefaultLdapAuthoritiesPopulator extractor;
120+
var extractor = new RbacLdapAuthoritiesExtractor(ctx, ldapCtx, props.getGroupFilterSearchBase());
104121

105-
if (rbacEnabled) {
106-
extractor = new RbacLdapAuthoritiesExtractor(context, contextSource, props.getGroupFilterSearchBase());
122+
Optional.ofNullable(props.getGroupFilterSearchFilter()).ifPresent(extractor::setGroupSearchFilter);
123+
extractor.setRolePrefix("");
124+
extractor.setConvertToUpperCase(false);
125+
extractor.setSearchSubtree(true);
126+
127+
return extractor;
107128
} else {
108-
extractor = new DefaultLdapAuthoritiesPopulator(contextSource, props.getGroupFilterSearchBase());
129+
return acs.isRbacEnabled()
130+
? new RbacActiveDirectoryAuthoritiesExtractor(ctx)
131+
: new DefaultActiveDirectoryAuthoritiesPopulator();
109132
}
110-
111-
Optional.ofNullable(props.getGroupFilterSearchFilter()).ifPresent(extractor::setGroupSearchFilter);
112-
extractor.setRolePrefix("");
113-
extractor.setConvertToUpperCase(false);
114-
extractor.setSearchSubtree(true);
115-
return extractor;
116133
}
117134

118135
@Bean
@@ -142,7 +159,7 @@ public SecurityWebFilterChain configureLdap(ServerHttpSecurity http) {
142159
return builder.build();
143160
}
144161

145-
private static class UserDetailsMapper extends LdapUserDetailsMapper {
162+
private static class RbacUserDetailsMapper extends LdapUserDetailsMapper {
146163
@Override
147164
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
148165
Collection<? extends GrantedAuthority> authorities) {

api/src/main/java/io/kafbat/ui/service/AdminClientServiceImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ private Mono<ReactiveAdminClient> createAdminClient(KafkaCluster cluster) {
5353
return AdminClient.create(properties);
5454
}).flatMap(ac -> ReactiveAdminClient.create(ac).doOnError(th -> ac.close()))
5555
.onErrorMap(th -> new IllegalStateException(
56-
"Error while creating AdminClient for Cluster " + cluster.getName(), th));
56+
"Error while creating AdminClient for the cluster " + cluster.getName(), th));
5757
}
5858

5959
@Override
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package io.kafbat.ui.service.rbac.extractor;
2+
3+
import io.kafbat.ui.model.rbac.Role;
4+
import io.kafbat.ui.model.rbac.provider.Provider;
5+
import io.kafbat.ui.service.rbac.AccessControlService;
6+
import java.util.Collection;
7+
import java.util.stream.Collectors;
8+
import lombok.extern.slf4j.Slf4j;
9+
import org.springframework.context.ApplicationContext;
10+
import org.springframework.ldap.core.DirContextOperations;
11+
import org.springframework.security.core.GrantedAuthority;
12+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
13+
import org.springframework.security.ldap.authentication.ad.DefaultActiveDirectoryAuthoritiesPopulator;
14+
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
15+
16+
@Slf4j
17+
public class RbacActiveDirectoryAuthoritiesExtractor implements LdapAuthoritiesPopulator {
18+
19+
private final DefaultActiveDirectoryAuthoritiesPopulator populator = new DefaultActiveDirectoryAuthoritiesPopulator();
20+
private final AccessControlService acs;
21+
22+
public RbacActiveDirectoryAuthoritiesExtractor(ApplicationContext context) {
23+
this.acs = context.getBean(AccessControlService.class);
24+
}
25+
26+
@Override
27+
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
28+
var adGroups = populator.getGrantedAuthorities(userData, username)
29+
.stream()
30+
.map(GrantedAuthority::getAuthority)
31+
.peek(group -> log.trace("Found AD group [{}] for user [{}]", group, username))
32+
.collect(Collectors.toSet());
33+
34+
return acs.getRoles()
35+
.stream()
36+
.filter(r -> r.getSubjects()
37+
.stream()
38+
.filter(subject -> subject.getProvider().equals(Provider.LDAP_AD))
39+
.anyMatch(subject -> switch (subject.getType()) {
40+
case "user" -> username.equalsIgnoreCase(subject.getValue());
41+
case "group" -> adGroups.contains(subject.getValue());
42+
default -> false;
43+
})
44+
)
45+
.map(Role::getName)
46+
.peek(role -> log.trace("Mapped role [{}] for user [{}]", role, username))
47+
.map(SimpleGrantedAuthority::new)
48+
.collect(Collectors.toSet());
49+
}
50+
}

api/src/main/java/io/kafbat/ui/service/rbac/extractor/RbacLdapAuthoritiesExtractor.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public class RbacLdapAuthoritiesExtractor extends NestedLdapAuthoritiesPopulator
1919
private final AccessControlService acs;
2020

2121
public RbacLdapAuthoritiesExtractor(ApplicationContext context,
22-
BaseLdapPathContextSource contextSource, String groupFilterSearchBase) {
22+
BaseLdapPathContextSource contextSource,
23+
String groupFilterSearchBase) {
2324
super(contextSource, groupFilterSearchBase);
2425
this.acs = context.getBean(AccessControlService.class);
2526
}
@@ -37,8 +38,11 @@ protected Set<GrantedAuthority> getAdditionalRoles(DirContextOperations user, St
3738
.filter(r -> r.getSubjects()
3839
.stream()
3940
.filter(subject -> subject.getProvider().equals(Provider.LDAP))
40-
.filter(subject -> subject.getType().equals("group"))
41-
.anyMatch(subject -> ldapGroups.contains(subject.getValue()))
41+
.anyMatch(subject -> switch (subject.getType()) {
42+
case "user" -> username.equalsIgnoreCase(subject.getValue());
43+
case "group" -> ldapGroups.contains(subject.getValue());
44+
default -> false;
45+
})
4246
)
4347
.map(Role::getName)
4448
.peek(role -> log.trace("Mapped role [{}] for user [{}]", role, username))

0 commit comments

Comments
 (0)