From bd2c6fdbb32df6a4c87e6340ba70ab95e4c0d192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Wed, 6 Nov 2024 13:54:36 +0100 Subject: [PATCH 01/10] Issues/300: rbac now supports regex for values --- .../extractor/OauthAuthorityExtractor.java | 4 +- .../ProviderAuthorityExtractorTest.java | 68 +++++++++++++++++++ api/src/test/resources/roles_definition.yaml | 34 ++++++++++ 3 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 api/src/test/java/io/kafbat/ui/config/ProviderAuthorityExtractorTest.java create mode 100644 api/src/test/resources/roles_definition.yaml diff --git a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java index 6d14ab870..1712b6410 100644 --- a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java +++ b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java @@ -60,7 +60,7 @@ private Set extractUsernameRoles(AccessControlService acs, DefaultOAuth2 .filter(s -> s.getType().equals("user")) .peek(s -> log.trace("[{}] matches [{}]? [{}]", s.getValue(), principalName, s.getValue().equalsIgnoreCase(principalName))) - .anyMatch(s -> s.getValue().equalsIgnoreCase(principalName))) + .anyMatch(s -> principalName.matches(s.getValue()))) .map(Role::getName) .collect(Collectors.toSet()); @@ -96,7 +96,7 @@ private Set extractRoles(AccessControlService acs, DefaultOAuth2User pri .filter(s -> s.getType().equals("role")) .anyMatch(subject -> { var roleName = subject.getValue(); - return principalRoles.contains(roleName); + return principalRoles.stream().anyMatch(s -> s.matches(subject.getValue())); }) ) .map(Role::getName) diff --git a/api/src/test/java/io/kafbat/ui/config/ProviderAuthorityExtractorTest.java b/api/src/test/java/io/kafbat/ui/config/ProviderAuthorityExtractorTest.java new file mode 100644 index 000000000..7ce5af2a7 --- /dev/null +++ b/api/src/test/java/io/kafbat/ui/config/ProviderAuthorityExtractorTest.java @@ -0,0 +1,68 @@ +package io.kafbat.ui.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +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.OauthAuthorityExtractor; +import io.kafbat.ui.service.rbac.extractor.ProviderAuthorityExtractor; +import io.kafbat.ui.util.AccessControlServiceMock; +import java.io.InputStream; +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.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.introspector.BeanAccess; + +public class ProviderAuthorityExtractorTest { + + + private final AccessControlService accessControlService = new AccessControlServiceMock().getMock(); + Yaml yaml; + ProviderAuthorityExtractor extractor; + + @BeforeEach + void setUp() { + yaml = new Yaml(); + yaml.setBeanAccess(BeanAccess.FIELD); + extractor = new OauthAuthorityExtractor(); + + 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 ExtractAuthoritiesFromRegex() { + + 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 additionalParams = new HashMap<>(); + OAuthProperties.OAuth2Provider oAuth2Provider = new OAuthProperties.OAuth2Provider(); + oAuth2Provider.setCustomParams(Map.of("roles-field", "role_definition")); + additionalParams.put("provider", oAuth2Provider); + + Set roles = extractor.extract(accessControlService, oauth2User, additionalParams).block(); + + assertEquals(Set.of("viewer", "admin"), roles); + + } + +} diff --git a/api/src/test/resources/roles_definition.yaml b/api/src/test/resources/roles_definition.yaml new file mode 100644 index 000000000..9428df22f --- /dev/null +++ b/api/src/test/resources/roles_definition.yaml @@ -0,0 +1,34 @@ +- name: 'admin' + subjects: + - provider: 'OAUTH' + value: 'ROLE-[A-Z]+' + type: 'role' + 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' + clusters: + - remote + permissions: + - resource: APPLICATIONCONFIG + actions: [ all ] +- name: 'editor' + subjects: + - provider: 'OAUTH' + value: 'ROLE_EDITOR' + type: 'role' + clusters: + - local + permissions: + - resource: APPLICATIONCONFIG + actions: [ all ] From 33b4a54c0c384daf10ba34cb47df0161ec53ffc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Wed, 6 Nov 2024 15:23:48 +0100 Subject: [PATCH 02/10] Issues/300: fix checkstyle --- .../kafbat/ui/config/ProviderAuthorityExtractorTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/test/java/io/kafbat/ui/config/ProviderAuthorityExtractorTest.java b/api/src/test/java/io/kafbat/ui/config/ProviderAuthorityExtractorTest.java index 7ce5af2a7..39a0e8b07 100644 --- a/api/src/test/java/io/kafbat/ui/config/ProviderAuthorityExtractorTest.java +++ b/api/src/test/java/io/kafbat/ui/config/ProviderAuthorityExtractorTest.java @@ -47,7 +47,7 @@ void setUp() { @SneakyThrows @Test - void ExtractAuthoritiesFromRegex() { + void extractAuthoritiesFromRegex() { OAuth2User oauth2User = new DefaultOAuth2User( AuthorityUtils.createAuthorityList("SCOPE_message:read"), @@ -55,9 +55,9 @@ void ExtractAuthoritiesFromRegex() { "user_name"); HashMap additionalParams = new HashMap<>(); - OAuthProperties.OAuth2Provider oAuth2Provider = new OAuthProperties.OAuth2Provider(); - oAuth2Provider.setCustomParams(Map.of("roles-field", "role_definition")); - additionalParams.put("provider", oAuth2Provider); + OAuthProperties.OAuth2Provider provider = new OAuthProperties.OAuth2Provider(); + provider.setCustomParams(Map.of("roles-field", "role_definition")); + additionalParams.put("provider", provider); Set roles = extractor.extract(accessControlService, oauth2User, additionalParams).block(); From f15fc925364efb98b1d82b01223a153bac3b39a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Tue, 12 Nov 2024 14:54:58 +0100 Subject: [PATCH 03/10] Issues/300: Other extractors handle regex as values. --- .../extractor/CognitoAuthorityExtractor.java | 4 +- .../extractor/GithubAuthorityExtractor.java | 6 +- .../extractor/GoogleAuthorityExtractor.java | 7 +- .../extractor/OauthAuthorityExtractor.java | 8 +- .../ProviderAuthorityExtractorTest.java | 68 -------- ...exBasedProviderAuthorityExtractorTest.java | 159 ++++++++++++++++++ api/src/test/resources/roles_definition.yaml | 15 ++ 7 files changed, 186 insertions(+), 81 deletions(-) delete mode 100644 api/src/test/java/io/kafbat/ui/config/ProviderAuthorityExtractorTest.java create mode 100644 api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java diff --git a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/CognitoAuthorityExtractor.java b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/CognitoAuthorityExtractor.java index a246b8910..e75666d83 100644 --- a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/CognitoAuthorityExtractor.java +++ b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/CognitoAuthorityExtractor.java @@ -50,7 +50,7 @@ private Set 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()); @@ -76,7 +76,7 @@ private Set 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()); diff --git a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/GithubAuthorityExtractor.java b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/GithubAuthorityExtractor.java index b50e76a16..f08e266d3 100644 --- a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/GithubAuthorityExtractor.java +++ b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/GithubAuthorityExtractor.java @@ -90,7 +90,7 @@ private Set 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()); @@ -131,7 +131,7 @@ private Mono> 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())); @@ -189,7 +189,7 @@ private Mono> getTeamRoles(WebClient webClient, Map .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())); diff --git a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/GoogleAuthorityExtractor.java b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/GoogleAuthorityExtractor.java index 8ea6d2108..c323e7ffd 100644 --- a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/GoogleAuthorityExtractor.java +++ b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/GoogleAuthorityExtractor.java @@ -50,7 +50,10 @@ private Set 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()); } @@ -68,7 +71,7 @@ private Set 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()); } diff --git a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java index 1712b6410..7bf19c61a 100644 --- a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java +++ b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java @@ -60,7 +60,7 @@ private Set extractUsernameRoles(AccessControlService acs, DefaultOAuth2 .filter(s -> s.getType().equals("user")) .peek(s -> log.trace("[{}] matches [{}]? [{}]", s.getValue(), principalName, s.getValue().equalsIgnoreCase(principalName))) - .anyMatch(s -> principalName.matches(s.getValue()))) + .anyMatch(s -> principalName != null && principalName.matches(s.getValue()))) .map(Role::getName) .collect(Collectors.toSet()); @@ -94,11 +94,7 @@ private Set 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.stream().anyMatch(s -> s.matches(subject.getValue())); - }) - ) + .anyMatch(subject -> principalRoles.stream().anyMatch(s -> s.matches(subject.getValue())))) .map(Role::getName) .collect(Collectors.toSet()); diff --git a/api/src/test/java/io/kafbat/ui/config/ProviderAuthorityExtractorTest.java b/api/src/test/java/io/kafbat/ui/config/ProviderAuthorityExtractorTest.java deleted file mode 100644 index 39a0e8b07..000000000 --- a/api/src/test/java/io/kafbat/ui/config/ProviderAuthorityExtractorTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package io.kafbat.ui.config; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.when; - -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.OauthAuthorityExtractor; -import io.kafbat.ui.service.rbac.extractor.ProviderAuthorityExtractor; -import io.kafbat.ui.util.AccessControlServiceMock; -import java.io.InputStream; -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.core.user.DefaultOAuth2User; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.introspector.BeanAccess; - -public class ProviderAuthorityExtractorTest { - - - private final AccessControlService accessControlService = new AccessControlServiceMock().getMock(); - Yaml yaml; - ProviderAuthorityExtractor extractor; - - @BeforeEach - void setUp() { - yaml = new Yaml(); - yaml.setBeanAccess(BeanAccess.FIELD); - extractor = new OauthAuthorityExtractor(); - - 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 extractAuthoritiesFromRegex() { - - 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 additionalParams = new HashMap<>(); - OAuthProperties.OAuth2Provider provider = new OAuthProperties.OAuth2Provider(); - provider.setCustomParams(Map.of("roles-field", "role_definition")); - additionalParams.put("provider", provider); - - Set roles = extractor.extract(accessControlService, oauth2User, additionalParams).block(); - - assertEquals(Set.of("viewer", "admin"), roles); - - } - -} diff --git a/api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java b/api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java new file mode 100644 index 000000000..7eb8c8bf1 --- /dev/null +++ b/api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java @@ -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 additionalParams = new HashMap<>(); + OAuthProperties.OAuth2Provider provider = new OAuthProperties.OAuth2Provider(); + provider.setCustomParams(Map.of("roles-field", "role_definition")); + additionalParams.put("provider", provider); + + Set 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 additionalParams = new HashMap<>(); + + OAuthProperties.OAuth2Provider provider = new OAuthProperties.OAuth2Provider(); + provider.setCustomParams(Map.of("roles-field", "role_definition")); + additionalParams.put("provider", provider); + + Set 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 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 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 additionalParams = new HashMap<>(); + + OAuthProperties.OAuth2Provider provider = new OAuthProperties.OAuth2Provider(); + provider.setCustomParams(Map.of("roles-field", "role_definition")); + additionalParams.put("provider", provider); + + Set roles = extractor.extract(accessControlService, oauth2User, additionalParams).block(); + + assertEquals(Set.of("viewer", "admin"), roles); + + } + +} diff --git a/api/src/test/resources/roles_definition.yaml b/api/src/test/resources/roles_definition.yaml index 9428df22f..25e22b8a1 100644 --- a/api/src/test/resources/roles_definition.yaml +++ b/api/src/test/resources/roles_definition.yaml @@ -3,6 +3,12 @@ - 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 @@ -17,6 +23,15 @@ - 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: From 1c47f7c097c88c31cd1fcfdb7141a0c49b90c361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Tue, 12 Nov 2024 15:52:54 +0100 Subject: [PATCH 04/10] Issues/300: Log message now reflects the new matching strategy --- .../ui/service/rbac/extractor/OauthAuthorityExtractor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java index 7bf19c61a..8812301a1 100644 --- a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java +++ b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java @@ -59,7 +59,7 @@ private Set 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))) + principalName != null && principalName.matches(s.getValue()))) .anyMatch(s -> principalName != null && principalName.matches(s.getValue()))) .map(Role::getName) .collect(Collectors.toSet()); From b689446c85afd9a1b9e669620e7a964bd20981c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Wed, 12 Feb 2025 13:18:57 +0100 Subject: [PATCH 05/10] Issues/300: remove useless null check & add test scenarios --- .../extractor/CognitoAuthorityExtractor.java | 2 +- ...exBasedProviderAuthorityExtractorTest.java | 35 +++++++++++++++++++ api/src/test/resources/roles_definition.yaml | 14 ++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/CognitoAuthorityExtractor.java b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/CognitoAuthorityExtractor.java index e75666d83..23354b86c 100644 --- a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/CognitoAuthorityExtractor.java +++ b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/CognitoAuthorityExtractor.java @@ -50,7 +50,7 @@ private Set extractUsernameRoles(AccessControlService acs, DefaultOAuth2 .stream() .filter(s -> s.getProvider().equals(Provider.OAUTH_COGNITO)) .filter(s -> s.getType().equals("user")) - .anyMatch(s -> principal.getName() != null && principal.getName().matches(s.getValue()))) + .anyMatch(s -> principal.getName().matches(s.getValue()))) .map(Role::getName) .collect(Collectors.toSet()); diff --git a/api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java b/api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java index 7eb8c8bf1..4623296e7 100644 --- a/api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java +++ b/api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java @@ -1,6 +1,9 @@ package io.kafbat.ui.config; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; import static org.springframework.security.oauth2.client.registration.ClientRegistration.withRegistrationId; @@ -71,7 +74,33 @@ void extractOauth2Authorities() { Set roles = extractor.extract(accessControlService, oauth2User, additionalParams).block(); + assertNotNull(roles); assertEquals(Set.of("viewer", "admin"), roles); + assertFalse(roles.contains("no one's role")); + + } + + @SneakyThrows + @Test() + void extractOauth2Authorities_blankEmail() { + + extractor = new OauthAuthorityExtractor(); + + OAuth2User oauth2User = new DefaultOAuth2User( + AuthorityUtils.createAuthorityList("SCOPE_message:read"), + Map.of("role_definition", Set.of("ROLE-ADMIN", "ANOTHER-ROLE"), "user_name", ""), + "user_name"); + + HashMap additionalParams = new HashMap<>(); + OAuthProperties.OAuth2Provider provider = new OAuthProperties.OAuth2Provider(); + provider.setCustomParams(Map.of("roles-field", "role_definition")); + additionalParams.put("provider", provider); + + Set roles = extractor.extract(accessControlService, oauth2User, additionalParams).block(); + + assertNotNull(roles); + assertFalse(roles.contains("viewer")); + assertTrue(roles.contains("admin")); } @@ -94,7 +123,9 @@ void extractCognitoAuthorities() { Set roles = extractor.extract(accessControlService, oauth2User, additionalParams).block(); + assertNotNull(roles); assertEquals(Set.of("viewer", "admin"), roles); + assertFalse(roles.contains("no one's role")); } @@ -129,7 +160,9 @@ void extractGithubAuthorities() { Set roles = extractor.extract(accessControlService, oauth2User, additionalParams).block(); + assertNotNull(roles); assertEquals(Set.of("viewer"), roles); + assertFalse(roles.contains("no one's role")); } @@ -152,7 +185,9 @@ void extractGoogleAuthorities() { Set roles = extractor.extract(accessControlService, oauth2User, additionalParams).block(); + assertNotNull(roles); assertEquals(Set.of("viewer", "admin"), roles); + assertFalse(roles.contains("no one's role")); } diff --git a/api/src/test/resources/roles_definition.yaml b/api/src/test/resources/roles_definition.yaml index 25e22b8a1..e1b370b5d 100644 --- a/api/src/test/resources/roles_definition.yaml +++ b/api/src/test/resources/roles_definition.yaml @@ -47,3 +47,17 @@ permissions: - resource: APPLICATIONCONFIG actions: [ all ] +- name: "no one's role" + subjects: + - provider: 'OAUTH' + value: '.*XXX' + type: 'role' + - provider: 'OAUTH_GITHUB' + value: '.*XXX' + type: 'user' + - provider: 'OAUTH_COGNITO' + value: '.*XXX' + type: 'user' + - provider: 'OAUTH_GOOGLE' + value: '.*XXX' + type: 'domain' From 708b463363cb5a711dff90fabb1d6c8ff31b8912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Wed, 19 Feb 2025 09:35:21 +0100 Subject: [PATCH 06/10] Issues/300: Remove useless null check --- .../ui/service/rbac/extractor/OauthAuthorityExtractor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java index 8812301a1..0d556bc1a 100644 --- a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java +++ b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java @@ -59,8 +59,8 @@ private Set 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, - principalName != null && principalName.matches(s.getValue()))) - .anyMatch(s -> principalName != null && principalName.matches(s.getValue()))) + principalName.matches(s.getValue()))) + .anyMatch(s -> principalName.matches(s.getValue()))) .map(Role::getName) .collect(Collectors.toSet()); From 57d1937fabb5073dc804d2d108862c289dff8682 Mon Sep 17 00:00:00 2001 From: Callaert Anthony Date: Thu, 27 Feb 2025 18:39:12 +0100 Subject: [PATCH 07/10] feat: introduce regex option --- .../java/io/kafbat/ui/model/rbac/Subject.java | 21 ++++++++++++------- .../extractor/CognitoAuthorityExtractor.java | 4 ++-- .../extractor/GithubAuthorityExtractor.java | 6 +++--- .../extractor/GoogleAuthorityExtractor.java | 4 ++-- .../extractor/OauthAuthorityExtractor.java | 6 ++---- ...acActiveDirectoryAuthoritiesExtractor.java | 4 ++-- .../RbacLdapAuthoritiesExtractor.java | 4 ++-- ...exBasedProviderAuthorityExtractorTest.java | 2 +- api/src/test/resources/roles_definition.yaml | 10 ++++++--- 9 files changed, 34 insertions(+), 27 deletions(-) diff --git a/api/src/main/java/io/kafbat/ui/model/rbac/Subject.java b/api/src/main/java/io/kafbat/ui/model/rbac/Subject.java index d653c1d0b..030e6ac7a 100644 --- a/api/src/main/java/io/kafbat/ui/model/rbac/Subject.java +++ b/api/src/main/java/io/kafbat/ui/model/rbac/Subject.java @@ -4,27 +4,25 @@ import static com.google.common.base.Preconditions.checkNotNull; import io.kafbat.ui.model.rbac.provider.Provider; +import java.util.Objects; import lombok.Getter; +import lombok.Setter; @Getter public class Subject { Provider provider; + @Setter String type; + @Setter String value; + @Setter + boolean isRegex; public void setProvider(String provider) { this.provider = Provider.fromString(provider.toUpperCase()); } - public void setType(String type) { - this.type = type; - } - - public void setValue(String value) { - this.value = value; - } - public void validate() { checkNotNull(type, "Subject type cannot be null"); checkNotNull(value, "Subject value cannot be null"); @@ -32,4 +30,11 @@ public void validate() { checkArgument(!type.isEmpty(), "Subject type cannot be empty"); checkArgument(!value.isEmpty(), "Subject value cannot be empty"); } + + public boolean matches(final String attribute) { + if (isRegex()) { + return Objects.nonNull(attribute) && attribute.matches(getValue()); + } + return getValue().equalsIgnoreCase(attribute); + } } diff --git a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/CognitoAuthorityExtractor.java b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/CognitoAuthorityExtractor.java index 23354b86c..cc0e419bf 100644 --- a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/CognitoAuthorityExtractor.java +++ b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/CognitoAuthorityExtractor.java @@ -50,7 +50,7 @@ private Set extractUsernameRoles(AccessControlService acs, DefaultOAuth2 .stream() .filter(s -> s.getProvider().equals(Provider.OAUTH_COGNITO)) .filter(s -> s.getType().equals("user")) - .anyMatch(s -> principal.getName().matches(s.getValue()))) + .anyMatch(s -> s.matches(principal.getName()))) .map(Role::getName) .collect(Collectors.toSet()); @@ -76,7 +76,7 @@ private Set extractGroupRoles(AccessControlService acs, DefaultOAuth2Use .filter(s -> s.getType().equals("group")) .anyMatch(subject -> groups .stream() - .anyMatch(cognitoGroup -> cognitoGroup.matches(subject.getValue())) + .anyMatch(subject::matches) )) .map(Role::getName) .collect(Collectors.toSet()); diff --git a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/GithubAuthorityExtractor.java b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/GithubAuthorityExtractor.java index f08e266d3..79f4907fc 100644 --- a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/GithubAuthorityExtractor.java +++ b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/GithubAuthorityExtractor.java @@ -90,7 +90,7 @@ private Set extractUsernameRoles(DefaultOAuth2User principal, AccessCont .stream() .filter(s -> s.getProvider().equals(Provider.OAUTH_GITHUB)) .filter(s -> s.getType().equals("user")) - .anyMatch(s -> username.matches(s.getValue()))) + .anyMatch(s -> s.matches(username))) .map(Role::getName) .collect(Collectors.toSet()); @@ -131,7 +131,7 @@ private Mono> getOrganizationRoles(DefaultOAuth2User principal, Map< .filter(s -> s.getType().equals(ORGANIZATION)) .anyMatch(subject -> orgsMap.stream() .map(org -> org.get(ORGANIZATION_NAME).toString()) - .anyMatch(orgName -> orgName.matches(subject.getValue())) + .anyMatch(subject::matches) )) .map(Role::getName) .collect(Collectors.toSet())); @@ -189,7 +189,7 @@ private Mono> getTeamRoles(WebClient webClient, Map .filter(s -> s.getProvider().equals(Provider.OAUTH_GITHUB)) .filter(s -> s.getType().equals("team")) .anyMatch(subject -> teams.stream() - .anyMatch(teamName -> teamName.matches(subject.getValue())) + .anyMatch(subject::matches) )) .map(Role::getName) .collect(Collectors.toSet())); diff --git a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/GoogleAuthorityExtractor.java b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/GoogleAuthorityExtractor.java index c323e7ffd..a90ab50ef 100644 --- a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/GoogleAuthorityExtractor.java +++ b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/GoogleAuthorityExtractor.java @@ -52,7 +52,7 @@ private Set extractUsernameRoles(AccessControlService acs, DefaultOAuth2 .filter(s -> s.getType().equals("user")) .anyMatch(s -> { String email = principal.getAttribute(EMAIL_ATTRIBUTE_NAME); - return email != null && email.matches(s.getValue()); + return s.matches(email); })) .map(Role::getName) .collect(Collectors.toSet()); @@ -71,7 +71,7 @@ private Set extractDomainRoles(AccessControlService acs, DefaultOAuth2Us .stream() .filter(s -> s.getProvider().equals(Provider.OAUTH_GOOGLE)) .filter(s -> s.getType().equals("domain")) - .anyMatch(s -> domain.matches(s.getValue()))) + .anyMatch(s -> s.matches(domain))) .map(Role::getName) .collect(Collectors.toSet()); } diff --git a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java index 0d556bc1a..20653953d 100644 --- a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java +++ b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java @@ -58,9 +58,7 @@ private Set extractUsernameRoles(AccessControlService acs, DefaultOAuth2 .stream() .filter(s -> s.getProvider().equals(Provider.OAUTH)) .filter(s -> s.getType().equals("user")) - .peek(s -> log.trace("[{}] matches [{}]? [{}]", s.getValue(), principalName, - principalName.matches(s.getValue()))) - .anyMatch(s -> principalName.matches(s.getValue()))) + .anyMatch(s -> s.matches(principalName))) .map(Role::getName) .collect(Collectors.toSet()); @@ -94,7 +92,7 @@ private Set extractRoles(AccessControlService acs, DefaultOAuth2User pri .stream() .filter(s -> s.getProvider().equals(Provider.OAUTH)) .filter(s -> s.getType().equals("role")) - .anyMatch(subject -> principalRoles.stream().anyMatch(s -> s.matches(subject.getValue())))) + .anyMatch(subject -> principalRoles.stream().anyMatch(subject::matches))) .map(Role::getName) .collect(Collectors.toSet()); diff --git a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/RbacActiveDirectoryAuthoritiesExtractor.java b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/RbacActiveDirectoryAuthoritiesExtractor.java index cefef5a7e..76feff063 100644 --- a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/RbacActiveDirectoryAuthoritiesExtractor.java +++ b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/RbacActiveDirectoryAuthoritiesExtractor.java @@ -37,8 +37,8 @@ public Collection getGrantedAuthorities(DirContextOp .stream() .filter(subject -> subject.getProvider().equals(Provider.LDAP_AD)) .anyMatch(subject -> switch (subject.getType()) { - case "user" -> username.equalsIgnoreCase(subject.getValue()); - case "group" -> adGroups.contains(subject.getValue()); + case "user" -> subject.matches(username); + case "group" -> adGroups.stream().anyMatch(subject::matches); default -> false; }) ) diff --git a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/RbacLdapAuthoritiesExtractor.java b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/RbacLdapAuthoritiesExtractor.java index 261b30cfe..78ec4ba19 100644 --- a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/RbacLdapAuthoritiesExtractor.java +++ b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/RbacLdapAuthoritiesExtractor.java @@ -39,8 +39,8 @@ protected Set getAdditionalRoles(DirContextOperations user, St .stream() .filter(subject -> subject.getProvider().equals(Provider.LDAP)) .anyMatch(subject -> switch (subject.getType()) { - case "user" -> username.equalsIgnoreCase(subject.getValue()); - case "group" -> ldapGroups.contains(subject.getValue()); + case "user" -> subject.matches(username); + case "group" -> ldapGroups.stream().anyMatch(subject::matches); default -> false; }) ) diff --git a/api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java b/api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java index 4623296e7..a2f704fae 100644 --- a/api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java +++ b/api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java @@ -174,7 +174,7 @@ void extractGoogleAuthorities() { OAuth2User oauth2User = new DefaultOAuth2User( AuthorityUtils.createAuthorityList("SCOPE_message:read"), - Map.of("hd", "test.domain.com", "email", "john@kafka.com"), + Map.of("hd", "memelord.lol", "email", "john@kafka.com"), "email"); HashMap additionalParams = new HashMap<>(); diff --git a/api/src/test/resources/roles_definition.yaml b/api/src/test/resources/roles_definition.yaml index e1b370b5d..f9af4f507 100644 --- a/api/src/test/resources/roles_definition.yaml +++ b/api/src/test/resources/roles_definition.yaml @@ -3,12 +3,13 @@ - provider: 'OAUTH' value: 'ROLE-[A-Z]+' type: 'role' + isRegex: 'true' - provider: 'OAUTH_COGNITO' - value: 'ROLE-[A-Z]+' + value: 'ROLE-ADMIN' type: 'group' - provider: 'OAUTH_GOOGLE' - value: '.*.domain.com' type: 'domain' + value: 'memelord.lol' clusters: - local - remote @@ -23,14 +24,17 @@ - provider: 'OAUTH' value: '.*@kafka.com' type: 'user' + isRegex: 'true' - provider: 'OAUTH_COGNITO' value: '.*@kafka.com' type: 'user' + isRegex: 'true' - provider: 'OAUTH_GITHUB' value: '.*@kafka.com' type: 'user' + isRegex: 'true' - provider: 'OAUTH_GOOGLE' - value: '.*@kafka.com' + value: 'john@kafka.com' type: 'user' clusters: - remote From 997a7c411d77c52980854adfbb0471d47c6381cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Fri, 28 Feb 2025 16:01:49 +0100 Subject: [PATCH 08/10] Issues/300: Replace Lombok annotation & add log --- .../java/io/kafbat/ui/model/rbac/Subject.java | 20 ++++++------------- .../extractor/OauthAuthorityExtractor.java | 1 + 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/api/src/main/java/io/kafbat/ui/model/rbac/Subject.java b/api/src/main/java/io/kafbat/ui/model/rbac/Subject.java index 030e6ac7a..509500648 100644 --- a/api/src/main/java/io/kafbat/ui/model/rbac/Subject.java +++ b/api/src/main/java/io/kafbat/ui/model/rbac/Subject.java @@ -5,23 +5,15 @@ import io.kafbat.ui.model.rbac.provider.Provider; import java.util.Objects; -import lombok.Getter; -import lombok.Setter; +import lombok.Data; -@Getter +@Data public class Subject { Provider provider; - @Setter String type; - @Setter String value; - @Setter - boolean isRegex; - - public void setProvider(String provider) { - this.provider = Provider.fromString(provider.toUpperCase()); - } + boolean isRegex = false; public void validate() { checkNotNull(type, "Subject type cannot be null"); @@ -32,9 +24,9 @@ public void validate() { } public boolean matches(final String attribute) { - if (isRegex()) { - return Objects.nonNull(attribute) && attribute.matches(getValue()); + if (isRegex) { + return Objects.nonNull(attribute) && attribute.matches(this.value); } - return getValue().equalsIgnoreCase(attribute); + return this.value.equalsIgnoreCase(attribute); } } diff --git a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java index 20653953d..61748610e 100644 --- a/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java +++ b/api/src/main/java/io/kafbat/ui/service/rbac/extractor/OauthAuthorityExtractor.java @@ -58,6 +58,7 @@ private Set extractUsernameRoles(AccessControlService acs, DefaultOAuth2 .stream() .filter(s -> s.getProvider().equals(Provider.OAUTH)) .filter(s -> s.getType().equals("user")) + .peek(s -> log.trace("[{}] matches [{}]? [{}]", s.getValue(), principalName, s.matches(principalName))) .anyMatch(s -> s.matches(principalName))) .map(Role::getName) .collect(Collectors.toSet()); From 9fe46048ceaa02aa5c3fd352499a68544db9f32f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Mon, 3 Mar 2025 15:40:00 +0100 Subject: [PATCH 09/10] Issues/300: Use Jackson to deserialize Roles[] --- .../java/io/kafbat/ui/model/rbac/Subject.java | 11 ++++++++--- ...gexBasedProviderAuthorityExtractorTest.java | 18 ++++++++++-------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/api/src/main/java/io/kafbat/ui/model/rbac/Subject.java b/api/src/main/java/io/kafbat/ui/model/rbac/Subject.java index 509500648..62c32f02c 100644 --- a/api/src/main/java/io/kafbat/ui/model/rbac/Subject.java +++ b/api/src/main/java/io/kafbat/ui/model/rbac/Subject.java @@ -3,17 +3,22 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import com.fasterxml.jackson.annotation.JsonProperty; import io.kafbat.ui.model.rbac.provider.Provider; import java.util.Objects; -import lombok.Data; +import lombok.Builder; +import lombok.Value; +import lombok.extern.jackson.Jacksonized; -@Data +@Value +@Jacksonized +@Builder public class Subject { Provider provider; String type; String value; - boolean isRegex = false; + boolean isRegex; public void validate() { checkNotNull(type, "Subject type cannot be null"); diff --git a/api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java b/api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java index a2f704fae..11eec0ea4 100644 --- a/api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java +++ b/api/src/test/java/io/kafbat/ui/config/RegexBasedProviderAuthorityExtractorTest.java @@ -7,6 +7,9 @@ import static org.mockito.Mockito.when; import static org.springframework.security.oauth2.client.registration.ClientRegistration.withRegistrationId; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; import io.kafbat.ui.config.auth.OAuthProperties; import io.kafbat.ui.model.rbac.Role; import io.kafbat.ui.service.rbac.AccessControlService; @@ -16,6 +19,7 @@ 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.IOException; import java.io.InputStream; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -32,27 +36,25 @@ 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); + void setUp() throws IOException { + + YAMLMapper mapper = new YAMLMapper(); InputStream rolesFile = this.getClass() .getClassLoader() .getResourceAsStream("roles_definition.yaml"); - Role[] roleArray = yaml.loadAs(rolesFile, Role[].class); - when(accessControlService.getRoles()).thenReturn(List.of(roleArray)); + Role[] roles = mapper.readValue(rolesFile, Role[].class); + + when(accessControlService.getRoles()).thenReturn(List.of(roles)); } From 2a9199bba148d8bd4efecaf80852e58dbf0f1efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Thu, 6 Mar 2025 11:50:33 +0100 Subject: [PATCH 10/10] Issues/300: @Data provides required constructor & setters --- api/src/main/java/io/kafbat/ui/model/rbac/Subject.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/io/kafbat/ui/model/rbac/Subject.java b/api/src/main/java/io/kafbat/ui/model/rbac/Subject.java index 62c32f02c..ae24c5c48 100644 --- a/api/src/main/java/io/kafbat/ui/model/rbac/Subject.java +++ b/api/src/main/java/io/kafbat/ui/model/rbac/Subject.java @@ -6,18 +6,15 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.kafbat.ui.model.rbac.provider.Provider; import java.util.Objects; -import lombok.Builder; -import lombok.Value; -import lombok.extern.jackson.Jacksonized; +import lombok.Data; -@Value -@Jacksonized -@Builder +@Data public class Subject { Provider provider; String type; String value; + @JsonProperty("isRegex") boolean isRegex; public void validate() {