This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch droplist in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/droplist by this push: new cb474ea101 JAMES-3946 add WebAdmin API to manage the DropList (#2241) cb474ea101 is described below commit cb474ea10185f41e007670e979dcdad9b0d75969 Author: Maksim <85022218+maxxx...@users.noreply.github.com> AuthorDate: Tue May 14 10:29:52 2024 +0300 JAMES-3946 add WebAdmin API to manage the DropList (#2241) --- .../docs/modules/ROOT/pages/operate/webadmin.adoc | 120 +++++++++ .../james/webadmin/routes/DropListRoutes.java | 218 ++++++++++++++++ .../james/webadmin/routes/DropListRoutesTest.java | 273 +++++++++++++++++++++ 3 files changed, 611 insertions(+) diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc index 81ab4f0c00..0186ff42b0 100644 --- a/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc +++ b/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc @@ -2835,6 +2835,126 @@ Response codes: * 204: Operation succeeded +== Administrating DropLists + +The DropList, also known as the mail blacklist, is a collection of +domains and email addresses that are denied from sending emails within the system. + +Owner scopes: + +- *global*: contains entries that are blocked across all domains and addresses within the system. +Entries in the global owner scope apply universally and affect all users and domains. +- *domain*: each domain can have its own droplist, which contains entries specific to that domain. +- *user*: allow to customize personalized droplist of blocked domains and email addresses. + +The `deniedEntityType` query parameter is optional and can take the values `domain` or `address`. + +=== Getting the DropList +==== Global DropList +.... +curl -XGET http://ip:port/droplist/global?deniedEntityType=null|domain|address +.... +==== Domain DropList +.... +curl -XGET http://ip:port/droplist/domain/target.com?deniedEntityType=null|domain|address +.... +==== User DropList +.... +curl -XGET http://ip:port/droplist/user/tag...@target.com?deniedEntityType=null|domain|address +.... + +The answer looks like: +.... +[ "evil.com", "devil.com", "bad_...@crime.com", "hac...@murder.org" ] +.... + +Response codes: + +* 200: The drop list was successfully retrieved +* 400: Invalid `owner scope` or `deniedEntityType` + +=== Testing a denied entity existence +==== Global DropList +.... +curl -XHEAD http://ip:port/droplist/global/attac...@evil.com +.... +.... +curl -XHEAD http://ip:port/droplist/global/evil.com +.... +==== Domain DropList +.... +curl -XHEAD http://ip:port/droplist/domain/target.com/attac...@evil.com +.... +.... +curl -XHEAD http://ip:port/droplist/domain/target.com/evil.com +.... +==== User DropList +.... +curl -XHEAD http://ip:port/droplist/user/tar...@target.com/attac...@evil.com +.... +.... +curl -XHEAD http://ip:port/droplist/user/tar...@target.com/evil.com +.... +Response codes: + +* 200: The denied entity exists +* 404: The denied entity does not exist + +=== Add Entry to the DropList +The denied entity must be a valid email address or link:#_create_a_domain[domain]. + +==== Global DropList +.... +curl -XPUT http://ip:port/droplist/global/attac...@evil.com +.... +.... +curl -XPUT http://ip:port/droplist/global/evil.com +.... +==== Domain DropList +.... +curl -XPUT http://ip:port/droplist/domain/target.com/attac...@evil.com +.... +.... +curl -XPUT http://ip:port/droplist/domain/target.com/evil.com +.... +==== User DropList +.... +curl -XPUT http://ip:port/droplist/user/tar...@target.com/attac...@evil.com +.... +.... +curl -XPUT http://ip:port/droplist/user/tar...@target.com/evil.com +.... +Response codes: + +* 204: The denied entity was successfully added +* 400: The denied entity is invalid + +=== Remove Entry from the DropList +==== Global DropList +.... +curl -XDELETE http://ip:port/droplist/global/attac...@evil.com +.... +.... +curl -XDELETE http://ip:port/droplist/global/evil.com +.... +==== Domain DropList +.... +curl -XDELETE http://ip:port/droplist/domain/target.com/attac...@evil.com +.... +.... +curl -XDELETE http://ip:port/droplist/domain/target.com/evil.com +.... +==== User DropList +.... +curl -XDELETE http://ip:port/droplist/user/tar...@target.com/attac...@evil.com +.... +.... +curl -XDELETE http://ip:port/droplist/user/tar...@target.com/evil.com +.... +Response codes: + +* 204: Entry deleted successfully. + == Administrating Jmap Uploads === Cleaning upload repository diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DropListRoutes.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DropListRoutes.java new file mode 100644 index 0000000000..5fdb93f28e --- /dev/null +++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DropListRoutes.java @@ -0,0 +1,218 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.webadmin.routes; + +import static org.apache.james.webadmin.Constants.SEPARATOR; + +import java.util.List; +import java.util.Optional; + +import jakarta.inject.Inject; +import jakarta.mail.internet.AddressException; + +import org.apache.commons.lang3.EnumUtils; +import org.apache.james.core.Domain; +import org.apache.james.core.MailAddress; +import org.apache.james.droplists.api.DeniedEntityType; +import org.apache.james.droplists.api.DropList; +import org.apache.james.droplists.api.DropListEntry; +import org.apache.james.droplists.api.OwnerScope; +import org.apache.james.util.ReactorUtils; +import org.apache.james.webadmin.Constants; +import org.apache.james.webadmin.Routes; +import org.apache.james.webadmin.utils.ErrorResponder; +import org.apache.james.webadmin.utils.JsonTransformer; +import org.apache.james.webadmin.utils.Responses; +import org.eclipse.jetty.http.HttpStatus; + +import com.google.common.collect.ImmutableSet; + +import reactor.core.publisher.Flux; +import spark.Request; +import spark.Response; +import spark.Service; + +public class DropListRoutes implements Routes { + public static final String DROP_LIST = "/droplist"; + public static final String OWNER_SCOPE = ":ownerScope"; + public static final String OWNER = ":owner"; + public static final String DENIED_ENTITY = ":deniedEntity"; + public static final String DENIED_ENTITY_TYPE = "deniedEntityType"; + + private final DropList dropList; + private final JsonTransformer jsonTransformer; + + @Inject + public DropListRoutes(DropList dropList, JsonTransformer jsonTransformer) { + this.dropList = dropList; + this.jsonTransformer = jsonTransformer; + } + + @Override + public String getBasePath() { + return DROP_LIST; + } + + @Override + public void define(Service service) { + service.get(DROP_LIST + SEPARATOR + OWNER_SCOPE, this::getDropList, jsonTransformer); + service.get(DROP_LIST + SEPARATOR + OWNER_SCOPE + SEPARATOR + OWNER, this::getDropList, jsonTransformer); + service.put(DROP_LIST + SEPARATOR + OWNER_SCOPE + SEPARATOR + OWNER + SEPARATOR + DENIED_ENTITY, this::addDropListEntry); + service.put(DROP_LIST + SEPARATOR + OWNER_SCOPE + SEPARATOR + DENIED_ENTITY, this::addDropListEntry); + service.head(DROP_LIST + SEPARATOR + OWNER_SCOPE + SEPARATOR + OWNER + SEPARATOR + DENIED_ENTITY, this::dropListEntryExist); + service.head(DROP_LIST + SEPARATOR + OWNER_SCOPE + SEPARATOR + DENIED_ENTITY, this::dropListEntryExist); + service.delete(DROP_LIST + SEPARATOR + OWNER_SCOPE + SEPARATOR + OWNER + SEPARATOR + DENIED_ENTITY, this::removeDropListEntry); + service.delete(DROP_LIST + SEPARATOR + OWNER_SCOPE + SEPARATOR + DENIED_ENTITY, this::removeDropListEntry); + } + + public ImmutableSet<String> getDropList(Request request, Response response) { + OwnerScope ownerScope = checkValidOwnerScope(request.params(OWNER_SCOPE)); + String owner = Optional.ofNullable(request.params(OWNER)).orElse(""); + Optional<DeniedEntityType> deniedEntityType = checkValidDeniedEntityType(request.queryParams(DENIED_ENTITY_TYPE)); + if (deniedEntityType.isPresent()) { + return dropList.list(ownerScope, owner) + .filter(deniedEntry -> deniedEntry.getDeniedEntityType().equals(deniedEntityType.get())) + .map(DropListEntry::getDeniedEntity) + .collect(ImmutableSet.toImmutableSet()) + .block(); + } else { + return dropList.list(ownerScope, owner) + .map(DropListEntry::getDeniedEntity) + .collect(ImmutableSet.toImmutableSet()) + .block(); + } + } + + public String addDropListEntry(Request request, Response response) { + OwnerScope ownerScope = checkValidOwnerScope(request.params(OWNER_SCOPE)); + String owner = Optional.ofNullable(request.params(OWNER)).orElse(""); + String deniedEntity = request.params(DENIED_ENTITY); + DropListEntry dropListEntry = getDropListEntry(ownerScope, owner, deniedEntity); + dropList.add(dropListEntry).block(); + return Responses.returnNoContent(response); + } + + public String removeDropListEntry(Request request, Response response) { + OwnerScope ownerScope = checkValidOwnerScope(request.params(OWNER_SCOPE)); + String owner = Optional.ofNullable(request.params(OWNER)).orElse(""); + String deniedEntity = request.params(DENIED_ENTITY); + dropList.list(ownerScope, owner) + .filter(dropListEntry -> dropListEntry.getDeniedEntity().equals(deniedEntity)) + .collectList() + .doOnNext(this::deleteDropListEntry) + .block(); + return Responses.returnNoContent(response); + } + + public String dropListEntryExist(Request request, Response response) { + OwnerScope ownerScope = checkValidOwnerScope(request.params(OWNER_SCOPE)); + String owner = Optional.ofNullable(request.params(OWNER)).orElse(""); + String deniedEntity = request.params(DENIED_ENTITY); + boolean entryExists = dropList.list(ownerScope, owner) + .any(dropListEntry -> dropListEntry.getDeniedEntity().equals(deniedEntity)) + .block(); + if (entryExists) { + response.status(HttpStatus.NO_CONTENT_204); + } else { + response.status(HttpStatus.NOT_FOUND_404); + } + return Constants.EMPTY_BODY; + } + + private Optional<DeniedEntityType> checkValidDeniedEntityType(String deniedEntityType) { + try { + if (deniedEntityType == null || deniedEntityType.isEmpty()) { + return Optional.empty(); + } else { + return Optional.of(DeniedEntityType.valueOf(deniedEntityType.toUpperCase())); + } + } catch (IllegalArgumentException e) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.BAD_REQUEST_400) + .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) + .message("Invalid DeniedEntityType") + .cause(new IllegalArgumentException("DeniedEntityType '" + deniedEntityType + "' is invalid. Supported values are " + + EnumUtils.getEnumList(DeniedEntityType.class))) + .haltError(); + } + } + + private DropListEntry getDropListEntry(OwnerScope ownerScope, String owner, String deniedEntity) { + DropListEntry.Builder dropListEntryBuilder = DropListEntry.builder(); + switch (ownerScope) { + case GLOBAL -> dropListEntryBuilder = dropListEntryBuilder.forAll(); + case DOMAIN -> dropListEntryBuilder = dropListEntryBuilder.domainOwner(checkValidDomain(owner)); + case USER -> dropListEntryBuilder = dropListEntryBuilder.userOwner(checkValidMailAddress(owner)); + } + if (deniedEntity.contains("@")) { + dropListEntryBuilder.denyAddress(checkValidMailAddress(deniedEntity)); + } else { + dropListEntryBuilder.denyDomain(checkValidDomain(deniedEntity)); + } + return dropListEntryBuilder.build(); + } + + private OwnerScope checkValidOwnerScope(String ownerScope) { + try { + return OwnerScope.valueOf(ownerScope.toUpperCase()); + } catch (IllegalArgumentException e) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.BAD_REQUEST_400) + .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) + .message("Invalid OwnerScope") + .cause(new IllegalArgumentException("OwnerScope '" + ownerScope + "' is invalid. Supported values are " + + EnumUtils.getEnumList(OwnerScope.class))) + .haltError(); + } + } + + private static MailAddress checkValidMailAddress(String address) { + try { + return new MailAddress(address); + } catch (AddressException e) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.BAD_REQUEST_400) + .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) + .message("Invalid mail address %s", address) + .cause(e) + .haltError(); + } + } + + private Domain checkValidDomain(String domainName) { + try { + return Domain.of(domainName); + } catch (IllegalArgumentException e) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.BAD_REQUEST_400) + .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) + .message("Invalid domain %s", domainName) + .cause(e) + .haltError(); + } + } + + private void deleteDropListEntry(List<DropListEntry> dropListEntries) { + Flux.fromIterable(dropListEntries) + .flatMap(dropList::remove, ReactorUtils.DEFAULT_CONCURRENCY) + .then() + .block(); + } +} \ No newline at end of file diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DropListRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DropListRoutesTest.java new file mode 100644 index 0000000000..3ba3df5d2e --- /dev/null +++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DropListRoutesTest.java @@ -0,0 +1,273 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.webadmin.routes; + +import static io.restassured.RestAssured.given; +import static io.restassured.RestAssured.when; +import static org.apache.james.webadmin.Constants.SEPARATOR; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import jakarta.mail.internet.AddressException; + +import org.apache.james.core.MailAddress; +import org.apache.james.droplists.api.DropList; +import org.apache.james.droplists.api.DropListEntry; +import org.apache.james.droplists.api.OwnerScope; +import org.apache.james.droplists.memory.MemoryDropList; +import org.apache.james.webadmin.WebAdminServer; +import org.apache.james.webadmin.WebAdminUtils; +import org.apache.james.webadmin.utils.ErrorResponder; +import org.apache.james.webadmin.utils.JsonTransformer; +import org.eclipse.jetty.http.HttpStatus; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; + +class DropListRoutesTest { + private static final String DENIED_SENDER = "attac...@evil.com"; + private static final String OWNER_RECIPIENT = "ow...@owner.com"; + + private WebAdminServer webAdminServer; + private DropList dropList; + + @BeforeEach + public void setUp() throws Exception { + dropList = new MemoryDropList(); + DropListRoutes dropListRoutes = new DropListRoutes(dropList, new JsonTransformer()); + this.webAdminServer = WebAdminUtils.createWebAdminServer(dropListRoutes).start(); + RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminServer).build(); + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); + getDropListTestEntries().forEach(entry -> dropList.add(entry).block()); + } + + @AfterEach + void tearDown() throws AddressException { + webAdminServer.destroy(); + getDropListTestEntries().forEach(entry -> dropList.remove(entry).block()); + } + + @ParameterizedTest(name = "{index} Owner: {0}") + @ValueSource(strings = { + "global", + "domain/owner.com", + "user/ow...@owner.com"}) + void shouldGetFullDropList(String pathParam) { + when() + .get(DropListRoutes.DROP_LIST + SEPARATOR + pathParam) + .then() + .statusCode(HttpStatus.OK_200) + .contentType(ContentType.JSON) + .body(".", containsInAnyOrder("attac...@evil.com", "evil.com")); + } + + @ParameterizedTest(name = "{index} Owner: {0}") + @ValueSource(strings = { + "unknown", + "unknown/owner.com", + "unknown/ow...@owner.com"}) + void shouldHandleWhenGetDropListWithInvalidOwnerScope(String pathParam) { + when() + .get(DropListRoutes.DROP_LIST + SEPARATOR + pathParam) + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .contentType(ContentType.JSON) + .body("statusCode", is(HttpStatus.BAD_REQUEST_400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("Invalid OwnerScope")) + .body("details", is("OwnerScope 'unknown' is invalid. Supported values are [GLOBAL, DOMAIN, USER]")); + } + + @ParameterizedTest(name = "{index} Owner: {0}, DeniedEntityType: {1}") + @CsvSource(value = { + "global, domain, evil.com", + "global, address, attac...@evil.com", + "domain/owner.com, domain, evil.com", + "domain/owner.com, address, attac...@evil.com", + "user/ow...@owner.com, domain, evil.com", + "user/ow...@owner.com, address, attac...@evil.com"}) + void shouldGetDropListWithQueryParams(String pathParam, String queryParam, String expected) { + given() + .queryParam("deniedEntityType", queryParam) + .when() + .get(DropListRoutes.DROP_LIST + SEPARATOR + pathParam) + .then() + .statusCode(HttpStatus.OK_200) + .contentType(ContentType.JSON) + .body(".", containsInAnyOrder(expected)); + } + + @Test + void shouldHandleInvalidDeniedEntityType() { + given() + .queryParam("deniedEntityType", "unknown") + .when() + .get(DropListRoutes.DROP_LIST + SEPARATOR + "global") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .contentType(ContentType.JSON) + .body("statusCode", is(HttpStatus.BAD_REQUEST_400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("Invalid DeniedEntityType")) + .body("details", is("DeniedEntityType 'unknown' is invalid. Supported values are [ADDRESS, DOMAIN]")); + } + + @ParameterizedTest(name = "{index} OwnerScope: {0}, Owner: {1}, DeniedEntity: {2}") + @CsvSource(value = { + "global, , devil.com", + "global, , bad_...@crime.com", + "domain, owner.com, devil.com", + "domain, owner.com, bad_...@crime.com", + "user, ow...@owner.com, devil.com", + "user, ow...@owner.com, bad_...@crime.com"}) + void shouldAddDropListEntry(String ownerScope, String owner, String newDeniedEntity) { + when() + .put(DropListRoutes.DROP_LIST + SEPARATOR + ownerScope + SEPARATOR + owner + SEPARATOR + newDeniedEntity) + .then() + .statusCode(HttpStatus.NO_CONTENT_204); + + assertThat(getResultDropList(ownerScope, owner)).contains(newDeniedEntity); + } + + @ParameterizedTest(name = "{index} OwnerScope: {0}, Owner: {1}, DeniedEntity: {2}") + @CsvSource(value = { + "global, , devil..com, Invalid domain devil..com", + "global, , bad_guy@@crime.com, Invalid mail address bad_guy@@crime.com", + "domain, owner.com, devil..com, Invalid domain devil..com", + "domain, owner.com, bad_guy@@crime.com, Invalid mail address bad_guy@@crime.com", + "user, ow...@owner.com, devil..com, Invalid domain devil..com", + "user, ow...@owner.com, bad_guy@@crime.com, Invalid mail address bad_guy@@crime.com"}) + void shouldFailWhenAddInvalidDeniedEntity(String ownerScope, String owner, String newDeniedEntity, String message) { + when() + .put(DropListRoutes.DROP_LIST + SEPARATOR + ownerScope + SEPARATOR + owner + SEPARATOR + newDeniedEntity) + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .contentType(ContentType.JSON) + .body("statusCode", is(HttpStatus.BAD_REQUEST_400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is(message)); + } + + @ParameterizedTest(name = "{index} Path: {0}") + @CsvSource(value = { + "/global/evil.com", + "/global/attac...@evil.com", + "/domain/owner.com/evil.com", + "/domain/owner.com/attac...@evil.com", + "/user/ow...@owner.com/evil.com", + "/user/ow...@owner.com/attac...@evil.com"}) + void headShouldReturnNoContentWhenDomainDeniedEntityExists(String path) { + when() + .head(DropListRoutes.DROP_LIST + path) + .then() + .statusCode(HttpStatus.NO_CONTENT_204); + } + + @ParameterizedTest(name = "{index} Path: {0}") + @CsvSource(value = { + "global/devil.com", + "global/bad_...@crime.com", + "/domain/owner.com/devil.com", + "/domain/owner.com/bad_...@crime.com", + "/user/ow...@owner.com/devil.com", + "/user/ow...@owner.com/bad_...@crime.com"}) + void headShouldReturnNotFoundWhenDomainDeniedEntityNotExists(String path) { + when() + .head(DropListRoutes.DROP_LIST + path) + .then() + .statusCode(HttpStatus.NOT_FOUND_404); + } + + @ParameterizedTest(name = "{index} Path: {3}") + @CsvSource(value = { + "global, , evil.com, /global/evil.com", + "global, , attac...@evil.com, /global/attac...@evil.com", + "domain, owner.com, evil.com, /domain/owner.com/evil.com", + "domain, owner.com, attac...@evil.com, /domain/owner.com/attac...@evil.com", + "user, ow...@owner.com, evil.com, /user/ow...@owner.com/evil.com", + "user, ow...@owner.com, attac...@evil.com, /user/ow...@owner.com/attac...@evil.com"}) + void deleteShouldReturnNoContent(String ownerScope, String owner, String deniedEntity, String path) { + given() + .delete(DropListRoutes.DROP_LIST + path) + .then() + .statusCode(HttpStatus.NO_CONTENT_204); + + assertThat(getResultDropList(ownerScope, owner)).doesNotContain(deniedEntity); + } + + @Test + void deleteShouldReturnNotFoundWhenUsedWithEmptyEntry() { + given() + .delete(SEPARATOR) + .then() + .statusCode(HttpStatus.NOT_FOUND_404); + } + + static Stream<DropListEntry> getDropListTestEntries() throws AddressException { + return Stream.of( + DropListEntry.builder() + .forAll() + .denyAddress(new MailAddress(DENIED_SENDER)) + .build(), + DropListEntry.builder() + .forAll() + .denyDomain(new MailAddress(DENIED_SENDER).getDomain()) + .build(), + DropListEntry.builder() + .forAll() + .denyAddress(new MailAddress(DENIED_SENDER)) + .build(), + DropListEntry.builder() + .domainOwner(new MailAddress(OWNER_RECIPIENT).getDomain()) + .denyAddress(new MailAddress(DENIED_SENDER)) + .build(), + DropListEntry.builder() + .domainOwner(new MailAddress(OWNER_RECIPIENT).getDomain()) + .denyDomain(new MailAddress(DENIED_SENDER).getDomain()) + .build(), + DropListEntry.builder() + .userOwner(new MailAddress(OWNER_RECIPIENT)) + .denyAddress(new MailAddress(DENIED_SENDER)) + .build(), + DropListEntry.builder() + .userOwner(new MailAddress(OWNER_RECIPIENT)) + .denyDomain(new MailAddress(DENIED_SENDER).getDomain()) + .build()); + } + + private List<String> getResultDropList(String ownerScope, String owner) { + return dropList.list(OwnerScope.valueOf(ownerScope.toUpperCase()), + Optional.ofNullable(owner).orElse("")) + .map(DropListEntry::getDeniedEntity) + .collectList() + .block(); + } +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org