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

Issues/300: rbac now supports regex for values #648

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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 @@ -50,7 +50,7 @@ private Set<String> extractUsernameRoles(AccessControlService acs, DefaultOAuth2
.stream()
.filter(s -> s.getProvider().equals(Provider.OAUTH_COGNITO))
.filter(s -> s.getType().equals("user"))
.anyMatch(s -> s.getValue().equalsIgnoreCase(principal.getName())))
.anyMatch(s -> principal.getName() != null && principal.getName().matches(s.getValue())))
.map(Role::getName)
.collect(Collectors.toSet());

Expand All @@ -76,7 +76,7 @@ private Set<String> extractGroupRoles(AccessControlService acs, DefaultOAuth2Use
.filter(s -> s.getType().equals("group"))
.anyMatch(subject -> groups
.stream()
.anyMatch(cognitoGroup -> cognitoGroup.equalsIgnoreCase(subject.getValue()))
.anyMatch(cognitoGroup -> cognitoGroup.matches(subject.getValue()))
))
.map(Role::getName)
.collect(Collectors.toSet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private Set<String> extractUsernameRoles(DefaultOAuth2User principal, AccessCont
.stream()
.filter(s -> s.getProvider().equals(Provider.OAUTH_GITHUB))
.filter(s -> s.getType().equals("user"))
.anyMatch(s -> s.getValue().equals(username)))
.anyMatch(s -> username.matches(s.getValue())))
.map(Role::getName)
.collect(Collectors.toSet());

Expand Down Expand Up @@ -131,7 +131,7 @@ private Mono<Set<String>> getOrganizationRoles(DefaultOAuth2User principal, Map<
.filter(s -> s.getType().equals(ORGANIZATION))
.anyMatch(subject -> orgsMap.stream()
.map(org -> org.get(ORGANIZATION_NAME).toString())
.anyMatch(orgName -> orgName.equalsIgnoreCase(subject.getValue()))
.anyMatch(orgName -> orgName.matches(subject.getValue()))
))
.map(Role::getName)
.collect(Collectors.toSet()));
Expand Down Expand Up @@ -189,7 +189,7 @@ private Mono<Set<String>> getTeamRoles(WebClient webClient, Map<String, Object>
.filter(s -> s.getProvider().equals(Provider.OAUTH_GITHUB))
.filter(s -> s.getType().equals("team"))
.anyMatch(subject -> teams.stream()
.anyMatch(teamName -> teamName.equalsIgnoreCase(subject.getValue()))
.anyMatch(teamName -> teamName.matches(subject.getValue()))
))
.map(Role::getName)
.collect(Collectors.toSet()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ private Set<String> extractUsernameRoles(AccessControlService acs, DefaultOAuth2
.stream()
.filter(s -> s.getProvider().equals(Provider.OAUTH_GOOGLE))
.filter(s -> s.getType().equals("user"))
.anyMatch(s -> s.getValue().equalsIgnoreCase(principal.getAttribute(EMAIL_ATTRIBUTE_NAME))))
.anyMatch(s -> {
String email = principal.getAttribute(EMAIL_ATTRIBUTE_NAME);
return email != null && email.matches(s.getValue());
}))
.map(Role::getName)
.collect(Collectors.toSet());
}
Expand All @@ -68,7 +71,7 @@ private Set<String> extractDomainRoles(AccessControlService acs, DefaultOAuth2Us
.stream()
.filter(s -> s.getProvider().equals(Provider.OAUTH_GOOGLE))
.filter(s -> s.getType().equals("domain"))
.anyMatch(s -> s.getValue().equals(domain)))
.anyMatch(s -> domain.matches(s.getValue())))
.map(Role::getName)
.collect(Collectors.toSet());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ private Set<String> extractUsernameRoles(AccessControlService acs, DefaultOAuth2
.filter(s -> s.getProvider().equals(Provider.OAUTH))
.filter(s -> s.getType().equals("user"))
.peek(s -> log.trace("[{}] matches [{}]? [{}]", s.getValue(), principalName,
s.getValue().equalsIgnoreCase(principalName)))
.anyMatch(s -> s.getValue().equalsIgnoreCase(principalName)))
principalName != null && principalName.matches(s.getValue())))
.anyMatch(s -> principalName != null && principalName.matches(s.getValue())))
.map(Role::getName)
.collect(Collectors.toSet());

Expand Down Expand Up @@ -94,11 +94,7 @@ private Set<String> extractRoles(AccessControlService acs, DefaultOAuth2User pri
.stream()
.filter(s -> s.getProvider().equals(Provider.OAUTH))
.filter(s -> s.getType().equals("role"))
.anyMatch(subject -> {
var roleName = subject.getValue();
return principalRoles.contains(roleName);
})
)
.anyMatch(subject -> principalRoles.stream().anyMatch(s -> s.matches(subject.getValue()))))
.map(Role::getName)
.collect(Collectors.toSet());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package io.kafbat.ui.config;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import static org.springframework.security.oauth2.client.registration.ClientRegistration.withRegistrationId;

import io.kafbat.ui.config.auth.OAuthProperties;
import io.kafbat.ui.model.rbac.Role;
import io.kafbat.ui.service.rbac.AccessControlService;
import io.kafbat.ui.service.rbac.extractor.CognitoAuthorityExtractor;
import io.kafbat.ui.service.rbac.extractor.GithubAuthorityExtractor;
import io.kafbat.ui.service.rbac.extractor.GoogleAuthorityExtractor;
import io.kafbat.ui.service.rbac.extractor.OauthAuthorityExtractor;
import io.kafbat.ui.service.rbac.extractor.ProviderAuthorityExtractor;
import io.kafbat.ui.util.AccessControlServiceMock;
import java.io.InputStream;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.SneakyThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.introspector.BeanAccess;

