This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 4845993e7bccc382be9e69e1eaa70a48e7b7cd22 Author: Benoit TELLIER <[email protected]> AuthorDate: Tue Mar 10 09:47:23 2026 +0100 JAMES-4189 Ability to list users by domain --- .../modules/servers/partials/operate/webadmin.adoc | 22 ++++ .../store/quota/DefaultQuotaChangeNotifier.java | 8 +- .../mailbox/UsersRepositoryUsernameSupplier.java | 6 ++ .../org/apache/james/user/api/UsersRepository.java | 7 ++ .../james/user/ldap/ReadOnlyLDAPUsersDAO.java | 46 +++++++++ .../user/ldap/ReadOnlyUsersLDAPRepositoryTest.java | 20 ++++ .../java/org/apache/james/user/lib/UsersDAO.java | 8 ++ .../apache/james/user/lib/UsersRepositoryImpl.java | 9 ++ .../james/user/lib/UsersRepositoryContract.java | 31 ++++++ .../james/user/postgres/PostgresUsersDAO.java | 11 ++ .../james/webadmin/routes/DomainsRoutes.java | 29 ++++++ .../james/webadmin/routes/DomainsRoutesTest.java | 113 +++++++++++++++++++++ 12 files changed, 307 insertions(+), 3 deletions(-) diff --git a/docs/modules/servers/partials/operate/webadmin.adoc b/docs/modules/servers/partials/operate/webadmin.adoc index 3efd9a83e4..11b1f955fa 100644 --- a/docs/modules/servers/partials/operate/webadmin.adoc +++ b/docs/modules/servers/partials/operate/webadmin.adoc @@ -460,6 +460,28 @@ Response codes: * 200: The domain list was successfully retrieved +=== Get the list of users for a domain + +.... +curl -XGET http://ip:port/domains/domain.tld/users +.... + +Will return the list of users for this domain: + +.... +[ + "[email protected]", + ... +] +.... + +* 200: The domain aliases was successfully retrieved +* 400: domain.tld has an invalid syntax +* 404: domain.tld is not part of handled domains and does +not have local domains as aliases. + +Only supported when virtualHosting is enabled. + === Get the list of aliases for a domain .... diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/DefaultQuotaChangeNotifier.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/DefaultQuotaChangeNotifier.java index ec1904511d..2d4eb5b6de 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/DefaultQuotaChangeNotifier.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/DefaultQuotaChangeNotifier.java @@ -43,7 +43,10 @@ import reactor.core.publisher.Mono; public class DefaultQuotaChangeNotifier implements QuotaChangeNotifier { @FunctionalInterface public interface UsernameSupplier extends Supplier<Flux<Username>> { - + default Flux<Username> getUsersForDomain(Domain domain) { + return get() + .filter(username -> username.getDomainPart().map(domain::equals).orElse(false)); + } } private static final ImmutableSet<RegistrationKey> NO_REGISTRATION_KEYS = ImmutableSet.of(); @@ -80,9 +83,8 @@ public class DefaultQuotaChangeNotifier implements QuotaChangeNotifier { @Override public Publisher<Void> notifyUpdate(Domain domain) { - return usernameSupplier.get() + return usernameSupplier.getUsersForDomain(domain) .map(quotaRootResolver::forUser) - .filter(user -> user.getDomain().map(domain::equals).orElse(false)) .concatMap(this::notifyUpdate) .then(); } diff --git a/server/container/mailbox-adapter/src/main/java/org/apache/james/adapter/mailbox/UsersRepositoryUsernameSupplier.java b/server/container/mailbox-adapter/src/main/java/org/apache/james/adapter/mailbox/UsersRepositoryUsernameSupplier.java index 08b60ba398..fcc886efbd 100644 --- a/server/container/mailbox-adapter/src/main/java/org/apache/james/adapter/mailbox/UsersRepositoryUsernameSupplier.java +++ b/server/container/mailbox-adapter/src/main/java/org/apache/james/adapter/mailbox/UsersRepositoryUsernameSupplier.java @@ -21,6 +21,7 @@ package org.apache.james.adapter.mailbox; import jakarta.inject.Inject; +import org.apache.james.core.Domain; import org.apache.james.core.Username; import org.apache.james.mailbox.store.quota.DefaultQuotaChangeNotifier; import org.apache.james.user.api.UsersRepository; @@ -39,4 +40,9 @@ public class UsersRepositoryUsernameSupplier implements DefaultQuotaChangeNotifi public Flux<Username> get() { return Flux.from(usersRepository.listReactive()); } + + @Override + public Flux<Username> getUsersForDomain(Domain domain) { + return Flux.from(usersRepository.listUsersOfADomainReactive(domain)); + } } diff --git a/server/data/data-api/src/main/java/org/apache/james/user/api/UsersRepository.java b/server/data/data-api/src/main/java/org/apache/james/user/api/UsersRepository.java index 3edf2c303a..16fabdab51 100644 --- a/server/data/data-api/src/main/java/org/apache/james/user/api/UsersRepository.java +++ b/server/data/data-api/src/main/java/org/apache/james/user/api/UsersRepository.java @@ -188,6 +188,13 @@ public interface UsersRepository { } default Publisher<Username> listUsersOfADomainReactive(Domain domain) { + try { + if (!supportVirtualHosting()) { + return Flux.error(new IllegalStateException("listUsersOfADomainReactive is not supported when virtual hosting is disabled")); + } + } catch (UsersRepositoryException e) { + return Flux.error(e); + } return Flux.from(listReactive()) .filter(username -> username.getDomainPart() .map(domain::equals) diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java index 6b749be5b1..70bb67f675 100644 --- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java +++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java @@ -60,6 +60,9 @@ import com.unboundid.ldap.sdk.SearchResult; import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.ldap.sdk.SearchScope; +import reactor.core.publisher.Flux; +import reactor.core.scheduler.Schedulers; + public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable { private static final Logger LOGGER = LoggerFactory.getLogger(ReadOnlyLDAPUsersDAO.class); @@ -338,6 +341,49 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable { } + @Override + public Flux<Username> listUsersOfADomainReactive(Domain domain) { + return Flux.fromStream(() -> { + try { + return getUsernamesForDomain(domain); + } catch (LDAPException e) { + throw new RuntimeException(e); + } + }) + .subscribeOn(Schedulers.boundedElastic()); + } + + private Stream<Username> getUsernamesForDomain(Domain domain) throws LDAPException { + if (!ldapConfiguration.getRestriction().isActivated()) { + return getUnrestrictedUsernamesForDomain(domain); + } + return buildUserCollection(getValidUserDNs()).stream() + .map(ReadOnlyLDAPUser::getUserName) + .filter(username -> username.getDomainPart().map(domain::equals).orElse(false)) + .distinct(); + } + + private Stream<Username> getUnrestrictedUsernamesForDomain(Domain domain) throws LDAPException { + String usernameAttribute = ldapConfiguration.getUsernameAttribute().orElse(ldapConfiguration.getUserIdAttribute()); + Filter domainFilter = Filter.createSubstringFilter(usernameAttribute, null, null, "@" + domain.asString()); + SearchRequest searchRequest = new SearchRequest(userBase(domain), SearchScope.SUB, + Filter.createANDFilter(listingFilter, domainFilter), usernameAttribute); + return ldapConnectionPool.search(searchRequest) + .getSearchEntries().stream() + .flatMap(entry -> Optional.ofNullable(entry.getAttribute(usernameAttribute)).stream()) + .map(Attribute::getValue) + .flatMap(name -> { + try { + return Stream.of(Username.of(name)); + } catch (Exception e) { + LOGGER.warn("Invalid username in the LDAP: {}", name, e); + return Stream.empty(); + } + }) + .filter(username -> username.getDomainPart().map(domain::equals).orElse(false)) + .distinct(); + } + private Collection<DN> getValidUserDNs() throws LDAPException { Set<DN> userDNs = getAllUsersDNFromLDAP(); Collection<DN> validUserDNs; diff --git a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java index 6673bc1361..440f2256e0 100644 --- a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java +++ b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java @@ -35,6 +35,7 @@ import org.apache.commons.configuration2.HierarchicalConfiguration; import org.apache.commons.configuration2.ex.ConversionException; import org.apache.commons.configuration2.plist.PropertyListConfiguration; import org.apache.commons.configuration2.tree.ImmutableNode; +import org.apache.james.core.Domain; import org.apache.james.core.Username; import org.apache.james.domainlist.api.DomainList; import org.apache.james.domainlist.api.mock.SimpleDomainList; @@ -56,6 +57,8 @@ import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; import com.unboundid.ldap.sdk.LDAPException; +import reactor.core.publisher.Flux; + class ReadOnlyUsersLDAPRepositoryTest { static final Logger LOGGER = LoggerFactory.getLogger(ReadOnlyUsersLDAPRepositoryTest.class); @@ -347,6 +350,22 @@ class ReadOnlyUsersLDAPRepositoryTest { assertThat(usersRepository.contains(usersRepository.getUsername(JAMES_USER_MAIL.asMailAddress()))).isTrue(); } + @Test + void listUsersOfADomainReactiveShouldReturnUsersOfDomain() throws Exception { + assertThat(Flux.from(usersRepository.listUsersOfADomainReactive(Domain.of(DOMAIN))) + .collectList() + .block()) + .containsOnly(JAMES_USER_MAIL); + } + + @Test + void listUsersOfADomainReactiveShouldReturnEmptyForUnknownDomain() throws Exception { + assertThat(Flux.from(usersRepository.listUsersOfADomainReactive(Domain.of("other.org"))) + .collectList() + .block()) + .isEmpty(); + } + @Disabled("JAMES-3088 Users are provisioned by default from Dockerfile, cannot setup this test case," + "See @link{ReadOnlyUsersLDAPRepositoryEmptyListTest}") @Override @@ -580,6 +599,7 @@ class ReadOnlyUsersLDAPRepositoryTest { PropertyListConfiguration configuration = baseConfiguration(ldapContainer); configuration.addProperty("[@userIdAttribute]", "mail"); configuration.addProperty("supportsVirtualHosting", true); + configuration.addProperty("enableVirtualHosting", true); administrator.ifPresent(username -> configuration.addProperty("[@administratorId]", username.asString())); return configuration; } diff --git a/server/data/data-library/src/main/java/org/apache/james/user/lib/UsersDAO.java b/server/data/data-library/src/main/java/org/apache/james/user/lib/UsersDAO.java index d345406292..c333f3af76 100644 --- a/server/data/data-library/src/main/java/org/apache/james/user/lib/UsersDAO.java +++ b/server/data/data-library/src/main/java/org/apache/james/user/lib/UsersDAO.java @@ -22,6 +22,7 @@ package org.apache.james.user.lib; import java.util.Iterator; import java.util.Optional; +import org.apache.james.core.Domain; import org.apache.james.core.Username; import org.apache.james.user.api.UsersRepositoryException; import org.apache.james.user.api.model.User; @@ -64,5 +65,12 @@ public interface UsersDAO { }).subscribeOn(Schedulers.boundedElastic()); } + default Publisher<Username> listUsersOfADomainReactive(Domain domain) { + return Flux.from(listReactive()) + .filter(username -> username.getDomainPart() + .map(domain::equals) + .orElse(false)); + } + void addUser(Username username, String password) throws UsersRepositoryException; } diff --git a/server/data/data-library/src/main/java/org/apache/james/user/lib/UsersRepositoryImpl.java b/server/data/data-library/src/main/java/org/apache/james/user/lib/UsersRepositoryImpl.java index b27bc3bc3d..cf0beacad3 100644 --- a/server/data/data-library/src/main/java/org/apache/james/user/lib/UsersRepositoryImpl.java +++ b/server/data/data-library/src/main/java/org/apache/james/user/lib/UsersRepositoryImpl.java @@ -52,6 +52,7 @@ import com.github.fge.lambdas.Throwing; import com.google.common.base.CharMatcher; import com.google.common.collect.ImmutableSet; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public class UsersRepositoryImpl<T extends UsersDAO> implements UsersRepository, Configurable { @@ -262,6 +263,14 @@ public class UsersRepositoryImpl<T extends UsersDAO> implements UsersRepository, return usersDAO.listReactive(); } + @Override + public Publisher<Username> listUsersOfADomainReactive(Domain domain) { + if (!virtualHosting) { + return Flux.error(new IllegalStateException("listUsersOfADomainReactive is not supported when virtual hosting is disabled")); + } + return usersDAO.listUsersOfADomainReactive(domain); + } + @Override public boolean supportVirtualHosting() { return virtualHosting; diff --git a/server/data/data-library/src/test/java/org/apache/james/user/lib/UsersRepositoryContract.java b/server/data/data-library/src/test/java/org/apache/james/user/lib/UsersRepositoryContract.java index 61f062c453..fbad8b98eb 100644 --- a/server/data/data-library/src/test/java/org/apache/james/user/lib/UsersRepositoryContract.java +++ b/server/data/data-library/src/test/java/org/apache/james/user/lib/UsersRepositoryContract.java @@ -582,6 +582,28 @@ public interface UsersRepositoryContract { .containsOnly(Username.of("[email protected]")); } + @Test + default void listUsersOfADomainShouldReturnEmptyWhenNoneInDomain(TestSystem testSystem) throws Exception { + testSystem.domainList.addDomain(Domain.of("empty.tld")); + + assertThat(Flux.from(testee().listUsersOfADomainReactive(Domain.of("empty.tld"))) + .collectList() + .block()) + .isEmpty(); + } + + @Test + default void listUsersOfADomainShouldReturnAllUsersOfDomain(TestSystem testSystem) throws Exception { + testSystem.domainList.addDomain(Domain.of("domain.tld")); + testee().addUser(Username.of("[email protected]"), "password"); + testee().addUser(Username.of("[email protected]"), "password"); + + assertThat(Flux.from(testee().listUsersOfADomainReactive(Domain.of("domain.tld"))) + .collectList() + .block()) + .containsExactlyInAnyOrder(Username.of("[email protected]"), Username.of("[email protected]")); + } + @Test default void addUserShouldThrowWhenUserDoesNotBelongToDomainList(TestSystem testSystem) { assertThatThrownBy(() -> testee().addUser(testSystem.userWithUnknownDomain, "password")) @@ -760,6 +782,15 @@ public interface UsersRepositoryContract { assertThatCode(() -> testee().assertValid(withOutDomainPart)) .doesNotThrowAnyException(); } + + @Test + default void listUsersOfADomainShouldFailWhenVirtualHostingIsDisabled(TestSystem testSystem) { + assertThatThrownBy(() -> Flux.from(testee().listUsersOfADomainReactive(TestSystem.DOMAIN)) + .collectList() + .block()) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("listUsersOfADomainReactive is not supported when virtual hosting is disabled"); + } } interface WithVirtualHostingContract extends WithVirtualHostingReadOnlyContract, WithVirtualHostingReadWriteContract { diff --git a/server/data/data-postgres/src/main/java/org/apache/james/user/postgres/PostgresUsersDAO.java b/server/data/data-postgres/src/main/java/org/apache/james/user/postgres/PostgresUsersDAO.java index 1340c87703..ba7c49e631 100644 --- a/server/data/data-postgres/src/main/java/org/apache/james/user/postgres/PostgresUsersDAO.java +++ b/server/data/data-postgres/src/main/java/org/apache/james/user/postgres/PostgresUsersDAO.java @@ -38,6 +38,7 @@ import jakarta.inject.Inject; import jakarta.inject.Named; import org.apache.james.backends.postgres.utils.PostgresExecutor; +import org.apache.james.core.Domain; import org.apache.james.core.Username; import org.apache.james.user.api.AlreadyExistInUsersRepositoryException; import org.apache.james.user.api.UsersRepositoryException; @@ -146,6 +147,16 @@ public class PostgresUsersDAO implements UsersDAO { .map(record -> Username.of(record.get(USERNAME))); } + @Override + public Flux<Username> listUsersOfADomainReactive(Domain domain) { + String domainPattern = "%@" + domain.asString(); + return postgresExecutor.executeRows(dslContext -> Flux.from( + dslContext.select(USERNAME) + .from(TABLE_NAME) + .where(USERNAME.like(domainPattern))), EAGER_FETCH) + .map(record -> Username.of(record.get(USERNAME))); + } + @Override public void addUser(Username username, String password) { DefaultUser user = new DefaultUser(username, algorithm, algorithm); diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DomainsRoutes.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DomainsRoutes.java index b3951145e7..a1108e5926 100644 --- a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DomainsRoutes.java +++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DomainsRoutes.java @@ -21,11 +21,13 @@ package org.apache.james.webadmin.routes; import static org.apache.james.webadmin.Constants.SEPARATOR; +import java.util.List; import java.util.stream.Collectors; import jakarta.inject.Inject; import org.apache.james.core.Domain; +import org.apache.james.core.Username; import org.apache.james.domainlist.api.AutoDetectedDomainRemovalException; import org.apache.james.domainlist.api.DomainList; import org.apache.james.domainlist.api.DomainListException; @@ -33,6 +35,7 @@ import org.apache.james.rrt.api.RecipientRewriteTableException; import org.apache.james.rrt.api.SameSourceAndDestinationException; import org.apache.james.task.TaskManager; import org.apache.james.user.api.UsersRepository; +import org.apache.james.user.api.UsersRepositoryException; import org.apache.james.webadmin.Routes; import org.apache.james.webadmin.dto.DomainAliasResponse; import org.apache.james.webadmin.service.DeleteUserDataService; @@ -51,6 +54,7 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; +import reactor.core.publisher.Flux; import spark.HaltException; import spark.Request; import spark.Response; @@ -67,6 +71,8 @@ public class DomainsRoutes implements Routes { private static final String SPECIFIC_DOMAIN = DOMAINS + SEPARATOR + DOMAIN_NAME; private static final String ALIASES = "aliases"; private static final String DOMAIN_ALIASES = SPECIFIC_DOMAIN + SEPARATOR + ALIASES; + private static final String USERS = "users"; + private static final String DOMAIN_USERS = SPECIFIC_DOMAIN + SEPARATOR + USERS; private static final String DELETE_ALL_USERS_DATA_OF_A_DOMAIN_PATH = "/domains/:domainName"; private static final String SPECIFIC_ALIAS = DOMAINS + SEPARATOR + DESTINATION_DOMAIN + SEPARATOR + ALIASES + SEPARATOR + SOURCE_DOMAIN; private static final TaskRegistrationKey DELETE_USERS_DATA = TaskRegistrationKey.of("deleteData"); @@ -108,6 +114,7 @@ public class DomainsRoutes implements Routes { defineListAliases(service); defineAddAlias(service); defineRemoveAlias(service); + defineListUsersOfDomain(service); // delete data of all users of a domain service.post(DELETE_ALL_USERS_DATA_OF_A_DOMAIN_PATH, deleteAllUsersData(), jsonTransformer); @@ -148,6 +155,28 @@ public class DomainsRoutes implements Routes { service.get(DOMAIN_ALIASES, this::listDomainAliases, jsonTransformer); } + public void defineListUsersOfDomain(Service service) { + service.get(DOMAIN_USERS, this::listUsersOfDomain, jsonTransformer); + } + + private List<String> listUsersOfDomain(Request request, Response response) throws UsersRepositoryException, DomainListException { + if (!usersRepository.supportVirtualHosting()) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.METHOD_NOT_ALLOWED_405) + .type(ErrorType.WRONG_STATE) + .message("Virtual hosting must be enabled to list users by domain") + .haltError(); + } + Domain domain = checkValidDomain(request.params(DOMAIN_NAME)); + if (!domainList.containsDomain(domain)) { + throw domainNotFound(domain); + } + return Flux.from(usersRepository.listUsersOfADomainReactive(domain)) + .map(Username::asString) + .collectList() + .block(); + } + public void defineRemoveAlias(Service service) { service.delete(SPECIFIC_ALIAS, this::removeDomainAlias, jsonTransformer); } diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainsRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainsRoutesTest.java index 9804973478..f7b583165f 100644 --- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainsRoutesTest.java +++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainsRoutesTest.java @@ -107,6 +107,20 @@ class DomainsRoutesTest { private WebAdminServer webAdminServer; private MemoryUsersRepository usersRepository; + private void createServerWithoutVirtualHosting(DomainList domainList) { + MemoryTaskManager taskManager = new MemoryTaskManager(new Hostname("foo")); + DomainAliasService domainAliasService = new DomainAliasService(new MemoryRecipientRewriteTable(), domainList); + usersRepository = MemoryUsersRepository.withoutVirtualHosting(domainList); + webAdminServer = WebAdminUtils.createWebAdminServer(new DomainsRoutes(domainList, domainAliasService, new JsonTransformer(), + new DeleteUserDataService(Set.of()), usersRepository, taskManager)) + .start(); + + RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminServer) + .setBasePath(DomainsRoutes.DOMAINS) + .setUrlEncodingEnabled(false) + .build(); + } + private void createServer(DomainList domainList) { MemoryTaskManager taskManager = new MemoryTaskManager(new Hostname("foo")); DomainAliasService domainAliasService = new DomainAliasService(new MemoryRecipientRewriteTable(), domainList); @@ -746,6 +760,74 @@ class DomainsRoutesTest { } } + @Nested + class DomainUsers { + + @Test + void getUsersOfDomainShouldReturnEmptyWhenNone() { + with().put(DOMAIN); + + when() + .get(DOMAIN + "/users") + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.OK_200) + .body(".", hasSize(0)); + } + + @Test + void getUsersOfDomainShouldReturnUsersWhenSomeExist() throws Exception { + with().put(DOMAIN); + usersRepository.addUser(Username.of("user1@" + DOMAIN), "password"); + usersRepository.addUser(Username.of("user2@" + DOMAIN), "password"); + + when() + .get(DOMAIN + "/users") + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.OK_200) + .body(".", containsInAnyOrder("user1@" + DOMAIN, "user2@" + DOMAIN)); + } + + @Test + void getUsersOfDomainShouldNotReturnUsersOfOtherDomain() throws Exception { + with().put(DOMAIN); + with().put(ALIAS_DOMAIN); + usersRepository.addUser(Username.of("user1@" + DOMAIN), "password"); + usersRepository.addUser(Username.of("user2@" + ALIAS_DOMAIN), "password"); + + when() + .get(DOMAIN + "/users") + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.OK_200) + .body(".", containsInAnyOrder("user1@" + DOMAIN)); + } + + @Test + void getUsersOfDomainShouldReturnNotFoundWhenDomainDoesNotExist() { + when() + .get(DOMAIN + "/users") + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.NOT_FOUND_404) + .body("statusCode", is(HttpStatus.NOT_FOUND_404)) + .body("type", is("InvalidArgument")) + .body("message", is("The domain list does not contain: " + DOMAIN)); + } + + @Test + void getUsersOfDomainShouldReturnBadRequestWhenDomainIsInvalid() { + when() + .get("invalid@domain/users") + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(HttpStatus.BAD_REQUEST_400)) + .body("type", is("InvalidArgument")); + } + } + } @Nested @@ -843,6 +925,37 @@ class DomainsRoutesTest { } + @Nested + class DomainUsersWithoutVirtualHosting { + + @BeforeEach + void setUp() throws Exception { + DNSService dnsService = mock(DNSService.class); + when(dnsService.getHostName(any())).thenReturn("localhost"); + when(dnsService.getLocalHost()).thenReturn(InetAddress.getByName("localhost")); + + MemoryDomainList domainList = new MemoryDomainList(dnsService); + domainList.configure(DomainListConfiguration.builder() + .autoDetect(false) + .autoDetectIp(false) + .build()); + domainList.addDomain(Domain.of(DOMAIN)); + createServerWithoutVirtualHosting(domainList); + } + + @Test + void getUsersOfDomainShouldReturn405WhenVirtualHostingDisabled() { + when() + .get(DOMAIN + "/users") + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.METHOD_NOT_ALLOWED_405) + .body("statusCode", is(HttpStatus.METHOD_NOT_ALLOWED_405)) + .body("type", is("WrongState")) + .body("message", is("Virtual hosting must be enabled to list users by domain")); + } + } + @Nested class DetectedDomainHandling { @BeforeEach --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
