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 2c439f825b16960d86adf18973ccb5f15dbcd21c Author: Benoit Tellier <[email protected]> AuthorDate: Thu Oct 1 10:12:36 2020 +0700 JAMES-3396 WebAdmin should inform about loops --- .../webadmin/routes/AddressMappingRoutes.java | 7 +++++ .../apache/james/webadmin/routes/AliasRoutes.java | 7 +++++ .../james/webadmin/routes/ForwardRoutes.java | 7 +++++ .../apache/james/webadmin/routes/GroupsRoutes.java | 18 ++++++++++-- .../webadmin/routes/AddressMappingRoutesTest.java | 27 ++++++++++++++++++ .../james/webadmin/routes/AliasRoutesTest.java | 33 +++++++++++++++++++--- .../webadmin/routes/DomainMappingsRoutesTest.java | 2 ++ .../james/webadmin/routes/ForwardRoutesTest.java | 23 +++++++++++++++ .../james/webadmin/routes/GroupsRoutesTest.java | 33 +++++++++++++++++++--- .../james/webadmin/routes/MappingRoutesTest.java | 2 ++ 10 files changed, 148 insertions(+), 11 deletions(-) diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/AddressMappingRoutes.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/AddressMappingRoutes.java index 1c0ff7e..4c5ff79 100644 --- a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/AddressMappingRoutes.java +++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/AddressMappingRoutes.java @@ -30,6 +30,7 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import org.apache.james.core.MailAddress; +import org.apache.james.rrt.api.LoopDetectedException; import org.apache.james.rrt.api.MappingAlreadyExistsException; import org.apache.james.rrt.api.RecipientRewriteTable; import org.apache.james.rrt.api.RecipientRewriteTableException; @@ -104,6 +105,12 @@ public class AddressMappingRoutes implements Routes { .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) .message(e.getMessage()) .haltError(); + } catch (LoopDetectedException e) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.CONFLICT_409) + .type(ErrorResponder.ErrorType.WRONG_STATE) + .message(e.getMessage()) + .haltError(); } } 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 index 4bcb247..8852160 100644 --- 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 @@ -37,6 +37,7 @@ import org.apache.james.core.MailAddress; import org.apache.james.core.Username; import org.apache.james.domainlist.api.DomainList; import org.apache.james.domainlist.api.DomainListException; +import org.apache.james.rrt.api.LoopDetectedException; import org.apache.james.rrt.api.MappingAlreadyExistsException; import org.apache.james.rrt.api.RecipientRewriteTable; import org.apache.james.rrt.api.RecipientRewriteTableException; @@ -169,6 +170,12 @@ public class AliasRoutes implements Routes { .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) .message(e.getMessage()) .haltError(); + } catch (LoopDetectedException e) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.CONFLICT_409) + .type(ErrorResponder.ErrorType.WRONG_STATE) + .message(e.getMessage()) + .haltError(); } } diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/ForwardRoutes.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/ForwardRoutes.java index c5cc581..be7304b 100644 --- a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/ForwardRoutes.java +++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/ForwardRoutes.java @@ -33,6 +33,7 @@ import javax.ws.rs.Produces; import org.apache.james.core.MailAddress; import org.apache.james.core.Username; +import org.apache.james.rrt.api.LoopDetectedException; import org.apache.james.rrt.api.MappingAlreadyExistsException; import org.apache.james.rrt.api.RecipientRewriteTable; import org.apache.james.rrt.api.RecipientRewriteTableException; @@ -169,6 +170,12 @@ public class ForwardRoutes implements Routes { .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) .message(e.getMessage()) .haltError(); + } catch (LoopDetectedException e) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.CONFLICT_409) + .type(ErrorResponder.ErrorType.WRONG_STATE) + .message(e.getMessage()) + .haltError(); } } diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/GroupsRoutes.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/GroupsRoutes.java index 1f2eb1c..ed74ad3 100644 --- a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/GroupsRoutes.java +++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/GroupsRoutes.java @@ -35,7 +35,7 @@ import javax.ws.rs.Produces; import org.apache.james.core.Domain; import org.apache.james.core.MailAddress; import org.apache.james.core.Username; -import org.apache.james.domainlist.api.DomainListException; +import org.apache.james.rrt.api.LoopDetectedException; import org.apache.james.rrt.api.MappingAlreadyExistsException; import org.apache.james.rrt.api.RecipientRewriteTable; import org.apache.james.rrt.api.RecipientRewriteTableException; @@ -143,7 +143,7 @@ public class GroupsRoutes implements Routes { @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side.") }) - public HaltException addToGroup(Request request, Response response) throws RecipientRewriteTableException, UsersRepositoryException, DomainListException { + public HaltException addToGroup(Request request, Response response) throws UsersRepositoryException { MailAddress groupAddress = MailAddressParser.parseMailAddress(request.params(GROUP_ADDRESS), GROUP_ADDRESS_TYPE); Domain domain = groupAddress.getDomain(); ensureNotShadowingAnotherAddress(groupAddress); @@ -153,7 +153,7 @@ public class GroupsRoutes implements Routes { return halt(HttpStatus.NO_CONTENT_204); } - private void addGroupMember(MappingSource source, MailAddress userAddress) throws RecipientRewriteTableException { + private void addGroupMember(MappingSource source, MailAddress userAddress) { try { recipientRewriteTable.addGroupMapping(source, userAddress.asString()); } catch (MappingAlreadyExistsException e) { @@ -164,6 +164,18 @@ public class GroupsRoutes implements Routes { .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) .message(e.getMessage()) .haltError(); + } catch (LoopDetectedException e) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.CONFLICT_409) + .type(ErrorResponder.ErrorType.WRONG_STATE) + .message(e.getMessage()) + .haltError(); + } catch (RecipientRewriteTableException e) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500) + .type(ErrorType.SERVER_ERROR) + .message(e.getMessage()) + .haltError(); } } diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/AddressMappingRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/AddressMappingRoutesTest.java index bebf415..6f82abb 100644 --- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/AddressMappingRoutesTest.java +++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/AddressMappingRoutesTest.java @@ -20,15 +20,19 @@ package org.apache.james.webadmin.routes; import static io.restassured.RestAssured.when; +import static io.restassured.RestAssured.with; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import java.util.Map; + import org.apache.james.core.Domain; import org.apache.james.core.MailAddress; import org.apache.james.dnsservice.api.DNSService; import org.apache.james.domainlist.api.DomainList; import org.apache.james.domainlist.api.DomainListException; import org.apache.james.domainlist.memory.MemoryDomainList; +import org.apache.james.rrt.api.RecipientRewriteTableConfiguration; import org.apache.james.rrt.lib.Mapping; import org.apache.james.rrt.lib.MappingSource; import org.apache.james.rrt.memory.MemoryRecipientRewriteTable; @@ -41,6 +45,7 @@ import org.junit.jupiter.api.Test; import io.restassured.RestAssured; import io.restassured.filter.log.LogDetail; +import io.restassured.http.ContentType; class AddressMappingRoutesTest { private static String MAPPING_SOURCE = "[email protected]"; @@ -58,6 +63,7 @@ class AddressMappingRoutesTest { domainList.addDomain(Domain.of("domain.tld")); recipientRewriteTable.setDomainList(domainList); + recipientRewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED); webAdminServer = WebAdminUtils.createWebAdminServer( new AddressMappingRoutes(recipientRewriteTable)) @@ -84,6 +90,27 @@ class AddressMappingRoutesTest { } @Test + void postShouldDetectLoops() { + with() + .post(MAPPING_SOURCE + "/targets/" + ALICE_ADDRESS); + + Map<String, Object> errors = when() + .post(ALICE_ADDRESS + "/targets/" + MAPPING_SOURCE) + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.CONFLICT_409) + .extract() + .body() + .jsonPath() + .getMap("."); + + assertThat(errors) + .containsEntry("statusCode", HttpStatus.CONFLICT_409) + .containsEntry("type", "WrongState") + .containsEntry("message", "Creation of redirection of [email protected] to [email protected] would lead to a loop, operation not performed"); + } + + @Test void addAddressMappingShouldReturnNotFoundWhenOneParameterIsEmpty() { when() .post(MAPPING_SOURCE + "/targets/") 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 index 6128a58..7b7e9e7 100644 --- 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 @@ -41,6 +41,7 @@ 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.rrt.api.RecipientRewriteTableConfiguration; import org.apache.james.rrt.api.RecipientRewriteTableException; import org.apache.james.rrt.lib.MappingSource; import org.apache.james.rrt.memory.MemoryRecipientRewriteTable; @@ -81,8 +82,8 @@ class AliasRoutesTest { private WebAdminServer webAdminServer; - private void createServer(AliasRoutes aliasRoutes) { - webAdminServer = WebAdminUtils.createWebAdminServer(aliasRoutes) + private void createServer(AliasRoutes aliasRoutes, AddressMappingRoutes addressMappingRoutes) { + webAdminServer = WebAdminUtils.createWebAdminServer(aliasRoutes, addressMappingRoutes) .start(); RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminServer) @@ -114,6 +115,7 @@ class AliasRoutesTest { domainList.addDomain(DOMAIN_MAPPING); MappingSourceModule module = new MappingSourceModule(); memoryRecipientRewriteTable.setDomainList(domainList); + memoryRecipientRewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED); usersRepository = MemoryUsersRepository.withVirtualHosting(domainList); @@ -121,7 +123,8 @@ class AliasRoutesTest { usersRepository.addUser(Username.of(BOB_WITH_SLASH), BOB_WITH_SLASH_PASSWORD); usersRepository.addUser(Username.of(ALICE), ALICE_PASSWORD); - createServer(new AliasRoutes(memoryRecipientRewriteTable, usersRepository, domainList, new JsonTransformer(module))); + createServer(new AliasRoutes(memoryRecipientRewriteTable, usersRepository, domainList, new JsonTransformer(module)), + new AddressMappingRoutes(memoryRecipientRewriteTable)); } @Test @@ -156,6 +159,27 @@ class AliasRoutesTest { } @Test + void putShouldDetectConflicts() { + with().basePath(AddressMappingRoutes.BASE_PATH) + .post(ALICE + "/targets/" + ALICE_ALIAS); + + Map<String, Object> errors = when() + .put(ALICE + SEPARATOR + "sources" + SEPARATOR + ALICE_ALIAS) + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.CONFLICT_409) + .extract() + .body() + .jsonPath() + .getMap("."); + + assertThat(errors) + .containsEntry("statusCode", HttpStatus.CONFLICT_409) + .containsEntry("type", "WrongState") + .containsEntry("message", "Creation of redirection of [email protected] to alias:[email protected] would lead to a loop, operation not performed"); + } + + @Test void getNotRegisteredAliasesShouldReturnEmptyList() { when() .get("[email protected]") @@ -423,7 +447,8 @@ class AliasRoutesTest { domainList = mock(DomainList.class); memoryRecipientRewriteTable.setDomainList(domainList); Mockito.when(domainList.containsDomain(any())).thenReturn(true); - createServer(new AliasRoutes(memoryRecipientRewriteTable, userRepository, domainList, new JsonTransformer())); + createServer(new AliasRoutes(memoryRecipientRewriteTable, userRepository, domainList, new JsonTransformer()), + new AddressMappingRoutes(memoryRecipientRewriteTable)); } @Test diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainMappingsRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainMappingsRoutesTest.java index 072c7a0..66bd803 100644 --- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainMappingsRoutesTest.java +++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainMappingsRoutesTest.java @@ -41,6 +41,7 @@ import java.util.function.Function; import org.apache.commons.lang3.StringUtils; import org.apache.james.core.Domain; import org.apache.james.domainlist.api.DomainList; +import org.apache.james.rrt.api.RecipientRewriteTableConfiguration; import org.apache.james.rrt.api.RecipientRewriteTableException; import org.apache.james.rrt.lib.Mapping; import org.apache.james.rrt.lib.MappingSource; @@ -87,6 +88,7 @@ class DomainMappingsRoutesTest { @BeforeEach void setUp() throws Exception { recipientRewriteTable = spy(new MemoryRecipientRewriteTable()); + recipientRewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED); domainList = mock(DomainList.class); recipientRewriteTable.setDomainList(domainList); Mockito.when(domainList.containsDomain(any())).thenReturn(true); diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/ForwardRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/ForwardRoutesTest.java index 6c3dac7..c7a1f96 100644 --- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/ForwardRoutesTest.java +++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/ForwardRoutesTest.java @@ -42,6 +42,7 @@ 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.rrt.api.RecipientRewriteTableConfiguration; import org.apache.james.rrt.api.RecipientRewriteTableException; import org.apache.james.rrt.lib.Mapping; import org.apache.james.rrt.lib.MappingSource; @@ -112,6 +113,7 @@ class ForwardRoutesTest { domainList.addDomain(ALIAS_DOMAIN); domainList.addDomain(DOMAIN_MAPPING); memoryRecipientRewriteTable.setDomainList(domainList); + memoryRecipientRewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED); MappingSourceModule mappingSourceModule = new MappingSourceModule(); usersRepository = MemoryUsersRepository.withVirtualHosting(domainList); @@ -239,6 +241,27 @@ class ForwardRoutesTest { } @Test + void putShouldDetectLoops() { + with() + .put(ALICE + SEPARATOR + "targets" + SEPARATOR + BOB); + + Map<String, Object> errors = when() + .put(BOB + SEPARATOR + "targets" + SEPARATOR + ALICE) + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.CONFLICT_409) + .extract() + .body() + .jsonPath() + .getMap("."); + + assertThat(errors) + .containsEntry("statusCode", HttpStatus.CONFLICT_409) + .containsEntry("type", "WrongState") + .containsEntry("message", "Creation of redirection of [email protected] to forward:[email protected] would lead to a loop, operation not performed"); + } + + @Test void putUserShouldBeIdempotent() { given() .put(ALICE + SEPARATOR + "targets" + SEPARATOR + BOB); diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/GroupsRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/GroupsRoutesTest.java index bd68fcd..4403e6f 100644 --- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/GroupsRoutesTest.java +++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/GroupsRoutesTest.java @@ -40,6 +40,7 @@ import org.apache.james.core.Username; import org.apache.james.dnsservice.api.DNSService; import org.apache.james.domainlist.api.DomainList; import org.apache.james.domainlist.memory.MemoryDomainList; +import org.apache.james.rrt.api.RecipientRewriteTableConfiguration; import org.apache.james.rrt.api.RecipientRewriteTableException; import org.apache.james.rrt.lib.Mapping; import org.apache.james.rrt.lib.MappingSource; @@ -78,8 +79,8 @@ class GroupsRoutesTest { private WebAdminServer webAdminServer; - private void createServer(GroupsRoutes groupsRoutes) { - webAdminServer = WebAdminUtils.createWebAdminServer(groupsRoutes) + private void createServer(GroupsRoutes groupsRoutes, AddressMappingRoutes addressMappingRoutes) { + webAdminServer = WebAdminUtils.createWebAdminServer(groupsRoutes, addressMappingRoutes) .start(); RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminServer) @@ -109,9 +110,11 @@ class GroupsRoutesTest { domainList.addDomain(ALIAS_DOMAIN); domainList.addDomain(DOMAIN_MAPPING); memoryRecipientRewriteTable.setDomainList(domainList); + memoryRecipientRewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED); usersRepository = MemoryUsersRepository.withVirtualHosting(domainList); MappingSourceModule mappingSourceModule = new MappingSourceModule(); - createServer(new GroupsRoutes(memoryRecipientRewriteTable, usersRepository, new JsonTransformer(mappingSourceModule))); + createServer(new GroupsRoutes(memoryRecipientRewriteTable, usersRepository, new JsonTransformer(mappingSourceModule)), + new AddressMappingRoutes(memoryRecipientRewriteTable)); } @Test @@ -295,6 +298,27 @@ class GroupsRoutesTest { } @Test + void putShouldDetectLoops() { + with().basePath(AddressMappingRoutes.BASE_PATH) + .post(USER_A + "/targets/" + GROUP1); + + Map<String, Object> errors = when() + .put(GROUP1 + SEPARATOR + USER_A) + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.CONFLICT_409) + .extract() + .body() + .jsonPath() + .getMap("."); + + assertThat(errors) + .containsEntry("statusCode", HttpStatus.CONFLICT_409) + .containsEntry("type", "WrongState") + .containsEntry("message", "Creation of redirection of [email protected] to group:[email protected] would lead to a loop, operation not performed"); + } + + @Test void putUserInGroupWithEncodedSlashShouldCreateGroup() { when() .put(GROUP_WITH_ENCODED_SLASH + SEPARATOR + USER_A); @@ -448,7 +472,8 @@ class GroupsRoutesTest { domainList = mock(DomainList.class); memoryRecipientRewriteTable.setDomainList(domainList); Mockito.when(domainList.containsDomain(any())).thenReturn(true); - createServer(new GroupsRoutes(memoryRecipientRewriteTable, userRepository, new JsonTransformer())); + createServer(new GroupsRoutes(memoryRecipientRewriteTable, userRepository, new JsonTransformer()), + new AddressMappingRoutes(memoryRecipientRewriteTable)); } @Test diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/MappingRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/MappingRoutesTest.java index 588cc1d..ee86bf1 100644 --- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/MappingRoutesTest.java +++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/MappingRoutesTest.java @@ -31,6 +31,7 @@ import org.apache.james.dnsservice.api.DNSService; import org.apache.james.domainlist.api.DomainList; import org.apache.james.domainlist.api.DomainListException; import org.apache.james.domainlist.memory.MemoryDomainList; +import org.apache.james.rrt.api.RecipientRewriteTableConfiguration; import org.apache.james.rrt.api.RecipientRewriteTableException; import org.apache.james.rrt.lib.MappingSource; import org.apache.james.rrt.memory.MemoryRecipientRewriteTable; @@ -73,6 +74,7 @@ class MappingRoutesTest { domainList.addDomain(Domain.of("abc")); domainList.addDomain(Domain.of("xyz")); + recipientRewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED); recipientRewriteTable.setDomainList(domainList); webAdminServer = WebAdminUtils.createWebAdminServer(new MappingRoutes(jsonTransformer, recipientRewriteTable)) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
