diff --git a/api/src/test/java/io/kafbat/ui/OpenLDAPIntegrationTest.java b/api/src/test/java/io/kafbat/ui/OpenLDAPIntegrationTest.java new file mode 100644 index 000000000..b85cfccd6 --- /dev/null +++ b/api/src/test/java/io/kafbat/ui/OpenLDAPIntegrationTest.java @@ -0,0 +1,129 @@ +package io.kafbat.ui; + +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 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; +import java.util.List; +import java.util.Objects; + +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; + +@SpringBootTest +@ActiveProfiles("rbac-ldap") +@AutoConfigureWebTestClient(timeout = "60000") +@ContextConfiguration(initializers = {OpenLDAPIntegrationTest.Initializer.class}) +class OpenLDAPIntegrationTest { + 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()); + System.out.println("info = " + info); + List permissions = info.getUserInfo().getPermissions(); + assertFalse(permissions.isEmpty()); + assertTrue(permissions.stream().anyMatch(permission -> + permission.getClusters().contains(LOCAL) && permission.getResource() == ResourceTypeDTO.TOPIC)); + assertEquals(permissions, authenticationInfo("johnwick").getUserInfo().getPermissions()); + assertEquals(permissions, authenticationInfo("jacksmith").getUserInfo().getPermissions()); + } + + @Test + public void testDirectUserPermissions() { + AuthenticationInfoDTO info = authenticationInfo("jacksmith"); + + assertNotNull(info); + assertTrue(info.getRbacEnabled()); + System.out.println("info = " + info); + List permissions = info.getUserInfo().getPermissions(); + assertFalse(permissions.isEmpty()); + } + + @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()); + System.setProperty("oauth2.ldap.activeDirectory", "false"); + } + } +} 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..ee9a6fc6f --- /dev/null +++ b/api/src/test/resources/application-rbac-ldap.yml @@ -0,0 +1,38 @@ +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 +oauth2: + ldap: + activeDirectory: false +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 +