You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2019/01/11 03:01:19 UTC
[09/10] james-project git commit: JAMES-2637 add PUT route for
aliases routes
JAMES-2637 add PUT route for aliases routes
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/de15eaa6
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/de15eaa6
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/de15eaa6
Branch: refs/heads/master
Commit: de15eaa6c4178b39d2405c2c944651456ea8b03a
Parents: 14e264c
Author: Rene Cordier <rc...@linagora.com>
Authored: Tue Jan 8 16:28:14 2019 +0700
Committer: Benoit Tellier <bt...@linagora.com>
Committed: Fri Jan 11 09:48:34 2019 +0700
----------------------------------------------------------------------
.../james/modules/server/DataRoutesModules.java | 2 +
.../integration/UnauthorizedEndpointsTest.java | 2 +
.../WebAdminServerIntegrationTest.java | 1 +
.../james/webadmin/routes/AliasRoutes.java | 139 ++++++++
.../webadmin/routes/MailAddressParser.java | 60 ++++
.../james/webadmin/routes/AliasRoutesTest.java | 348 +++++++++++++++++++
src/site/markdown/server/manage-webadmin.md | 31 ++
7 files changed, 583 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/james-project/blob/de15eaa6/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java
----------------------------------------------------------------------
diff --git a/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java b/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java
index ef695fa..a2726af 100644
--- a/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java
+++ b/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java
@@ -20,6 +20,7 @@
package org.apache.james.modules.server;
import org.apache.james.webadmin.Routes;
+import org.apache.james.webadmin.routes.AliasRoutes;
import org.apache.james.webadmin.routes.DomainMappingsRoutes;
import org.apache.james.webadmin.routes.DomainsRoutes;
import org.apache.james.webadmin.routes.ForwardRoutes;
@@ -34,6 +35,7 @@ public class DataRoutesModules extends AbstractModule {
@Override
protected void configure() {
Multibinder<Routes> routesMultibinder = Multibinder.newSetBinder(binder(), Routes.class);
+ routesMultibinder.addBinding().to(AliasRoutes.class);
routesMultibinder.addBinding().to(DomainsRoutes.class);
routesMultibinder.addBinding().to(DomainMappingsRoutes.class);
routesMultibinder.addBinding().to(ForwardRoutes.class);
http://git-wip-us.apache.org/repos/asf/james-project/blob/de15eaa6/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/UnauthorizedEndpointsTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/UnauthorizedEndpointsTest.java b/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/UnauthorizedEndpointsTest.java
index dc2140f..f20b041 100644
--- a/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/UnauthorizedEndpointsTest.java
+++ b/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/UnauthorizedEndpointsTest.java
@@ -24,6 +24,7 @@ import static io.restassured.RestAssured.when;
import org.apache.james.GuiceJamesServer;
import org.apache.james.utils.WebAdminGuiceProbe;
import org.apache.james.webadmin.WebAdminUtils;
+import org.apache.james.webadmin.routes.AliasRoutes;
import org.apache.james.webadmin.routes.CassandraMigrationRoutes;
import org.apache.james.webadmin.routes.DLPConfigurationRoutes;
import org.apache.james.webadmin.routes.DomainMappingsRoutes;
@@ -127,6 +128,7 @@ class UnauthorizedEndpointsTest {
UserQuotaRoutes.USERS_QUOTA_ENDPOINT + "/joe@perdu.com/size",
UserRoutes.USERS + "/user@james.org",
ForwardRoutes.ROOT_PATH + "/alice@james.org/bob@james.org",
+ AliasRoutes.ROOT_PATH + "/bob@james.org/sources/bob-alias@james.org",
GlobalQuotaRoutes.QUOTA_ENDPOINT + "/count",
GlobalQuotaRoutes.QUOTA_ENDPOINT + "/size",
GlobalQuotaRoutes.QUOTA_ENDPOINT,
http://git-wip-us.apache.org/repos/asf/james-project/blob/de15eaa6/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java b/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java
index 61e690b..23c0e2e 100644
--- a/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java
@@ -326,6 +326,7 @@ public class WebAdminServerIntegrationTest {
.body(containsString("\"tags\":[\"MailRepositories\"]"))
.body(containsString("\"tags\":[\"MailQueues\"]"))
.body(containsString("\"tags\":[\"Address Forwards\"]"))
+ .body(containsString("\"tags\":[\"Address Aliases\"]"))
.body(containsString("\"tags\":[\"Address Groups\"]"))
.body(containsString("{\"name\":\"ReIndexing (mailboxes)\"}"))
.body(containsString("{\"name\":\"MessageIdReIndexing\"}"));
http://git-wip-us.apache.org/repos/asf/james-project/blob/de15eaa6/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/AliasRoutes.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/AliasRoutes.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/AliasRoutes.java
new file mode 100644
index 0000000..583858d
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/AliasRoutes.java
@@ -0,0 +1,139 @@
+/****************************************************************
+ * 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 static spark.Spark.halt;
+
+import javax.inject.Inject;
+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.User;
+import org.apache.james.rrt.api.MappingAlreadyExistsException;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableException;
+import org.apache.james.rrt.lib.MappingSource;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.api.UsersRepositoryException;
+import org.apache.james.webadmin.Constants;
+import org.apache.james.webadmin.Routes;
+import org.apache.james.webadmin.utils.ErrorResponder;
+import org.eclipse.jetty.http.HttpStatus;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import spark.HaltException;
+import spark.Request;
+import spark.Response;
+import spark.Service;
+
+@Api(tags = "Address Aliases")
+@Path(AliasRoutes.ROOT_PATH)
+@Produces(Constants.JSON_CONTENT_TYPE)
+public class AliasRoutes implements Routes {
+
+ public static final String ROOT_PATH = "address/aliases";
+
+ private static final String ALIAS_DESTINATION_ADDRESS = "aliasDestinationAddress";
+ private static final String ALIAS_ADDRESS_PATH = ROOT_PATH + SEPARATOR + ":" + ALIAS_DESTINATION_ADDRESS;
+ private static final String ALIAS_SOURCE_ADDRESS = "aliasSourceAddress";
+ private static final String USER_IN_ALIAS_SOURCES_ADDRESSES_PATH = ALIAS_ADDRESS_PATH + SEPARATOR +
+ "sources" + SEPARATOR + ":" + ALIAS_SOURCE_ADDRESS;
+ private static final String MAILADDRESS_ASCII_DISCLAIMER = "Note that email addresses are restricted to ASCII character set. " +
+ "Mail addresses not matching this criteria will be rejected.";
+ private static final String ADDRESS_TYPE = "alias";
+
+ private final UsersRepository usersRepository;
+ private final RecipientRewriteTable recipientRewriteTable;
+
+ @Inject
+ @VisibleForTesting
+ AliasRoutes(RecipientRewriteTable recipientRewriteTable, UsersRepository usersRepository) {
+ this.usersRepository = usersRepository;
+ this.recipientRewriteTable = recipientRewriteTable;
+ }
+
+ @Override
+ public String getBasePath() {
+ return ROOT_PATH;
+ }
+
+ @Override
+ public void define(Service service) {
+ service.put(USER_IN_ALIAS_SOURCES_ADDRESSES_PATH, this::addToAliasSources);
+ }
+
+ @PUT
+ @Path(ROOT_PATH + "/{" + ALIAS_DESTINATION_ADDRESS + "}/sources/{" + ALIAS_SOURCE_ADDRESS + "}")
+ @ApiOperation(value = "adding a source address into an alias")
+ @ApiImplicitParams({
+ @ApiImplicitParam(required = true, dataType = "string", name = ALIAS_DESTINATION_ADDRESS, paramType = "path",
+ value = "Destination mail address of the alias. Sending a mail to the alias source address will send it to " +
+ "that email address.\n" +
+ MAILADDRESS_ASCII_DISCLAIMER),
+ @ApiImplicitParam(required = true, dataType = "string", name = ALIAS_SOURCE_ADDRESS, paramType = "path",
+ value = "Source mail address of the alias. Sending a mail to that address will send it to " +
+ "the email destination address.\n" +
+ MAILADDRESS_ASCII_DISCLAIMER)
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = HttpStatus.NO_CONTENT_204, message = "OK"),
+ @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = ALIAS_DESTINATION_ADDRESS + " or alias structure format is not valid"),
+ @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "The alias source exists as an user already"),
+ @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500,
+ message = "Internal server error - Something went bad on the server side.")
+ })
+ public HaltException addToAliasSources(Request request, Response response) throws UsersRepositoryException, RecipientRewriteTableException {
+ MailAddress aliasSourceAddress = MailAddressParser.parseMailAddress(request.params(ALIAS_SOURCE_ADDRESS), ADDRESS_TYPE);
+ ensureUserDoesNotExist(aliasSourceAddress);
+ MailAddress destinationAddress = MailAddressParser.parseMailAddress(request.params(ALIAS_DESTINATION_ADDRESS), ADDRESS_TYPE);
+ MappingSource source = MappingSource.fromUser(User.fromMailAddress(destinationAddress));
+ addAlias(source, aliasSourceAddress);
+ return halt(HttpStatus.NO_CONTENT_204);
+ }
+
+ private void addAlias(MappingSource source, MailAddress aliasSourceAddress) throws RecipientRewriteTableException {
+ try {
+ recipientRewriteTable.addAliasMapping(source, aliasSourceAddress.asString());
+ } catch (MappingAlreadyExistsException e) {
+ // ignore
+ }
+ }
+
+ private void ensureUserDoesNotExist(MailAddress mailAddress) throws UsersRepositoryException {
+ String username = usersRepository.getUser(mailAddress);
+
+ if (usersRepository.contains(username)) {
+ throw ErrorResponder.builder()
+ .statusCode(HttpStatus.BAD_REQUEST_400)
+ .type(ErrorResponder.ErrorType.INVALID_ARGUMENT)
+ .message("The alias source exists as an user already")
+ .haltError();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/james-project/blob/de15eaa6/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/MailAddressParser.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/MailAddressParser.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/MailAddressParser.java
new file mode 100644
index 0000000..ff363a0
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/MailAddressParser.java
@@ -0,0 +1,60 @@
+/****************************************************************
+ * 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 java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import javax.mail.internet.AddressException;
+
+import org.apache.james.core.MailAddress;
+import org.apache.james.webadmin.utils.ErrorResponder;
+import org.eclipse.jetty.http.HttpStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class MailAddressParser {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(MailAddressParser.class);
+
+ static MailAddress parseMailAddress(String address, String addressType) {
+ try {
+ String decodedAddress = URLDecoder.decode(address, StandardCharsets.UTF_8.displayName());
+ return new MailAddress(decodedAddress);
+ } catch (AddressException e) {
+ LOGGER.error("The " + addressType + " " + address + " is not an email address");
+ throw ErrorResponder.builder()
+ .statusCode(HttpStatus.BAD_REQUEST_400)
+ .type(ErrorResponder.ErrorType.INVALID_ARGUMENT)
+ .message("The " + addressType + " is not an email address")
+ .cause(e)
+ .haltError();
+ } catch (UnsupportedEncodingException e) {
+ LOGGER.error("UTF-8 should be a valid encoding");
+ throw ErrorResponder.builder()
+ .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500)
+ .type(ErrorResponder.ErrorType.SERVER_ERROR)
+ .message("Internal server error - Something went bad on the server side.")
+ .cause(e)
+ .haltError();
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/james-project/blob/de15eaa6/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/AliasRoutesTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/AliasRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/AliasRoutesTest.java
new file mode 100644
index 0000000..9eb0744
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/AliasRoutesTest.java
@@ -0,0 +1,348 @@
+/****************************************************************
+ * 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 io.restassured.RestAssured;
+import io.restassured.filter.log.LogDetail;
+import io.restassured.http.ContentType;
+import org.apache.commons.configuration.DefaultConfigurationBuilder;
+import org.apache.james.core.Domain;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.metrics.logger.DefaultMetricFactory;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableException;
+import org.apache.james.rrt.lib.Mapping;
+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.memory.MemoryUsersRepository;
+import org.apache.james.webadmin.WebAdminServer;
+import org.apache.james.webadmin.WebAdminUtils;
+import org.eclipse.jetty.http.HttpStatus;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.Map;
+
+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.apache.james.webadmin.WebAdminServer.NO_CONFIGURATION;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+
+class AliasRoutesTest {
+
+ private static final Domain DOMAIN = Domain.of("b.com");
+ public static final String BOB = "bob@" + DOMAIN.name();
+ public static final String BOB_WITH_SLASH = "bob/@" + DOMAIN.name();
+ public static final String BOB_WITH_ENCODED_SLASH = "bob%2F@" + DOMAIN.name();
+ public static final String BOB_ALIAS = "bob-alias@" + DOMAIN.name();
+ public static final String BOB_ALIAS_2 = "bob-alias2@" + DOMAIN.name();
+ public static final String BOB_ALIAS_WITH_SLASH = "bob-alias/@" + DOMAIN.name();
+ public static final String BOB_ALIAS_WITH_ENCODED_SLASH = "bob-alias%2F@" + DOMAIN.name();
+ public static final String ALICE = "alice@" + DOMAIN.name();
+ public static final String BOB_PASSWORD = "123456";
+ public static final String BOB_WITH_SLASH_PASSWORD = "abcdef";
+ public static final String ALICE_PASSWORD = "789123";
+
+ private static final MappingSource BOB_SOURCE = MappingSource.fromUser("bob", DOMAIN);
+ private static final MappingSource BOB_WITH_ENCODED_SLASH_SOURCE = MappingSource.fromUser("bob/", DOMAIN);
+ private static final Mapping BOB_MAPPING = Mapping.alias(BOB_ALIAS);
+
+ private WebAdminServer webAdminServer;
+
+ private void createServer(AliasRoutes aliasRoutes) throws Exception {
+ webAdminServer = WebAdminUtils.createWebAdminServer(
+ new DefaultMetricFactory(),
+ aliasRoutes);
+ webAdminServer.configure(NO_CONFIGURATION);
+ webAdminServer.await();
+
+ RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminServer)
+ .setBasePath("address/aliases")
+ .log(LogDetail.METHOD)
+ .build();
+ }
+
+ @AfterEach
+ void stop() {
+ webAdminServer.destroy();
+ }
+
+ @Nested
+ class NormalBehaviour {
+
+ MemoryUsersRepository usersRepository;
+ MemoryDomainList domainList;
+ MemoryRecipientRewriteTable memoryRecipientRewriteTable;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ memoryRecipientRewriteTable = new MemoryRecipientRewriteTable();
+ DNSService dnsService = mock(DNSService.class);
+ domainList = new MemoryDomainList(dnsService);
+ domainList.configure(DomainListConfiguration.builder()
+ .autoDetect(false)
+ .autoDetectIp(false));
+ domainList.addDomain(DOMAIN);
+
+ usersRepository = MemoryUsersRepository.withVirtualHosting();
+ usersRepository.setDomainList(domainList);
+ usersRepository.configure(new DefaultConfigurationBuilder());
+
+ usersRepository.addUser(BOB, BOB_PASSWORD);
+ usersRepository.addUser(BOB_WITH_SLASH, BOB_WITH_SLASH_PASSWORD);
+ usersRepository.addUser(ALICE, ALICE_PASSWORD);
+
+ createServer(new AliasRoutes(memoryRecipientRewriteTable, usersRepository));
+ }
+
+ @Test
+ void putAliasForUserShouldReturnNoContent() {
+ when()
+ .put(BOB + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS)
+ .then()
+ .statusCode(HttpStatus.NO_CONTENT_204);
+ }
+
+ @Test
+ void putAliasShouldBeIdempotent() {
+ given()
+ .put(BOB + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS);
+
+ when()
+ .put(BOB + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS)
+ .then()
+ .statusCode(HttpStatus.NO_CONTENT_204);
+ }
+
+ @Test
+ void putAliasWithSlashForUserShouldReturnNoContent() {
+ when()
+ .put(BOB + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS_WITH_ENCODED_SLASH)
+ .then()
+ .statusCode(HttpStatus.NO_CONTENT_204);
+ }
+
+ @Test
+ void putUserForAliasWithEncodedSlashShouldReturnNoContent() {
+ when()
+ .put(BOB_WITH_ENCODED_SLASH + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS)
+ .then()
+ .statusCode(HttpStatus.NO_CONTENT_204);
+ }
+
+ @Test
+ void putExistingUserAsAliasSourceShouldNotBePossible() {
+ Map<String, Object> errors = when()
+ .put(BOB + SEPARATOR + "sources" + SEPARATOR + ALICE)
+ .then()
+ .statusCode(HttpStatus.BAD_REQUEST_400)
+ .contentType(ContentType.JSON)
+ .extract()
+ .body()
+ .jsonPath()
+ .getMap(".");
+
+ assertThat(errors)
+ .containsEntry("statusCode", HttpStatus.BAD_REQUEST_400)
+ .containsEntry("type", "InvalidArgument")
+ .containsEntry("message", "The alias source exists as an user already");
+ }
+
+ @Test
+ void putAliasForUserShouldCreateAlias() {
+ with()
+ .put(BOB + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS);
+
+ assertThat(memoryRecipientRewriteTable.getStoredMappings(BOB_SOURCE)).containsOnly(BOB_MAPPING);
+ }
+
+ @Test
+ void putAliasWithEncodedSlashForUserShouldAddItAsADestination() {
+ Mapping mapping = Mapping.alias(BOB_ALIAS_WITH_SLASH);
+
+ with()
+ .put(BOB + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS_WITH_ENCODED_SLASH);
+
+ assertThat(memoryRecipientRewriteTable.getStoredMappings(BOB_SOURCE)).containsOnly(mapping);
+ }
+
+ @Test
+ void putAliasForUserWithEncodedSlashShouldCreateForward() {
+ with()
+ .put(BOB_WITH_ENCODED_SLASH + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS);
+
+ assertThat(memoryRecipientRewriteTable.getStoredMappings(BOB_WITH_ENCODED_SLASH_SOURCE)).containsOnly(BOB_MAPPING);
+ }
+
+ @Test
+ void putSameAliasForUserTwiceShouldBeIdempotent() {
+ with()
+ .put(BOB + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS);
+
+ with()
+ .put(BOB + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS);
+
+ assertThat(memoryRecipientRewriteTable.getStoredMappings(BOB_SOURCE)).containsOnly(BOB_MAPPING);
+ }
+
+ @Test
+ void putAliasForUserShouldAllowSeveralSources() {
+ Mapping mapping2 = Mapping.alias(BOB_ALIAS_2);
+
+ with()
+ .put(BOB + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS);
+
+ with()
+ .put(BOB + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS_2);
+
+ assertThat(memoryRecipientRewriteTable.getStoredMappings(BOB_SOURCE)).containsOnly(BOB_MAPPING, mapping2);
+ }
+ }
+
+ @Nested
+ class FilteringOtherRewriteRuleTypes extends NormalBehaviour {
+
+ @BeforeEach
+ void setup() throws Exception {
+ super.setUp();
+ memoryRecipientRewriteTable.addErrorMapping(MappingSource.fromUser("error", DOMAIN), "disabled");
+ memoryRecipientRewriteTable.addRegexMapping(MappingSource.fromUser("regex", DOMAIN), ".*@b\\.com");
+ memoryRecipientRewriteTable.addAliasDomainMapping(MappingSource.fromDomain(Domain.of("alias")), DOMAIN);
+ }
+
+ }
+
+ @Nested
+ class ExceptionHandling {
+
+ private RecipientRewriteTable memoryRecipientRewriteTable;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ memoryRecipientRewriteTable = mock(RecipientRewriteTable.class);
+ UsersRepository userRepository = mock(UsersRepository.class);
+ DomainList domainList = mock(DomainList.class);
+ Mockito.when(domainList.containsDomain(any())).thenReturn(true);
+ createServer(new AliasRoutes(memoryRecipientRewriteTable, userRepository));
+ }
+
+ @Test
+ void putMalformedUserDestinationShouldReturnBadRequest() {
+ Map<String, Object> errors = when()
+ .put("not-an-address" + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS)
+ .then()
+ .statusCode(HttpStatus.BAD_REQUEST_400)
+ .contentType(ContentType.JSON)
+ .extract()
+ .body()
+ .jsonPath()
+ .getMap(".");
+
+ assertThat(errors)
+ .containsEntry("statusCode", HttpStatus.BAD_REQUEST_400)
+ .containsEntry("type", "InvalidArgument")
+ .containsEntry("message", "The alias is not an email address")
+ .containsEntry("details", "Out of data at position 1 in 'not-an-address'");
+ }
+
+ @Test
+ void putMalformedAliasSourceShouldReturnBadRequest() {
+ Map<String, Object> errors = when()
+ .put(BOB + SEPARATOR + "sources" + SEPARATOR + "not-an-address")
+ .then()
+ .statusCode(HttpStatus.BAD_REQUEST_400)
+ .contentType(ContentType.JSON)
+ .extract()
+ .body()
+ .jsonPath()
+ .getMap(".");
+
+ assertThat(errors)
+ .containsEntry("statusCode", HttpStatus.BAD_REQUEST_400)
+ .containsEntry("type", "InvalidArgument")
+ .containsEntry("message", "The alias is not an email address")
+ .containsEntry("details", "Out of data at position 1 in 'not-an-address'");
+ }
+
+ @Test
+ void putUserDestinationInForwardWithSlashShouldReturnNotFound() {
+ when()
+ .put(BOB_WITH_SLASH + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS)
+ .then()
+ .statusCode(HttpStatus.NOT_FOUND_404);
+ }
+
+ @Test
+ void putAliasSourceWithSlashShouldReturnNotFound() {
+ when()
+ .put(BOB + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS_WITH_SLASH)
+ .then()
+ .statusCode(HttpStatus.NOT_FOUND_404);
+ }
+
+ @Test
+ void putRequiresTwoPathParams() {
+ when()
+ .put(BOB)
+ .then()
+ .statusCode(HttpStatus.NOT_FOUND_404);
+ }
+
+ @Test
+ void putShouldReturnErrorWhenRecipientRewriteTableExceptionIsThrown() throws Exception {
+ doThrow(RecipientRewriteTableException.class)
+ .when(memoryRecipientRewriteTable)
+ .addAliasMapping(any(), anyString());
+
+ when()
+ .put(BOB + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS)
+ .then()
+ .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500);
+ }
+
+ @Test
+ void putShouldReturnErrorWhenRuntimeExceptionIsThrown() throws Exception {
+ doThrow(RuntimeException.class)
+ .when(memoryRecipientRewriteTable)
+ .addAliasMapping(any(), anyString());
+
+ when()
+ .put(BOB + SEPARATOR + "sources" + SEPARATOR + BOB_ALIAS)
+ .then()
+ .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/james-project/blob/de15eaa6/src/site/markdown/server/manage-webadmin.md
----------------------------------------------------------------------
diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md
index 2ba0255..c0a7b8b 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -37,6 +37,7 @@ as exposed above). To avoid information duplication, this is ommited on endpoint
- [Correcting ghost mailbox](#Correcting_ghost_mailbox)
- [Creating address group](#Creating_address_group)
- [Creating address forwards](#Creating_address_forwards)
+ - [Creating address aliases](#Creating_address_aliases)
- [Administrating mail repositories](#Administrating_mail_repositories)
- [Administrating mail queues](#Administrating_mail_queues)
- [Administrating DLP Configuration](#Administrating_dlp_configuration)
@@ -1299,6 +1300,36 @@ Response codes:
- 204: Success
- 400: Forward structure or member is not valid
+## Creating address aliases
+
+You can use **webadmin** to define aliases for an user.
+
+When a specific email is sent to the alias address, the destination address of the alias will receive it.
+
+Aliases can be defined for existing users.
+
+This feature uses [Recipients rewrite table](/server/config-recipientrewritetable.html) and requires
+the [RecipientRewriteTable mailet](https://github.com/apache/james-project/blob/master/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/RecipientRewriteTable.java)
+to be configured.
+
+Note that email addresses are restricted to ASCII character set. Mail addresses not matching this criteria will be rejected.
+
+ - [Adding a new alias to an user](#Adding_a_new_alias_to_an_user)
+
+### Adding a new alias to an user
+
+```
+curl -XPUT http://ip:port/address/aliases/user@domain.com/sources/alias@domain.com
+```
+
+Will add alias@domain.com to user@domain.com, creating the alias if needed
+
+Response codes:
+
+ - 204: OK
+ - 400: Alias structure or member is not valid
+ - 400: The alias source exists as an user already
+
## Administrating mail repositories
- [Create a mail repository](#Create_a_mail_repository)
---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org