public class RegexBasedProviderAuthorityExtractorTest {


private final AccessControlService accessControlService = new AccessControlServiceMock().getMock();
Yaml yaml;
ProviderAuthorityExtractor extractor;

@BeforeEach
void setUp() {
yaml = new Yaml();
yaml.setBeanAccess(BeanAccess.FIELD);

InputStream rolesFile = this.getClass()
.getClassLoader()
.getResourceAsStream("roles_definition.yaml");

Role[] roleArray = yaml.loadAs(rolesFile, Role[].class);
when(accessControlService.getRoles()).thenReturn(List.of(roleArray));

}

@SneakyThrows
@Test
void extractOauth2Authorities() {

extractor = new OauthAuthorityExtractor();

OAuth2User oauth2User = new DefaultOAuth2User(
AuthorityUtils.createAuthorityList("SCOPE_message:read"),
Map.of("role_definition", Set.of("ROLE-ADMIN", "ANOTHER-ROLE"), "user_name", "john@kafka.com"),
"user_name");

HashMap<String, Object> additionalParams = new HashMap<>();
OAuthProperties.OAuth2Provider provider = new OAuthProperties.OAuth2Provider();
provider.setCustomParams(Map.of("roles-field", "role_definition"));
additionalParams.put("provider", provider);

Set<String> roles = extractor.extract(accessControlService, oauth2User, additionalParams).block();

assertEquals(Set.of("viewer", "admin"), roles);

}

@SneakyThrows
@Test
void extractCognitoAuthorities() {

extractor = new CognitoAuthorityExtractor();

OAuth2User oauth2User = new DefaultOAuth2User(
AuthorityUtils.createAuthorityList("SCOPE_message:read"),
Map.of("cognito:groups", List.of("ROLE-ADMIN", "ANOTHER-ROLE"), "user_name", "john@kafka.com"),
"user_name");

HashMap<String, Object> additionalParams = new HashMap<>();

OAuthProperties.OAuth2Provider provider = new OAuthProperties.OAuth2Provider();
provider.setCustomParams(Map.of("roles-field", "role_definition"));
additionalParams.put("provider", provider);

Set<String> roles = extractor.extract(accessControlService, oauth2User, additionalParams).block();

assertEquals(Set.of("viewer", "admin"), roles);

}

@SneakyThrows
@Test
void extractGithubAuthorities() {

extractor = new GithubAuthorityExtractor();

OAuth2User oauth2User = new DefaultOAuth2User(
AuthorityUtils.createAuthorityList("SCOPE_message:read"),
Map.of("login", "john@kafka.com"),
"login");

HashMap<String, Object> additionalParams = new HashMap<>();

OAuthProperties.OAuth2Provider provider = new OAuthProperties.OAuth2Provider();
additionalParams.put("provider", provider);

additionalParams.put("request", new OAuth2UserRequest(
withRegistrationId("registration-1")
.clientId("client-1")
.clientSecret("secret")
.redirectUri("https://client.com")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationUri("https://provider.com/oauth2/authorization")
.tokenUri("https://provider.com/oauth2/token")
.clientName("Client 1")
.build(),
new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "XXXX", Instant.now(),
Instant.now().plus(10, ChronoUnit.HOURS))));

Set<String> roles = extractor.extract(accessControlService, oauth2User, additionalParams).block();

assertEquals(Set.of("viewer"), roles);

}

@SneakyThrows
@Test
void extractGoogleAuthorities() {

extractor = new GoogleAuthorityExtractor();

OAuth2User oauth2User = new DefaultOAuth2User(
AuthorityUtils.createAuthorityList("SCOPE_message:read"),
Map.of("hd", "test.domain.com", "email", "john@kafka.com"),
"email");

HashMap<String, Object> additionalParams = new HashMap<>();

OAuthProperties.OAuth2Provider provider = new OAuthProperties.OAuth2Provider();
provider.setCustomParams(Map.of("roles-field", "role_definition"));
additionalParams.put("provider", provider);

Set<String> roles = extractor.extract(accessControlService, oauth2User, additionalParams).block();

assertEquals(Set.of("viewer", "admin"), roles);

}

}
49 changes: 49 additions & 0 deletions api/src/test/resources/roles_definition.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
- name: 'admin'
subjects:
- provider: 'OAUTH'
value: 'ROLE-[A-Z]+'
type: 'role'
- provider: 'OAUTH_COGNITO'
value: 'ROLE-[A-Z]+'
type: 'group'
- provider: 'OAUTH_GOOGLE'
value: '.*.domain.com'
type: 'domain'
clusters:
- local
- remote
permissions:
- resource: APPLICATIONCONFIG
actions: [ all ]
- name: 'viewer'
subjects:
- provider: 'LDAP'
value: 'CS-XXX'
type: 'kafka-viewer'
- provider: 'OAUTH'
value: '.*@kafka.com'
type: 'user'
- provider: 'OAUTH_COGNITO'
value: '.*@kafka.com'
type: 'user'
- provider: 'OAUTH_GITHUB'
value: '.*@kafka.com'
type: 'user'
- provider: 'OAUTH_GOOGLE'
value: '.*@kafka.com'
type: 'user'
clusters:
- remote
permissions:
- resource: APPLICATIONCONFIG
actions: [ all ]
- name: 'editor'
subjects:
- provider: 'OAUTH'
value: 'ROLE_EDITOR'
type: 'role'
clusters:
- local
permissions:
- resource: APPLICATIONCONFIG
actions: [ all ]
Loading