Skip to content

BE: RBAC: Impl default role #1056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,34 @@
import jakarta.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("rbac")
public class RoleBasedAccessControlProperties {

private final List<Role> roles = new ArrayList<>();

private Role defaultRole;

@PostConstruct
public void init() {
roles.forEach(Role::validate);
if (defaultRole != null) {
defaultRole.validateDefaultRole();
}
}

public List<Role> getRoles() {
return roles;
}

public void setDefaultRole(Role defaultRole) {
this.defaultRole = defaultRole;
}

@Nullable
public Role getDefaultRole() {
return defaultRole;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ public class AuthorizationController implements AuthorizationApi {
private final AccessControlService accessControlService;

public Mono<ResponseEntity<AuthenticationInfoDTO>> getUserAuthInfo(ServerWebExchange exchange) {
List<UserPermissionDTO> defaultRolePermissions = accessControlService.getDefaultRole() != null
? mapPermissions(
accessControlService.getDefaultRole().getPermissions(),
accessControlService.getDefaultRole().getClusters())
: Collections.emptyList();

Mono<List<UserPermissionDTO>> permissions = AccessControlService.getUser()
.map(user -> accessControlService.getRoles()
.stream()
Expand All @@ -39,12 +45,14 @@ public Mono<ResponseEntity<AuthenticationInfoDTO>> getUserAuthInfo(ServerWebExch
.flatMap(Collection::stream)
.toList()
)
.map(userPermissions -> userPermissions.isEmpty() ? defaultRolePermissions : userPermissions)
.switchIfEmpty(Mono.just(Collections.emptyList()));

Mono<String> userName = ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(Principal::getName);


var builder = AuthenticationInfoDTO.builder()
.rbacEnabled(accessControlService.isRbacEnabled());

Expand Down
4 changes: 4 additions & 0 deletions api/src/main/java/io/kafbat/ui/model/rbac/Role.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ public void validate() {
subjects.forEach(Subject::validate);
}

public void validateDefaultRole() {
permissions.forEach(Permission::validate);
permissions.forEach(Permission::transform);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.kafbat.ui.model.ClusterDTO;
import io.kafbat.ui.model.ConnectDTO;
import io.kafbat.ui.model.InternalTopic;
import io.kafbat.ui.model.KafkaCluster;
import io.kafbat.ui.model.rbac.AccessContext;
import io.kafbat.ui.model.rbac.Permission;
import io.kafbat.ui.model.rbac.Role;
Expand All @@ -14,6 +15,7 @@
import io.kafbat.ui.model.rbac.permission.ConsumerGroupAction;
import io.kafbat.ui.model.rbac.permission.SchemaAction;
import io.kafbat.ui.model.rbac.permission.TopicAction;
import io.kafbat.ui.service.ClustersStorage;
import io.kafbat.ui.service.rbac.extractor.CognitoAuthorityExtractor;
import io.kafbat.ui.service.rbac.extractor.GithubAuthorityExtractor;
import io.kafbat.ui.service.rbac.extractor.GoogleAuthorityExtractor;
Expand Down Expand Up @@ -53,6 +55,7 @@ public class AccessControlService {
@Nullable
private final InMemoryReactiveClientRegistrationRepository clientRegistrationRepository;
private final RoleBasedAccessControlProperties properties;
private final ClustersStorage clustersStorage;
private final Environment environment;

@Getter
Expand All @@ -62,31 +65,48 @@ public class AccessControlService {

@PostConstruct
public void init() {
if (CollectionUtils.isEmpty(properties.getRoles())) {
if (CollectionUtils.isEmpty(properties.getRoles()) && properties.getDefaultRole() == null) {
log.trace("No roles provided, disabling RBAC");
return;
}
if (properties.getDefaultRole() != null) {
log.trace("Set Default Role Clusters");
properties.getDefaultRole().setClusters(
clustersStorage.getKafkaClusters().stream()
.map(KafkaCluster::getName)
.collect(Collectors.toList())
);
}
rbacEnabled = true;

this.oauthExtractors = properties.getRoles()
if (properties.getDefaultRole() != null) {
this.oauthExtractors = Set.of(
new CognitoAuthorityExtractor(),
new GoogleAuthorityExtractor(),
new GithubAuthorityExtractor(),
new OauthAuthorityExtractor()
);
} else {
this.oauthExtractors = properties.getRoles()
.stream()
.map(role -> role.getSubjects()
.stream()
.map(Subject::getProvider)
.distinct()
.map(provider -> switch (provider) {
case OAUTH_COGNITO -> new CognitoAuthorityExtractor();
case OAUTH_GOOGLE -> new GoogleAuthorityExtractor();
case OAUTH_GITHUB -> new GithubAuthorityExtractor();
case OAUTH -> new OauthAuthorityExtractor();
default -> null;
})
.filter(Objects::nonNull)
.collect(Collectors.toSet()))
.stream()
.map(Subject::getProvider)
.distinct()
.map(provider -> switch (provider) {
case OAUTH_COGNITO -> new CognitoAuthorityExtractor();
case OAUTH_GOOGLE -> new GoogleAuthorityExtractor();
case OAUTH_GITHUB -> new GithubAuthorityExtractor();
case OAUTH -> new OauthAuthorityExtractor();
default -> null;
})
.filter(Objects::nonNull)
.collect(Collectors.toSet()))
.flatMap(Set::stream)
.collect(Collectors.toSet());
}

if (!properties.getRoles().isEmpty()
if (!(properties.getRoles().isEmpty() && properties.getDefaultRole() == null)
&& "oauth2".equalsIgnoreCase(environment.getProperty("auth.type"))
&& (clientRegistrationRepository == null || !clientRegistrationRepository.iterator().hasNext())) {
log.error("Roles are configured but no authentication methods are present. Authentication might fail.");
Expand Down Expand Up @@ -114,12 +134,20 @@ private boolean isAccessible(AuthenticatedUser user, AccessContext context) {
}

private List<Permission> getUserPermissions(AuthenticatedUser user, @Nullable String clusterName) {
return properties.getRoles()
.stream()
.filter(filterRole(user))
.filter(role -> clusterName == null || role.getClusters().stream().anyMatch(clusterName::equalsIgnoreCase))
.flatMap(role -> role.getPermissions().stream())
.toList();
List<Role> filteredRoles = properties.getRoles()
.stream()
.filter(filterRole(user))
.filter(role -> clusterName == null || role.getClusters().stream().anyMatch(clusterName::equalsIgnoreCase))
.toList();

// if no roles are found, check if default role is set
if (filteredRoles.isEmpty() && properties.getDefaultRole() != null) {
return properties.getDefaultRole().getPermissions();
}

return filteredRoles.stream()
.flatMap(role -> role.getPermissions().stream())
.toList();
}

public static Mono<AuthenticatedUser> getUser() {
Expand All @@ -132,6 +160,9 @@ public static Mono<AuthenticatedUser> getUser() {

private boolean isClusterAccessible(String clusterName, AuthenticatedUser user) {
Assert.isTrue(StringUtils.isNotEmpty(clusterName), "cluster value is empty");
if (properties.getDefaultRole() != null) {
return true;
}
return properties.getRoles()
.stream()
.filter(filterRole(user))
Expand Down Expand Up @@ -200,6 +231,10 @@ public List<Role> getRoles() {
return Collections.unmodifiableList(properties.getRoles());
}

public Role getDefaultRole() {
return properties.getDefaultRole();
}

private Predicate<Role> filterRole(AuthenticatedUser user) {
return role -> user.groups().contains(role.getName());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,13 @@ public Mono<Set<String>> extract(AccessControlService acs, Object value, Map<Str

var usernameRoles = extractUsernameRoles(acs, principal);
var groupRoles = extractGroupRoles(acs, principal);
var unionRoles = Sets.union(usernameRoles, groupRoles);

return Mono.just(Sets.union(usernameRoles, groupRoles));
if (unionRoles.isEmpty() && acs.getDefaultRole() != null) {
return Mono.just(Set.of(acs.getDefaultRole().getName()));
}

return Mono.just(unionRoles);
}

private Set<String> extractUsernameRoles(AccessControlService acs, DefaultOAuth2User principal) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,15 @@ public Mono<Set<String>> extract(AccessControlService acs, Object value, Map<Str
Mono<Set<String>> organizationRoles = getOrganizationRoles(principal, additionalParams, acs, webClient);
Mono<Set<String>> teamRoles = getTeamRoles(webClient, additionalParams, acs);

Set<String> defaultRoles = acs.getDefaultRole() == null
? Collections.emptySet()
: Set.of(acs.getDefaultRole().getName());

return Mono.zip(organizationRoles, teamRoles)
.map((t) -> Stream.of(t.getT1(), t.getT2(), usernameRoles)
.flatMap(Collection::stream)
.collect(Collectors.toSet()));
.collect(Collectors.toSet()))
.map(roles -> roles.isEmpty() ? defaultRoles : roles);
}

private Set<String> extractUsernameRoles(DefaultOAuth2User principal, AccessControlService acs) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,13 @@ public Mono<Set<String>> extract(AccessControlService acs, Object value, Map<Str

var usernameRoles = extractUsernameRoles(acs, principal);
var domainRoles = extractDomainRoles(acs, principal);
var unionRoles = Sets.union(usernameRoles, domainRoles);

return Mono.just(Sets.union(usernameRoles, domainRoles));
if (unionRoles.isEmpty() && acs.getDefaultRole() != null) {
return Mono.just(Set.of(acs.getDefaultRole().getName()));
}

return Mono.just(unionRoles);
}

private Set<String> extractUsernameRoles(AccessControlService acs, DefaultOAuth2User principal) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,13 @@ public Mono<Set<String>> extract(AccessControlService acs, Object value, Map<Str

var usernameRoles = extractUsernameRoles(acs, principal);
var roles = extractRoles(acs, principal, additionalParams);
var unionRoles = Sets.union(usernameRoles, roles);

return Mono.just(Sets.union(usernameRoles, roles));
if (unionRoles.isEmpty() && acs.getDefaultRole() != null) {
return Mono.just(Set.of(acs.getDefaultRole().getName()));
}

return Mono.just(unionRoles);
}

private Set<String> extractUsernameRoles(AccessControlService acs, DefaultOAuth2User principal) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.kafbat.ui.model.rbac.provider.Provider;
import io.kafbat.ui.service.rbac.AccessControlService;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
Expand Down Expand Up @@ -31,7 +32,7 @@ public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOp
.peek(group -> log.trace("Found AD group [{}] for user [{}]", group, username))
.collect(Collectors.toSet());

return acs.getRoles()
var grantedAuthorities = acs.getRoles()
.stream()
.filter(r -> r.getSubjects()
.stream()
Expand All @@ -46,5 +47,10 @@ public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOp
.peek(role -> log.trace("Mapped role [{}] for user [{}]", role, username))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet());

if (grantedAuthorities.isEmpty() && acs.getDefaultRole() != null) {
return Set.of(new SimpleGrantedAuthority(acs.getDefaultRole().getName()));
}
return grantedAuthorities;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
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.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -33,7 +34,7 @@ protected Set<GrantedAuthority> getAdditionalRoles(DirContextOperations user, St
.peek(group -> log.trace("Found LDAP group [{}] for user [{}]", group, username))
.collect(Collectors.toSet());

return acs.getRoles()
var simpleGrantedAuthorities = acs.getRoles()
.stream()
.filter(r -> r.getSubjects()
.stream()
Expand All @@ -48,5 +49,10 @@ protected Set<GrantedAuthority> getAdditionalRoles(DirContextOperations user, St
.peek(role -> log.trace("Mapped role [{}] for user [{}]", role, username))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet());

if (simpleGrantedAuthorities.isEmpty() && acs.getDefaultRole() != null) {
return Set.of(new SimpleGrantedAuthority(acs.getDefaultRole().getName()));
}
return new HashSet<>(simpleGrantedAuthorities);
}
}
Loading