This is an automated email from the ASF dual-hosted git repository. rcordier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit c845dfe8bd2f7e1447386f4f36a8412b2d484a2f Author: Gautier DI FOLCO <[email protected]> AuthorDate: Thu Feb 13 12:57:46 2020 +0100 JAMES-3066 Add allowed From headers list route in WebAdmin --- .../apache/james/webadmin/routes/UserRoutes.java | 62 +++++++++++++- .../apache/james/webadmin/service/UserService.java | 13 +++ .../james/webadmin/routes/UserRoutesTest.java | 96 +++++++++++++++++++++- src/site/markdown/server/manage-webadmin.md | 19 +++++ 4 files changed, 186 insertions(+), 4 deletions(-) diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/UserRoutes.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/UserRoutes.java index 6d3b641..d0b78ee 100644 --- a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/UserRoutes.java +++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/UserRoutes.java @@ -22,6 +22,8 @@ package org.apache.james.webadmin.routes; import static org.apache.james.webadmin.Constants.SEPARATOR; import static spark.Spark.halt; +import java.util.List; + import javax.inject.Inject; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -29,7 +31,11 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.Produces; +import org.apache.james.core.MailAddress; import org.apache.james.core.Username; +import org.apache.james.rrt.api.CanSendFrom; +import org.apache.james.rrt.api.RecipientRewriteTable; +import org.apache.james.rrt.api.RecipientRewriteTableException; import org.apache.james.user.api.InvalidUsernameException; import org.apache.james.user.api.UsersRepositoryException; import org.apache.james.webadmin.Routes; @@ -46,6 +52,8 @@ import org.eclipse.jetty.http.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.github.steveash.guavate.Guavate; + import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; @@ -69,14 +77,16 @@ public class UserRoutes implements Routes { private final UserService userService; private final JsonTransformer jsonTransformer; + private final CanSendFrom canSendFrom; private final JsonExtractor<AddUserRequest> jsonExtractor; private Service service; @Inject - public UserRoutes(UserService userService, JsonTransformer jsonTransformer) { + public UserRoutes(UserService userService, CanSendFrom canSendFrom, JsonTransformer jsonTransformer) { this.userService = userService; this.jsonTransformer = jsonTransformer; + this.canSendFrom = canSendFrom; this.jsonExtractor = new JsonExtractor<>(AddUserRequest.class); } @@ -94,6 +104,8 @@ public class UserRoutes implements Routes { defineCreateUser(); defineDeleteUser(); + + defineAllowedFromHeaders(); } @DELETE @@ -142,6 +154,25 @@ public class UserRoutes implements Routes { jsonTransformer); } + @GET + @Path("/{username}/allowedFromHeaders") + @ApiOperation(value = "List all possible From header value for an existing user") + @ApiImplicitParams({ + @ApiImplicitParam(required = true, dataType = "string", name = "username", paramType = "path") + }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.NO_CONTENT_204, message = "OK.", response = List.class), + @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "user is not valid."), + @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "user does not exist."), + @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, + message = "Internal server error - Something went bad on the server side.") + }) + public void defineAllowedFromHeaders() { + service.get(USERS + SEPARATOR + USER_NAME + SEPARATOR + "allowedFromHeaders", + this::allowedFromHeaders, + jsonTransformer); + } + private String removeUser(Request request, Response response) { Username username = extractUsername(request); try { @@ -184,6 +215,35 @@ public class UserRoutes implements Routes { } } + private List<String> allowedFromHeaders(Request request, Response response) { + Username username = extractUsername(request); + + try { + if (!userService.existUser(username)) { + LOGGER.info("allowed From headers on an unknown user: '{}", username.asString()); + throw ErrorResponder.builder() + .statusCode(HttpStatus.NOT_FOUND_404) + .type(ErrorType.INVALID_ARGUMENT) + .message("user '" + username.asString() + "' does not exist") + .haltError(); + } + + return canSendFrom + .allValidFromAddressesForUser(username) + .map(MailAddress::asString) + .collect(Guavate.toImmutableList()); + } catch (RecipientRewriteTable.ErrorMappingException | RecipientRewriteTableException | UsersRepositoryException e) { + String errorMessage = String.format("Error while listing allowed From headers for user '%s'", username); + LOGGER.info(errorMessage, e); + throw ErrorResponder.builder() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500) + .type(ErrorType.SERVER_ERROR) + .message(errorMessage) + .cause(e) + .haltError(); + } + } + private Username extractUsername(Request request) { return Username.of(request.params(USER_NAME)); } diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UserService.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UserService.java index 78501da..cd36afb 100644 --- a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UserService.java +++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UserService.java @@ -24,6 +24,7 @@ import java.util.Optional; import java.util.stream.Stream; import javax.inject.Inject; +import javax.mail.internet.AddressException; import org.apache.james.core.Username; import org.apache.james.user.api.UsersRepository; @@ -31,11 +32,15 @@ import org.apache.james.user.api.UsersRepositoryException; import org.apache.james.user.api.model.User; import org.apache.james.util.streams.Iterators; import org.apache.james.webadmin.dto.UserResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.github.steveash.guavate.Guavate; public class UserService { + private static final Logger LOGGER = LoggerFactory.getLogger(UserService.class); + private final UsersRepository usersRepository; @Inject @@ -61,6 +66,14 @@ public class UserService { upsert(user, username, password); } + public boolean existUser(Username username) throws UsersRepositoryException { + try { + return usersRepository.contains(usersRepository.getUser(username.asMailAddress())); + } catch (AddressException e) { + LOGGER.info("Unable to parse address '%s'", username.asString(), e); + return false; + } + } private void upsert(User user, Username username, char[] password) throws UsersRepositoryException { if (user == null) { diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UserRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UserRoutesTest.java index 89112c7..db1dba9 100644 --- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UserRoutesTest.java +++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UserRoutesTest.java @@ -22,6 +22,7 @@ package org.apache.james.webadmin.routes; import static io.restassured.RestAssured.given; import static io.restassured.RestAssured.when; import static io.restassured.RestAssured.with; +import static org.apache.james.webadmin.Constants.SEPARATOR; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; @@ -39,6 +40,12 @@ import org.apache.james.core.Domain; import org.apache.james.core.Username; import org.apache.james.domainlist.api.DomainListException; import org.apache.james.domainlist.api.mock.SimpleDomainList; +import org.apache.james.rrt.api.CanSendFrom; +import org.apache.james.rrt.api.RecipientRewriteTable; +import org.apache.james.rrt.api.RecipientRewriteTableException; +import org.apache.james.rrt.lib.CanSendFromImpl; +import org.apache.james.rrt.lib.MappingSource; +import org.apache.james.rrt.memory.MemoryRecipientRewriteTable; import org.apache.james.user.api.UsersRepository; import org.apache.james.user.api.UsersRepositoryException; import org.apache.james.user.api.model.User; @@ -90,12 +97,17 @@ class UserRoutesTest { final MemoryUsersRepository usersRepository; final SimpleDomainList domainList; + final MemoryRecipientRewriteTable recipientRewriteTable; + final CanSendFrom canSendFrom; WebAdminServer webAdminServer; UserRoutesExtension(MemoryUsersRepository usersRepository, SimpleDomainList domainList) { this.usersRepository = spy(usersRepository); this.domainList = domainList; + this.recipientRewriteTable = new MemoryRecipientRewriteTable(); + this.recipientRewriteTable.setDomainList(domainList); + this.canSendFrom = new CanSendFromImpl(recipientRewriteTable); } @Override @@ -112,16 +124,25 @@ class UserRoutesTest { public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return parameterContext.getParameter() .getType() - .isAssignableFrom(UsersRepository.class); + .isAssignableFrom(UsersRepository.class) + || parameterContext.getParameter() + .getType() + .isAssignableFrom(RecipientRewriteTable.class); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { - return usersRepository; + if (parameterContext.getParameter().getType().isAssignableFrom(UsersRepository.class)) { + return usersRepository; + } + if (parameterContext.getParameter().getType().isAssignableFrom(RecipientRewriteTable.class)) { + return recipientRewriteTable; + } + throw new RuntimeException("Unknown parameter type: " + parameterContext.getParameter().getType()); } private WebAdminServer startServer(UsersRepository usersRepository) { - WebAdminServer server = WebAdminUtils.createWebAdminServer(new UserRoutes(new UserService(usersRepository), new JsonTransformer())) + WebAdminServer server = WebAdminUtils.createWebAdminServer(new UserRoutes(new UserService(usersRepository), canSendFrom, new JsonTransformer())) .start(); RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(server) @@ -573,6 +594,75 @@ class UserRoutesTest { .statusCode(HttpStatus.NO_CONTENT_204); } + @Test + void allowedFromHeadersShouldHaveUsersMailAddress() { + // Given + with() + .body("{\"password\":\"password\"}") + .put(USERNAME_WITH_DOMAIN.asString()); + + // Then + List<String> allowedFroms = + when() + .get(USERNAME_WITH_DOMAIN.asString() + SEPARATOR + "allowedFromHeaders") + .then() + .statusCode(HttpStatus.OK_200) + .contentType(ContentType.JSON) + .extract() + .body() + .jsonPath() + .getList("."); + + assertThat(allowedFroms).containsExactly(USERNAME_WITH_DOMAIN.asString()); + } + + @Test + void allowedFromHeadersShouldHaveAllMailAddressesWhenAliasAdded(RecipientRewriteTable recipientRewriteTable) throws RecipientRewriteTableException { + // Given + with() + .body("{\"password\":\"password\"}") + .put(USERNAME_WITH_DOMAIN.asString()); + + String aliasAddress = "alias@" + DOMAIN.asString(); + recipientRewriteTable.addAliasMapping(MappingSource.fromUser(Username.of(aliasAddress)), USERNAME_WITH_DOMAIN.asString()); + + // Then + List<String> allowedFroms = + when() + .get(USERNAME_WITH_DOMAIN.asString() + SEPARATOR + "allowedFromHeaders") + .then() + .statusCode(HttpStatus.OK_200) + .contentType(ContentType.JSON) + .extract() + .body() + .jsonPath() + .getList("."); + + assertThat(allowedFroms).containsExactly(USERNAME_WITH_DOMAIN.asString(), aliasAddress); + } + + @Test + void allowedFromHeadersShouldReturn404WhenUserDoesNotExist() { + when() + .get(USERNAME_WITH_DOMAIN.asString() + SEPARATOR + "allowedFromHeaders") + .then() + .statusCode(HttpStatus.NOT_FOUND_404) + .body("statusCode", is(HttpStatus.NOT_FOUND_404)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("user 'username@domain' does not exist")); + } + + @Test + void allowedFromHeadersShouldReturn404WhenUserIsInvalid() { + when() + .get("@@" + SEPARATOR + "allowedFromHeaders") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(HttpStatus.BAD_REQUEST_400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("Invalid arguments supplied in the user request")); + } + @Nested class IllegalCharacterErrorHandlingTest implements UserRoutesContract.IllegalCharactersErrorHandlingContract { diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md index b2e0142..76dccc6 100644 --- a/src/site/markdown/server/manage-webadmin.md +++ b/src/site/markdown/server/manage-webadmin.md @@ -292,6 +292,7 @@ Response codes: - [Updating a user password](#Updating_a_user_password) - [Deleting a domain](#Deleting_a_user) - [Retrieving the user list](#Retrieving_the_user_list) + - [Retrieving the list of allowed `From` headers for a given user](Retrieving_the_list_of_allowed_From_headers_for_a_given_user) ### Create a user @@ -343,6 +344,24 @@ Response codes: - 200: The user name list was successfully retrieved +### Retrieving the list of allowed `From` headers for a given user + +``` +curl -XGET http://ip:port/users/givenUser/allowedFromHeaders +``` + +The answer looks like: + +``` +["[email protected]","[email protected]"] +``` + +Response codes: + + - 200: The list was successfully retrieved + - 400: The user is invalid + - 404: The user is unknown + ## Administrating mailboxes ### All mailboxes --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
