Skip to content
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

RBAC: Implement roles by github teams #4093

Merged
merged 5 commits into from
Aug 7, 2023
Merged
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 @@ -5,6 +5,8 @@
import com.provectus.kafka.ui.model.rbac.Role;
import com.provectus.kafka.ui.model.rbac.provider.Provider;
import com.provectus.kafka.ui.service.rbac.AccessControlService;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
Expand All @@ -26,6 +28,8 @@ public class GithubAuthorityExtractor implements ProviderAuthorityExtractor {
private static final String ORGANIZATION_ATTRIBUTE_NAME = "organizations_url";
private static final String USERNAME_ATTRIBUTE_NAME = "login";
private static final String ORGANIZATION_NAME = "login";
Haarolean marked this conversation as resolved.
Show resolved Hide resolved
private static final String ORGANIZATION = "organization";
private static final String TEAM_NAME = "slug";
private static final String GITHUB_ACCEPT_HEADER = "application/vnd.github+json";
private static final String DUMMY = "dummy";
// The number of results (max 100) per page of list organizations for authenticated user.
Expand All @@ -46,7 +50,7 @@ public Mono<Set<String>> extract(AccessControlService acs, Object value, Map<Str
throw new RuntimeException();
}

Set<String> groupsByUsername = new HashSet<>();
Set<String> rolesByUsername = new HashSet<>();
String username = principal.getAttribute(USERNAME_ATTRIBUTE_NAME);
if (username == null) {
log.debug("Github username param is not present");
Expand All @@ -59,13 +63,7 @@ public Mono<Set<String>> extract(AccessControlService acs, Object value, Map<Str
.filter(s -> s.getType().equals("user"))
.anyMatch(s -> s.getValue().equals(username)))
.map(Role::getName)
.forEach(groupsByUsername::add);
}

String organization = principal.getAttribute(ORGANIZATION_ATTRIBUTE_NAME);
if (organization == null) {
log.debug("Github organization param is not present");
return Mono.just(groupsByUsername);
.forEach(rolesByUsername::add);
}

OAuth2UserRequest req = (OAuth2UserRequest) additionalParams.get("request");
Expand All @@ -80,8 +78,24 @@ public Mono<Set<String>> extract(AccessControlService acs, Object value, Map<Str
.getUserInfoEndpoint()
.getUri();
}
var webClient = WebClient.create(infoEndpoint);

WebClient webClient = WebClient.create(infoEndpoint);
Mono<Set<String>> rolesByOrganization = getOrganizationRoles(principal, additionalParams, acs, webClient);
Mono<Set<String>> rolesByTeams = getTeamRoles(webClient, additionalParams, acs);

return Mono.zip(rolesByOrganization, rolesByTeams)
.map((t) -> Stream.of(t.getT1(), t.getT2(), rolesByUsername)
.flatMap(Collection::stream)
.collect(Collectors.toSet()));
}

private Mono<Set<String>> getOrganizationRoles(DefaultOAuth2User principal, Map<String, Object> additionalParams,
AccessControlService acs, WebClient webClient) {
String organization = principal.getAttribute(ORGANIZATION_ATTRIBUTE_NAME);
if (organization == null) {
log.debug("Github organization param is not present");
return Mono.just(Collections.emptySet());
}

final Mono<List<Map<String, Object>>> userOrganizations = webClient
.get()
Expand All @@ -99,22 +113,76 @@ public Mono<Set<String>> extract(AccessControlService acs, Object value, Map<Str
//@formatter:on

return userOrganizations
.map(orgsMap -> {
var groupsByOrg = acs.getRoles()
.stream()
.filter(role -> role.getSubjects()
.stream()
.filter(s -> s.getProvider().equals(Provider.OAUTH_GITHUB))
.filter(s -> s.getType().equals("organization"))
.anyMatch(subject -> orgsMap.stream()
.map(org -> org.get(ORGANIZATION_NAME).toString())
.distinct()
.anyMatch(orgName -> orgName.equalsIgnoreCase(subject.getValue()))
))
.map(Role::getName);

return Stream.concat(groupsByOrg, groupsByUsername.stream()).collect(Collectors.toSet());
});
.map(orgsMap -> acs.getRoles()
.stream()
.filter(role -> role.getSubjects()
.stream()
.filter(s -> s.getProvider().equals(Provider.OAUTH_GITHUB))
.filter(s -> s.getType().equals(ORGANIZATION))
.anyMatch(subject -> orgsMap.stream()
.map(org -> org.get(ORGANIZATION_NAME).toString())
.anyMatch(orgName -> orgName.equalsIgnoreCase(subject.getValue()))
))
.map(Role::getName)
.collect(Collectors.toSet()));
}

@SuppressWarnings("unchecked")
private Mono<Set<String>> getTeamRoles(WebClient webClient, Map<String, Object> additionalParams,
AccessControlService acs) {

var requestedTeams = acs.getRoles()
.stream()
.filter(r -> r.getSubjects()
.stream()
.filter(s -> s.getProvider().equals(Provider.OAUTH_GITHUB))
.anyMatch(s -> s.getType().equals("team")))
.collect(Collectors.toSet());

if (requestedTeams.isEmpty()) {
log.debug("No roles with github teams found, skipping");
return Mono.just(Collections.emptySet());
}

final Mono<List<Map<String, Object>>> rawTeams = webClient
.get()
.uri(uriBuilder -> uriBuilder.path("/teams")
.queryParam("per_page", ORGANIZATIONS_PER_PAGE)
.build())
.headers(headers -> {
headers.set(HttpHeaders.ACCEPT, GITHUB_ACCEPT_HEADER);
OAuth2UserRequest request = (OAuth2UserRequest) additionalParams.get("request");
headers.setBearerAuth(request.getAccessToken().getTokenValue());
})
.retrieve()
//@formatter:off
.bodyToMono(new ParameterizedTypeReference<>() {});
//@formatter:on

final Mono<List<String>> mappedTeams = rawTeams
.map(teams -> teams.stream()
.map(teamInfo -> {
var name = teamInfo.get(TEAM_NAME);
var orgInfo = (Map<String, Object>) teamInfo.get(ORGANIZATION);
var orgName = orgInfo.get(ORGANIZATION_NAME);
return orgName + "/" + name;
})
.map(Object::toString)
.collect(Collectors.toList())
);

return mappedTeams
.map(teams -> acs.getRoles()
.stream()
.filter(role -> role.getSubjects()
.stream()
.filter(s -> s.getProvider().equals(Provider.OAUTH_GITHUB))
.filter(s -> s.getType().equals("team"))
.anyMatch(subject -> teams.stream()
.anyMatch(teamName -> teamName.equalsIgnoreCase(subject.getValue()))
))
.map(Role::getName)
.collect(Collectors.toSet()));
}

}
Loading