Skip to content

Commit 4d3d510

Browse files
committed
BE: RBAC: Subject type/value is unintended to be optional
add tests
1 parent 8e2b511 commit 4d3d510

File tree

4 files changed

+259
-1
lines changed

4 files changed

+259
-1
lines changed

api/src/test/java/io/kafbat/ui/AbstractIntegrationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public abstract class AbstractIntegrationTest {
4040
private static final boolean IS_ARM =
4141
System.getProperty("os.arch").contains("arm") || System.getProperty("os.arch").contains("aarch64");
4242

43-
private static final String CONFLUENT_PLATFORM_VERSION = IS_ARM ? "7.2.1.arm64" : "7.2.1";
43+
public static final String CONFLUENT_PLATFORM_VERSION = IS_ARM ? "7.2.1.arm64" : "7.2.1";
4444

4545
public static final KafkaContainer kafka = new KafkaContainer(
4646
DockerImageName.parse("confluentinc/cp-kafka").withTag(CONFLUENT_PLATFORM_VERSION))
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package io.kafbat.ui;
2+
3+
import static io.kafbat.ui.AbstractIntegrationTest.CONFLUENT_PLATFORM_VERSION;
4+
import static io.kafbat.ui.AbstractIntegrationTest.LOCAL;
5+
import static io.kafbat.ui.container.ActiveDirectoryContainer.DOMAIN;
6+
import static io.kafbat.ui.container.ActiveDirectoryContainer.PASSWORD;
7+
import static io.kafbat.ui.container.ActiveDirectoryContainer.SECOND_GROUP_USER;
8+
import static io.kafbat.ui.container.ActiveDirectoryContainer.USER_WITHOUT_GROUP;
9+
import static io.kafbat.ui.container.ActiveDirectoryContainer.FIRST_GROUP_USER;
10+
import static org.junit.jupiter.api.Assertions.assertEquals;
11+
import static org.junit.jupiter.api.Assertions.assertFalse;
12+
import static org.junit.jupiter.api.Assertions.assertNotNull;
13+
import static org.junit.jupiter.api.Assertions.assertTrue;
14+
15+
import io.kafbat.ui.container.ActiveDirectoryContainer;
16+
import java.util.List;
17+
import java.util.Objects;
18+
import io.kafbat.ui.model.AuthenticationInfoDTO;
19+
import io.kafbat.ui.model.ResourceTypeDTO;
20+
import io.kafbat.ui.model.TopicCreationDTO;
21+
import io.kafbat.ui.model.TopicDTO;
22+
import io.kafbat.ui.model.UserPermissionDTO;
23+
import org.jetbrains.annotations.NotNull;
24+
import org.junit.jupiter.api.AfterAll;
25+
import org.junit.jupiter.api.BeforeAll;
26+
import org.junit.jupiter.api.Test;
27+
import org.springframework.beans.factory.annotation.Autowired;
28+
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
29+
import org.springframework.boot.test.context.SpringBootTest;
30+
import org.springframework.context.ApplicationContextInitializer;
31+
import org.springframework.context.ConfigurableApplicationContext;
32+
import org.springframework.http.MediaType;
33+
import org.springframework.test.context.ActiveProfiles;
34+
import org.springframework.test.context.ContextConfiguration;
35+
import org.springframework.test.web.reactive.server.WebTestClient;
36+
import org.springframework.web.reactive.function.BodyInserters;
37+
import org.testcontainers.containers.KafkaContainer;
38+
import org.testcontainers.containers.Network;
39+
import org.testcontainers.utility.DockerImageName;
40+
41+
@SpringBootTest
42+
@ActiveProfiles("rbac-ad")
43+
@AutoConfigureWebTestClient(timeout = "60000")
44+
@ContextConfiguration(initializers = {ActiveDirectoryIntegrationTest.Initializer.class})
45+
public class ActiveDirectoryIntegrationTest {
46+
private static final String SESSION = "SESSION";
47+
48+
private static final KafkaContainer KAFKA = new KafkaContainer(
49+
DockerImageName.parse("confluentinc/cp-kafka").withTag(CONFLUENT_PLATFORM_VERSION))
50+
.withNetwork(Network.SHARED);
51+
52+
private static final ActiveDirectoryContainer ACTIVE_DIRECTORY = new ActiveDirectoryContainer();
53+
54+
@Autowired
55+
private WebTestClient webTestClient;
56+
57+
@BeforeAll
58+
public static void setup() {
59+
KAFKA.start();
60+
ACTIVE_DIRECTORY.start();
61+
}
62+
63+
@AfterAll
64+
public static void shutdown() {
65+
ACTIVE_DIRECTORY.stop();
66+
KAFKA.stop();
67+
}
68+
69+
@Test
70+
public void testUserPermissions() {
71+
AuthenticationInfoDTO info = authenticationInfo(FIRST_GROUP_USER);
72+
73+
assertNotNull(info);
74+
assertTrue(info.getRbacEnabled());
75+
76+
List<UserPermissionDTO> permissions = info.getUserInfo().getPermissions();
77+
78+
assertFalse(permissions.isEmpty());
79+
assertTrue(permissions.stream().anyMatch(permission ->
80+
permission.getClusters().contains(LOCAL) &&
81+
permission.getResource() == ResourceTypeDTO.TOPIC)
82+
);
83+
assertEquals(permissions, authenticationInfo(SECOND_GROUP_USER).getUserInfo().getPermissions());
84+
}
85+
86+
@Test
87+
public void testCreateTopic() {
88+
TopicCreationDTO topic = new TopicCreationDTO()
89+
.name("new")
90+
.partitions(10);
91+
92+
TopicDTO result = webTestClient
93+
.post()
94+
.uri("/api/clusters/{clusterName}/topics", LOCAL)
95+
.cookie(SESSION, session(FIRST_GROUP_USER))
96+
.bodyValue(topic)
97+
.exchange()
98+
.expectStatus()
99+
.isOk()
100+
.returnResult(TopicDTO.class)
101+
.getResponseBody()
102+
.blockFirst();
103+
104+
assertNotNull(result);
105+
assertEquals(topic.getName(), result.getName());
106+
assertEquals(topic.getPartitions(), result.getPartitionCount());
107+
}
108+
109+
@Test
110+
public void testEmptyPermissions() {
111+
assertTrue(Objects.requireNonNull(authenticationInfo(USER_WITHOUT_GROUP))
112+
.getUserInfo()
113+
.getPermissions()
114+
.isEmpty()
115+
);
116+
}
117+
118+
private String session(String name) {
119+
return Objects.requireNonNull(
120+
webTestClient
121+
.post()
122+
.uri("/login")
123+
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
124+
.body(BodyInserters.fromFormData("username", name).with("password", PASSWORD))
125+
.exchange()
126+
.expectStatus()
127+
.isFound()
128+
.returnResult(String.class)
129+
.getResponseCookies()
130+
.getFirst("SESSION"))
131+
.getValue();
132+
}
133+
134+
private AuthenticationInfoDTO authenticationInfo(String name) {
135+
return webTestClient
136+
.get()
137+
.uri("/api/authorization")
138+
.cookie(SESSION, session(name))
139+
.exchange()
140+
.expectStatus()
141+
.isOk()
142+
.returnResult(AuthenticationInfoDTO.class)
143+
.getResponseBody()
144+
.blockFirst();
145+
}
146+
147+
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
148+
@Override
149+
public void initialize(@NotNull ConfigurableApplicationContext context) {
150+
System.setProperty("kafka.clusters.0.name", LOCAL);
151+
System.setProperty("kafka.clusters.0.bootstrapServers", KAFKA.getBootstrapServers());
152+
System.setProperty("spring.ldap.urls", ACTIVE_DIRECTORY.getLdapUrl());
153+
System.setProperty("oauth2.ldap.activeDirectory", "true");
154+
System.setProperty("oauth2.ldap.activeDirectory.domain", DOMAIN);
155+
}
156+
}
157+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package io.kafbat.ui.container;
2+
3+
import com.github.dockerjava.api.command.InspectContainerResponse;
4+
import java.io.IOException;
5+
import lombok.extern.slf4j.Slf4j;
6+
import org.testcontainers.containers.GenericContainer;
7+
import org.testcontainers.utility.DockerImageName;
8+
9+
@Slf4j
10+
public class ActiveDirectoryContainer extends GenericContainer<ActiveDirectoryContainer> {
11+
public static final DockerImageName IMAGE_NAME = DockerImageName.parse("nowsci/samba-domain:latest");
12+
public static final String DOMAIN = "corp.kafbat.io";
13+
public static final String PASSWORD = "StrongPassword123";
14+
public static final String FIRST_GROUP_USER = "JohnDoe";
15+
public static final String SECOND_GROUP_USER = "JohnWick";
16+
public static final String USER_WITHOUT_GROUP = "JohnJames";
17+
18+
private static final String DOMAIN_DC = "dc=corp,dc=kafbat,dc=io";
19+
private static final String GROUP = "group";
20+
private static final String FIRST_GROUP = "firstGroup";
21+
private static final String SECOND_GROUP = "secondGroup";
22+
private static final String DOMAIN_EMAIL = "kafbat.io";
23+
private static final String SAMBA_TOOL = "samba-tool";
24+
private static final int LDAP_PORT = 389;
25+
26+
public ActiveDirectoryContainer() {
27+
super(IMAGE_NAME);
28+
29+
withExposedPorts(LDAP_PORT);
30+
31+
withEnv("DOMAIN", DOMAIN);
32+
withEnv("DOMAIN_DC", DOMAIN_DC);
33+
withEnv("DOMAIN_EMAIL", DOMAIN_EMAIL);
34+
withEnv("DOMAINPASS", PASSWORD);
35+
withEnv("NOCOMPLEXITY", "true");
36+
withEnv("INSECURELDAP", "true");
37+
38+
withPrivilegedMode(true);
39+
}
40+
41+
protected void containerIsStarted(InspectContainerResponse containerInfo) {
42+
createUser(USER_WITHOUT_GROUP);
43+
createUser(FIRST_GROUP_USER);
44+
createUser(SECOND_GROUP_USER);
45+
46+
exec(SAMBA_TOOL, GROUP, "add", FIRST_GROUP);
47+
exec(SAMBA_TOOL, GROUP, "add", SECOND_GROUP);
48+
49+
exec(SAMBA_TOOL, GROUP, "addmembers", FIRST_GROUP, FIRST_GROUP_USER);
50+
exec(SAMBA_TOOL, GROUP, "addmembers", SECOND_GROUP, SECOND_GROUP_USER);
51+
}
52+
53+
public String getLdapUrl() {
54+
return String.format("ldap://%s:%s", getHost(), getMappedPort(LDAP_PORT));
55+
}
56+
57+
private void createUser(String name) {
58+
exec(SAMBA_TOOL, "user", "create", name, PASSWORD, "--mail-address", name + '@' + DOMAIN_EMAIL);
59+
exec(SAMBA_TOOL, "user", "setexpiry", name, "--noexpiry");
60+
}
61+
62+
private void exec(String... cmd) {
63+
ExecResult result;
64+
try {
65+
result = execInContainer(cmd);
66+
} catch (IOException | InterruptedException e) {
67+
throw new RuntimeException(e);
68+
}
69+
70+
if (result.getStdout() != null && !result.getStdout().isEmpty()) {
71+
log.info("Output: {}", result.getStdout());
72+
}
73+
74+
if (result.getExitCode() != 0) {
75+
throw new IllegalStateException(result.toString());
76+
}
77+
}
78+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
spring:
2+
jmx:
3+
enabled: true
4+
auth:
5+
type: LDAP
6+
rbac:
7+
roles:
8+
- name: "roleName"
9+
clusters:
10+
- local
11+
subjects:
12+
- provider: ldap_ad
13+
type: group
14+
value: firstGroup
15+
- provider: ldap_ad
16+
type: group
17+
value: secondGroup
18+
permissions:
19+
- resource: applicationconfig
20+
actions: all
21+
- resource: topic
22+
value: ".*"
23+
actions: all

0 commit comments

Comments
 (0)