diff --git a/api/pom.xml b/api/pom.xml
index dd2c3a378..911f7d2f2 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -139,6 +139,12 @@
org.springframework.boot
spring-boot-starter-test
test
+
+
+ com.vaadin.external.google
+ android-json
+
+
io.projectreactor
diff --git a/api/src/test/java/io/kafbat/ui/OpenLdapPIntegrationTest.java b/api/src/test/java/io/kafbat/ui/OpenLdapPIntegrationTest.java
new file mode 100644
index 000000000..dd38ad825
--- /dev/null
+++ b/api/src/test/java/io/kafbat/ui/OpenLdapPIntegrationTest.java
@@ -0,0 +1,122 @@
+package io.kafbat.ui;
+
+import static io.kafbat.ui.AbstractIntegrationTest.LOCAL;
+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 io.kafbat.ui.api.model.Action;
+import io.kafbat.ui.container.OpenLdapContainer;
+import io.kafbat.ui.model.AuthenticationInfoDTO;
+import io.kafbat.ui.model.ResourceTypeDTO;
+import io.kafbat.ui.model.UserPermissionDTO;
+import java.util.List;
+import java.util.Objects;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import org.springframework.web.reactive.function.BodyInserters;
+
+@SpringBootTest
+@ActiveProfiles("rbac-ldap")
+@AutoConfigureWebTestClient(timeout = "60000")
+@ContextConfiguration(initializers = {OpenLdapPIntegrationTest.Initializer.class})
+class OpenLdapPIntegrationTest {
+ private static final String SESSION = "SESSION";
+ private static final OpenLdapContainer LDAP_CONTAINER = new OpenLdapContainer();
+
+ @Autowired
+ private WebTestClient webTestClient;
+
+ @DynamicPropertySource
+ static void neo4jProperties(DynamicPropertyRegistry registry) {
+ registry.add("spring.ldap.urls", LDAP_CONTAINER::getLdapUrl);
+ }
+
+ @BeforeAll
+ static void setup() {
+ LDAP_CONTAINER.start();
+ }
+
+ @AfterAll
+ static void shutdown() {
+ LDAP_CONTAINER.stop();
+ }
+
+ @Test
+ public void testUserPermissions() {
+ AuthenticationInfoDTO info = authenticationInfo("johndoe");
+
+ assertNotNull(info);
+ assertTrue(info.getRbacEnabled());
+ List permissions = info.getUserInfo().getPermissions();
+ assertFalse(permissions.isEmpty());
+ assertTrue(permissions.stream().anyMatch(permission ->
+ permission.getClusters().contains(LOCAL)
+ && permission.getResource() == ResourceTypeDTO.TOPIC
+ && permission.getActions().stream()
+ .allMatch(action -> Action.fromValue(action.getValue()) != Action.ALL)
+ )
+ );
+ assertEquals(permissions, authenticationInfo("johnwick").getUserInfo().getPermissions());
+ assertEquals(permissions, authenticationInfo("jacksmith").getUserInfo().getPermissions());
+ }
+
+ @Test
+ public void testEmptyPermissions() {
+ assertTrue(Objects.requireNonNull(authenticationInfo("johnjames"))
+ .getUserInfo()
+ .getPermissions()
+ .isEmpty()
+ );
+ }
+
+ private String session(String name) {
+ return Objects.requireNonNull(
+ webTestClient
+ .post()
+ .uri("/login")
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .body(BodyInserters.fromFormData("username", name).with("password", name + "@kafbat.io"))
+ .exchange()
+ .expectStatus()
+ .isFound()
+ .returnResult(String.class)
+ .getResponseCookies()
+ .getFirst(SESSION))
+ .getValue();
+ }
+
+ private AuthenticationInfoDTO authenticationInfo(String name) {
+ return webTestClient
+ .get()
+ .uri("/api/authorization")
+ .cookie(SESSION, session(name))
+ .exchange()
+ .expectStatus()
+ .isOk()
+ .returnResult(AuthenticationInfoDTO.class)
+ .getResponseBody()
+ .blockFirst();
+ }
+
+ public static class Initializer implements ApplicationContextInitializer {
+
+ @Override
+ public void initialize(ConfigurableApplicationContext context) {
+ System.setProperty("spring.ldap.urls", LDAP_CONTAINER.getLdapUrl());
+ }
+ }
+}
diff --git a/api/src/test/java/io/kafbat/ui/container/OpenLdapContainer.java b/api/src/test/java/io/kafbat/ui/container/OpenLdapContainer.java
new file mode 100644
index 000000000..18bded9b2
--- /dev/null
+++ b/api/src/test/java/io/kafbat/ui/container/OpenLdapContainer.java
@@ -0,0 +1,34 @@
+package io.kafbat.ui.container;
+
+import lombok.extern.slf4j.Slf4j;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
+import org.testcontainers.utility.MountableFile;
+
+@Slf4j
+public class OpenLdapContainer extends GenericContainer {
+ public static final String ADMIN_PASSWORD = "StrongPassword123";
+ private static final String DOMAIN = "kafbat.io";
+ private static final String DOMAIN_DC = "dc=kafbat,dc=io";
+ private static final int LDAP_PORT = 1389;
+ private static final DockerImageName IMAGE_NAME = DockerImageName.parse("bitnami/openldap:2.6.9");
+
+ public OpenLdapContainer() {
+ super(IMAGE_NAME);
+
+ withExposedPorts(LDAP_PORT);
+
+ withEnv("LDAP_ORGANISATION", DOMAIN.replace(".", ""));
+ withEnv("LDAP_DOMAIN", DOMAIN);
+ withEnv("LDAP_ROOT", DOMAIN_DC);
+ withEnv("LDAP_ADMIN_DN", "cn=admin," + DOMAIN_DC);
+ withEnv("LDAP_ADMIN_PASSWORD", ADMIN_PASSWORD);
+ withEnv("LDAP_LOGLEVEL", "-1");
+
+ withCopyFileToContainer(MountableFile.forClasspathResource("/open-ldap/"), "/ldifs/");
+ }
+
+ public String getLdapUrl() {
+ return String.format("ldap://%s:%s", getHost(), getMappedPort(LDAP_PORT));
+ }
+}
diff --git a/api/src/test/resources/application-rbac-ldap.yml b/api/src/test/resources/application-rbac-ldap.yml
new file mode 100644
index 000000000..76d87529b
--- /dev/null
+++ b/api/src/test/resources/application-rbac-ldap.yml
@@ -0,0 +1,35 @@
+spring:
+ ldap:
+ base: "cn={0},ou=people,dc=kafbat,dc=io"
+ admin-user: "cn=admin,dc=kafbat,dc=io"
+ admin-password: "StrongPassword123"
+ user-filter-search-base: "dc=kafbat,dc=io"
+ user-filter-search-filter: "(&(uid={0})(objectClass=inetOrgPerson))"
+ group-filter-search-base: "ou=people,dc=kafbat,dc=io" # required for RBAC
+logging:
+ level:
+ root: info
+
+auth:
+ type: LDAP
+rbac:
+ roles:
+ - name: "roleName"
+ clusters:
+ - local
+ subjects:
+ - provider: ldap
+ type: group
+ value: firstGroup
+ - provider: ldap
+ type: group
+ value: secondGroup
+ - provider: ldap
+ type: user
+ value: jacksmith
+ permissions:
+ - resource: applicationconfig
+ actions: all
+ - resource: topic
+ value: ".*"
+ actions: all
diff --git a/api/src/test/resources/open-ldap/export.ldif b/api/src/test/resources/open-ldap/export.ldif
new file mode 100644
index 000000000..c0635064c
--- /dev/null
+++ b/api/src/test/resources/open-ldap/export.ldif
@@ -0,0 +1,64 @@
+dn: dc=kafbat,dc=io
+objectClass: dcObject
+objectClass: organization
+dc: kafbat
+o: kafbat
+
+# dn: ou=groups,dc=kafbat,dc=io
+# ou: groups
+# objectClass: organizationalUnit
+
+dn: ou=people,dc=kafbat,dc=io
+ou: people
+objectClass: top
+objectClass: organizationalUnit
+
+dn: cn=johndoe,ou=people,dc=kafbat,dc=io
+sn: JohnDoe
+cn: johndoe
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: inetOrgPerson
+userPassword: johndoe@kafbat.io
+
+dn: cn=johnwick,ou=people,dc=kafbat,dc=io
+sn: JohnWick
+cn: johnwick
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: inetOrgPerson
+userPassword: johnwick@kafbat.io
+
+dn: cn=jacksmith,ou=people,dc=kafbat,dc=io
+sn: JackSmith
+cn: jacksmith
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: inetOrgPerson
+userPassword: jacksmith@kafbat.io
+
+dn: cn=johnjames,ou=people,dc=kafbat,dc=io
+sn: JohnJames
+cn: johnjames
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: inetOrgPerson
+userPassword: johnjames@kafbat.io
+
+dn: cn=firstGroup,ou=people,dc=kafbat,dc=io
+description: App First Group Team
+cn: firstGroup
+objectClass: top
+objectClass: groupOfNames
+member: cn=johndoe,ou=people,dc=kafbat,dc=io
+
+dn: cn=secondGroup,ou=people,dc=kafbat,dc=io
+cn: secondGroup
+objectClass: top
+objectClass: groupOfNames
+member: cn=johnwick,ou=people,dc=kafbat,dc=io
+