diff --git a/api/src/test/java/io/kafbat/ui/service/rbac/AccessControlServiceInitPropertiesTest.java b/api/src/test/java/io/kafbat/ui/service/rbac/AccessControlServiceInitPropertiesTest.java new file mode 100644 index 000000000..81f261e23 --- /dev/null +++ b/api/src/test/java/io/kafbat/ui/service/rbac/AccessControlServiceInitPropertiesTest.java @@ -0,0 +1,59 @@ +package io.kafbat.ui.service.rbac; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.kafbat.ui.AbstractIntegrationTest; +import io.kafbat.ui.service.rbac.extractor.GoogleAuthorityExtractor; +import io.kafbat.ui.service.rbac.extractor.ProviderAuthorityExtractor; +import java.util.Set; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; + +/** + * Test case for Properties initializer. + */ +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +@ContextConfiguration(initializers = AccessControlServiceInitPropertiesTest.PropertiesInitializer.class) +class AccessControlServiceInitPropertiesTest extends AbstractIntegrationTest { + + public static class PropertiesInitializer extends AbstractIntegrationTest.Initializer + implements ApplicationContextInitializer { + + @Override + public void initialize(@NotNull ConfigurableApplicationContext applicationContext) { + System.setProperty("rbac.roles[0].name", "memelords"); + System.setProperty("rbac.roles[0].clusters[0]", "local"); + + System.setProperty("rbac.roles[0].subjects[0].provider", "oauth_google"); + System.setProperty("rbac.roles[0].subjects[0].type", "domain"); + System.setProperty("rbac.roles[0].subjects[0].value", "katbat.dev"); + + System.setProperty("rbac.roles[0].subjects[1].provider", "oauth_google"); + System.setProperty("rbac.roles[0].subjects[1].type", "user"); + System.setProperty("rbac.roles[0].subjects[1].value", "name@kafbat.dev"); + + System.setProperty("rbac.roles[0].permissions[0].resource", "applicationconfig"); + System.setProperty("rbac.roles[0].permissions[0].actions", "all"); + } + } + + @Autowired + AccessControlService accessControlService; + + @Test + void rbacEnabled() { + assertTrue(accessControlService.isRbacEnabled()); + + Set oauthExtractors = accessControlService.getOauthExtractors(); + assertThat(oauthExtractors) + .hasSize(1) + .anyMatch(ext -> ext instanceof GoogleAuthorityExtractor); + } + +} diff --git a/api/src/test/java/io/kafbat/ui/service/rbac/AccessControlServiceRbacDisabledTest.java b/api/src/test/java/io/kafbat/ui/service/rbac/AccessControlServiceRbacDisabledTest.java new file mode 100644 index 000000000..199771a78 --- /dev/null +++ b/api/src/test/java/io/kafbat/ui/service/rbac/AccessControlServiceRbacDisabledTest.java @@ -0,0 +1,173 @@ +package io.kafbat.ui.service.rbac; + +import static io.kafbat.ui.service.rbac.MockedRbacUtils.CONNECT_NAME; +import static io.kafbat.ui.service.rbac.MockedRbacUtils.CONSUMER_GROUP_NAME; +import static io.kafbat.ui.service.rbac.MockedRbacUtils.DEV_ROLE; +import static io.kafbat.ui.service.rbac.MockedRbacUtils.PROD_CLUSTER; +import static io.kafbat.ui.service.rbac.MockedRbacUtils.SCHEMA_NAME; +import static io.kafbat.ui.service.rbac.MockedRbacUtils.TOPIC_NAME; +import static io.kafbat.ui.service.rbac.MockedRbacUtils.getAccessContext; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import io.kafbat.ui.AbstractIntegrationTest; +import io.kafbat.ui.config.auth.RbacUser; +import io.kafbat.ui.model.ClusterDTO; +import io.kafbat.ui.model.ConnectDTO; +import io.kafbat.ui.model.InternalTopic; +import io.kafbat.ui.model.rbac.AccessContext; +import io.kafbat.ui.model.rbac.Role; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.test.annotation.DirtiesContext; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +/** + * Test cases for AccessControlService when RBAC is disabled. + * Using PROD cluster and user DEV role for all tests. + */ +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +class AccessControlServiceRbacDisabledTest extends AbstractIntegrationTest { + + @Autowired + AccessControlService accessControlService; + + @Mock + SecurityContext securityContext; + + @Mock + Authentication authentication; + + @Mock + RbacUser user; + + @BeforeEach + void setUp() { + // Mock security context + when(securityContext.getAuthentication()).thenReturn(authentication); + when(authentication.getPrincipal()).thenReturn(user); + } + + public void withSecurityContext(Runnable runnable) { + try (MockedStatic ctxHolder = Mockito.mockStatic( + ReactiveSecurityContextHolder.class)) { + // Mock static method to get security context + ctxHolder.when(ReactiveSecurityContextHolder::getContext).thenReturn(Mono.just(securityContext)); + runnable.run(); + } + } + + @Test + void validateAccess() { + withSecurityContext(() -> { + when(user.groups()).thenReturn(List.of(DEV_ROLE)); + AccessContext context = getAccessContext(PROD_CLUSTER, true); + Mono validateAccessMono = accessControlService.validateAccess(context); + StepVerifier.create(validateAccessMono) + .expectComplete() + .verify(); + }); + } + + @Test + void isClusterAccessible() { + withSecurityContext(() -> { + when(user.groups()).thenReturn(List.of(DEV_ROLE)); + ClusterDTO clusterDto = new ClusterDTO(); + clusterDto.setName(PROD_CLUSTER); + Mono clusterAccessibleMono = accessControlService.isClusterAccessible(clusterDto); + StepVerifier.create(clusterAccessibleMono) + .expectNext(true) + .expectComplete() + .verify(); + }); + } + + @Test + void filterViewableTopics() { + withSecurityContext(() -> { + when(user.groups()).thenReturn(List.of(DEV_ROLE)); + List topics = List.of( + InternalTopic.builder() + .name(TOPIC_NAME) + .build() + ); + Mono> filterTopicsMono = accessControlService.filterViewableTopics(topics, PROD_CLUSTER); + StepVerifier.create(filterTopicsMono) + .expectNextMatches(responseTopics -> responseTopics.stream().anyMatch(t -> t.getName().equals(TOPIC_NAME))) + .expectComplete() + .verify(); + }); + } + + @Test + void isConsumerGroupAccessible() { + withSecurityContext(() -> { + when(user.groups()).thenReturn(List.of(DEV_ROLE)); + Mono consumerGroupAccessibleMono = + accessControlService.isConsumerGroupAccessible(CONSUMER_GROUP_NAME, PROD_CLUSTER); + StepVerifier.create(consumerGroupAccessibleMono) + .expectNext(true) + .expectComplete() + .verify(); + }); + } + + @Test + void isSchemaAccessible() { + withSecurityContext(() -> { + when(user.groups()).thenReturn(List.of(DEV_ROLE)); + Mono consumerGroupAccessibleMono = + accessControlService.isSchemaAccessible(SCHEMA_NAME, PROD_CLUSTER); + StepVerifier.create(consumerGroupAccessibleMono) + .expectNext(true) + .expectComplete() + .verify(); + }); + } + + @Test + void isConnectAccessible() { + withSecurityContext(() -> { + when(user.groups()).thenReturn(List.of(DEV_ROLE)); + Mono consumerGroupAccessibleMono = + accessControlService.isConnectAccessible(CONNECT_NAME, PROD_CLUSTER); + StepVerifier.create(consumerGroupAccessibleMono) + .expectNext(true) + .expectComplete() + .verify(); + }); + } + + @Test + void isConnectAccessibleDto() { + withSecurityContext(() -> { + when(user.groups()).thenReturn(List.of(DEV_ROLE)); + ConnectDTO connectDto = ConnectDTO.builder() + .name(CONNECT_NAME) + .build(); + Mono consumerGroupAccessibleMono = + accessControlService.isConnectAccessible(connectDto, PROD_CLUSTER); + StepVerifier.create(consumerGroupAccessibleMono) + .expectNext(true) + .expectComplete() + .verify(); + }); + } + + @Test + void getRoles() { + List roles = accessControlService.getRoles(); + assertThat(roles).isEmpty(); + } + +} diff --git a/api/src/test/java/io/kafbat/ui/service/rbac/AccessControlServiceRbacEnabledTest.java b/api/src/test/java/io/kafbat/ui/service/rbac/AccessControlServiceRbacEnabledTest.java index 57264190e..b00d4e7d4 100644 --- a/api/src/test/java/io/kafbat/ui/service/rbac/AccessControlServiceRbacEnabledTest.java +++ b/api/src/test/java/io/kafbat/ui/service/rbac/AccessControlServiceRbacEnabledTest.java @@ -9,6 +9,7 @@ import static io.kafbat.ui.service.rbac.MockedRbacUtils.SCHEMA_NAME; import static io.kafbat.ui.service.rbac.MockedRbacUtils.TOPIC_NAME; import static io.kafbat.ui.service.rbac.MockedRbacUtils.getAccessContext; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -31,6 +32,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.security.core.context.SecurityContext; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.util.ReflectionTestUtils; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -38,6 +40,7 @@ /** * Test cases for AccessControlService when RBAC is enabled. */ +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) class AccessControlServiceRbacEnabledTest extends AbstractIntegrationTest { @Autowired @@ -147,8 +150,6 @@ void isClusterAccessible_deniedCluster() { void filterViewableTopics() { withSecurityContext(() -> { when(user.groups()).thenReturn(List.of(DEV_ROLE)); - ClusterDTO clusterDto = new ClusterDTO(); - clusterDto.setName(DEV_CLUSTER); List topics = List.of( InternalTopic.builder() .name(TOPIC_NAME) @@ -166,8 +167,6 @@ void filterViewableTopics() { void filterViewableTopics_notAccessibleTopic() { withSecurityContext(() -> { when(user.groups()).thenReturn(List.of(DEV_ROLE)); - ClusterDTO clusterDto = new ClusterDTO(); - clusterDto.setName(DEV_CLUSTER); List topics = List.of( InternalTopic.builder() .name("some other topic") @@ -291,4 +290,12 @@ void isConnectAccessibleDto_notAccessible() { }); } + @Test + void getRoles() { + List roles = accessControlService.getRoles(); + assertThat(roles).hasSize(2) + .anyMatch(role -> role.getName().equals(DEV_ROLE)) + .anyMatch(role -> role.getName().equals(ADMIN_ROLE)); + } + } diff --git a/api/src/test/java/io/kafbat/ui/service/rbac/MockedRbacUtils.java b/api/src/test/java/io/kafbat/ui/service/rbac/MockedRbacUtils.java index f9e8bb3c9..f4c6dc5e7 100644 --- a/api/src/test/java/io/kafbat/ui/service/rbac/MockedRbacUtils.java +++ b/api/src/test/java/io/kafbat/ui/service/rbac/MockedRbacUtils.java @@ -48,7 +48,7 @@ public static Role getAdminRole() { } /** - * View actions to topic, consumer, schema and connect + * View actions to topic, consumer, schema and connect. * * @return admin role */