Skip to content

Commit 3f82ec2

Browse files
committed
Add subject type 'scope' for role mapping (#1014)
1 parent b71a753 commit 3f82ec2

File tree

3 files changed

+58
-17
lines changed

3 files changed

+58
-17
lines changed

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

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
import java.util.Arrays;
1111
import java.util.Collection;
1212
import java.util.Collections;
13+
import java.util.HashSet;
1314
import java.util.List;
1415
import java.util.Map;
1516
import java.util.Set;
1617
import java.util.stream.Collectors;
1718
import lombok.extern.slf4j.Slf4j;
19+
import org.springframework.security.core.GrantedAuthority;
1820
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
1921
import org.springframework.util.Assert;
2022
import reactor.core.publisher.Mono;
@@ -72,32 +74,48 @@ private Set<String> extractRoles(AccessControlService acs, DefaultOAuth2User pri
7274
Map<String, Object> additionalParams) {
7375
var provider = (OAuthProperties.OAuth2Provider) additionalParams.get("provider");
7476
Assert.notNull(provider, "provider is null");
77+
7578
var rolesFieldName = provider.getCustomParams().get(ROLES_FIELD_PARAM_NAME);
7679

80+
Set<String> roles = new HashSet<>();
7781
if (rolesFieldName == null) {
78-
log.warn("Provider [{}] doesn't contain a roles field param name, won't map roles", provider.getClientName());
79-
return Collections.emptySet();
82+
log.warn("Provider [{}] doesn't contain a roles field param name, using default authorities/scopes for role mapping", provider.getClientName());
83+
} else {
84+
var principalRoles = convertRoles(principal.getAttribute(rolesFieldName));
85+
86+
if (principalRoles.isEmpty()) {
87+
log.debug("Principal [{}] doesn't have any roles through configured roles-field, using default authorities", principal.getName());
88+
} else {
89+
log.debug("Token's groups: [{}]. Mapping providers of type role.", String.join(",", principalRoles));
90+
roles.addAll(acs.getRoles().stream()
91+
.filter(role -> role.getSubjects()
92+
.stream()
93+
.filter(s -> s.getProvider().equals(Provider.OAUTH))
94+
.filter(s -> s.getType().equals("role"))
95+
.anyMatch(subject -> principalRoles.stream().anyMatch(subject::matches)))
96+
.map(Role::getName)
97+
.collect(Collectors.toSet()));
98+
}
8099
}
81100

82-
var principalRoles = convertRoles(principal.getAttribute(rolesFieldName));
83-
if (principalRoles.isEmpty()) {
84-
log.debug("Principal [{}] doesn't have any roles, nothing to do", principal.getName());
85-
return Collections.emptySet();
86-
}
101+
// Mapping groups from scopes
102+
Set<String> scopes = principal.getAuthorities().stream()
103+
.map(GrantedAuthority::getAuthority)
104+
.map(s -> s.replace("SCOPE_", ""))
105+
.collect(Collectors.toSet());
87106

88-
log.debug("Token's groups: [{}]", String.join(",", principalRoles));
107+
log.debug("Available scopes: [{}]. Mapping providers of type scope.", String.join(",", scopes));
89108

90-
Set<String> roles = acs.getRoles()
91-
.stream()
109+
roles.addAll(acs.getRoles().stream()
92110
.filter(role -> role.getSubjects()
93111
.stream()
94112
.filter(s -> s.getProvider().equals(Provider.OAUTH))
95-
.filter(s -> s.getType().equals("role"))
96-
.anyMatch(subject -> principalRoles.stream().anyMatch(subject::matches)))
113+
.filter(s -> s.getType().equals("scope"))
114+
.anyMatch(subject -> scopes.stream().anyMatch(subject::matches)))
97115
.map(Role::getName)
98-
.collect(Collectors.toSet());
116+
.collect(Collectors.toSet()));
99117

100-
log.debug("Matched group roles: [{}]", String.join(", ", roles));
118+
log.debug("Matched group/scope roles: [{}]", String.join(", ", roles));
101119

102120
return roles;
103121
}

api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
import static org.mockito.Mockito.when;
88
import static org.springframework.security.oauth2.client.registration.ClientRegistration.withRegistrationId;
99

10-
import com.fasterxml.jackson.databind.ObjectMapper;
11-
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
1210
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
1311
import io.kafbat.ui.config.auth.OAuthProperties;
1412
import io.kafbat.ui.model.rbac.Role;
@@ -23,6 +21,7 @@
2321
import java.io.InputStream;
2422
import java.time.Instant;
2523
import java.time.temporal.ChronoUnit;
24+
import java.util.Collections;
2625
import java.util.HashMap;
2726
import java.util.List;
2827
import java.util.Map;
@@ -79,7 +78,6 @@ void extractOauth2Authorities() {
7978
assertNotNull(roles);
8079
assertEquals(Set.of("viewer", "admin"), roles);
8180
assertFalse(roles.contains("no one's role"));
82-
8381
}
8482

8583
@SneakyThrows
@@ -106,6 +104,28 @@ void extractOauth2Authorities_blankEmail() {
106104

107105
}
108106

107+
@SneakyThrows
108+
@Test
109+
void extractOauth2Authorities_fromScopes() {
110+
111+
extractor = new OauthAuthorityExtractor();
112+
113+
OAuth2User oauth2User = new DefaultOAuth2User(
114+
AuthorityUtils.createAuthorityList("SCOPE_message:all"),
115+
Map.of("role_definition", Collections.emptySet(), "user_name", "invalidUser"),
116+
"user_name");
117+
118+
HashMap<String, Object> additionalParams = new HashMap<>();
119+
OAuthProperties.OAuth2Provider provider = new OAuthProperties.OAuth2Provider();
120+
provider.setCustomParams(Map.of("roles-field", "role_definition"));
121+
additionalParams.put("provider", provider);
122+
123+
Set<String> roles = extractor.extract(accessControlService, oauth2User, additionalParams).block();
124+
125+
assertNotNull(roles);
126+
assertEquals(Set.of("admin"), roles);
127+
}
128+
109129
@SneakyThrows
110130
@Test
111131
void extractCognitoAuthorities() {

api/src/test/resources/roles_definition.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
value: 'ROLE-[A-Z]+'
55
type: 'role'
66
isRegex: 'true'
7+
- provider: 'OAUTH'
8+
value: 'message:all'
9+
type: 'scope'
710
- provider: 'OAUTH_COGNITO'
811
value: 'ROLE-ADMIN'
912
type: 'group'

0 commit comments

Comments
 (0)