You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2023/03/03 02:06:07 UTC
[james-project] branch master updated: JAMES-3893 Add a WebAdmin API allowing listing user identity
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
The following commit(s) were added to refs/heads/master by this push:
new 2a51f9cfc0 JAMES-3893 Add a WebAdmin API allowing listing user identity
2a51f9cfc0 is described below
commit 2a51f9cfc09968d134e71677330aa63545c36bd8
Author: Tung Van TRAN <vt...@linagora.com>
AuthorDate: Tue Feb 28 07:49:52 2023 +0700
JAMES-3893 Add a WebAdmin API allowing listing user identity
---
.../docs/modules/ROOT/pages/operate/webadmin.adoc | 46 ++++
.../jmap/api/identity/CustomIdentityDAO.scala | 23 +-
.../apache/james/jmap/api/model/EmailAddress.scala | 2 +
.../james/webadmin/utils/ParametersExtractor.java | 14 +
server/protocols/webadmin/webadmin-jmap/pom.xml | 8 +
.../webadmin/data/jmap/UserIdentityRoutes.java | 111 ++++++++
.../james/webadmin/data/jmap/dto/UserIdentity.java | 151 +++++++++++
.../data/jmap/UserIdentitiesRoutesTest.java | 295 +++++++++++++++++++++
.../webadmin/data/jmap/UserIdentitiesHelper.scala | 38 +++
src/site/markdown/server/manage-webadmin.md | 47 ++++
10 files changed, 733 insertions(+), 2 deletions(-)
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 9621808429..6b66d208f8 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
@@ -671,6 +671,52 @@ Valid status includes:
- `FAILED`: Error encountered while executing this step. Check the logs.
- `ABORTED`: Won't be executed because of previous step failures.
+=== Retrieving the user identities
+
+....
+curl -XGET http://ip:port/users/{baseUser}/identities?default=true
+....
+
+API to get the list of identities of a user
+
+The response will look like:
+
+```
+[
+ {
+ "name":"identity name 1",
+ "email":"bob@domain.tld",
+ "id":"4c039533-75b9-45db-becc-01fb0e747aa8",
+ "mayDelete":true,
+ "textSignature":"textSignature 1",
+ "htmlSignature":"htmlSignature 1",
+ "sortOrder":1,
+ "bcc":[
+ {
+ "emailerName":"bcc name 1",
+ "mailAddress":"bcc1@domain.org"
+ }
+ ],
+ "replyTo":[
+ {
+ "emailerName":"reply name 1",
+ "mailAddress":"reply1@domain.org"
+ }
+ ]
+ }
+]
+```
+
+Query parameters:
+
+* default: (Optional) allows getting the default identity of a user. In order to do that: `default=true`
+
+Response codes:
+
+* 200: The list was successfully retrieved
+* 400: The user is invalid
+* 404: The user is unknown or the default identity can not be found.
+
== Administrating vacation settings
=== Get vacation settings
diff --git a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala
index 679c05bdd8..0d67cb23c7 100644
--- a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala
+++ b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala
@@ -20,8 +20,7 @@
package org.apache.james.jmap.api.identity
import java.nio.charset.StandardCharsets
-import java.util.UUID
-
+import java.util.{Optional, UUID}
import javax.inject.Inject
import org.apache.james.core.{MailAddress, Username}
import org.apache.james.jmap.api.model.{EmailAddress, ForbiddenSendFromException, HtmlSignature, Identity, IdentityId, IdentityName, MayDeleteIdentity, TextSignature}
@@ -33,6 +32,26 @@ import reactor.core.scala.publisher.{SFlux, SMono}
import scala.jdk.StreamConverters._
import scala.util.Try
+import scala.jdk.OptionConverters._
+
+object IdentityCreationRequest {
+ def fromJava(mailAddress: MailAddress,
+ identityName: Optional[String],
+ replyTo: Optional[List[EmailAddress]],
+ bcc: Optional[List[EmailAddress]],
+ sortOrder: Optional[Integer],
+ textSignature: Optional[String],
+ htmlSignature: Optional[String]): IdentityCreationRequest = {
+ IdentityCreationRequest(
+ name = identityName.toScala.map(IdentityName(_)),
+ email = mailAddress,
+ replyTo = replyTo.toScala,
+ bcc = bcc.toScala,
+ sortOrder = sortOrder.toScala.map(_.toInt),
+ textSignature = textSignature.toScala.map(TextSignature(_)),
+ htmlSignature = htmlSignature.toScala.map(HtmlSignature(_)))
+ }
+}
case class IdentityCreationRequest(name: Option[IdentityName],
email: MailAddress,
diff --git a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/EmailAddress.scala b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/EmailAddress.scala
index 68fdebe401..8fca1136f6 100644
--- a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/EmailAddress.scala
+++ b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/EmailAddress.scala
@@ -53,4 +53,6 @@ case class EmailAddress(name: Option[EmailerName], email: MailAddress) {
name.map(_.value).orNull,
email.getLocalPart,
email.getDomain.asString)
+
+ val nameAsString: String = name.map(_.value).orNull
}
diff --git a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/utils/ParametersExtractor.java b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/utils/ParametersExtractor.java
index 7d16a1b691..61a3e09ccc 100644
--- a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/utils/ParametersExtractor.java
+++ b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/utils/ParametersExtractor.java
@@ -28,6 +28,7 @@ import org.apache.james.util.streams.Limit;
import org.apache.james.util.streams.Offset;
import org.eclipse.jetty.http.HttpStatus;
+import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import spark.Request;
@@ -60,6 +61,19 @@ public class ParametersExtractor {
.map(raw -> DurationParser.parse(raw, ChronoUnit.SECONDS));
}
+ public static Optional<Boolean> extractBoolean(Request request, String parameterName) {
+ return Optional.ofNullable(request.queryParams(parameterName))
+ .filter(s -> !s.isEmpty())
+ .map(String::trim)
+ .map(s -> {
+ Preconditions.checkArgument(s.equalsIgnoreCase(Boolean.TRUE.toString())
+ || s.equalsIgnoreCase(Boolean.FALSE.toString()),
+ "Invalid '" + parameterName + "' query parameter");
+ return Boolean.parseBoolean(s);
+ });
+ }
+
+
private static <T extends Number> Optional<T> extractPositiveNumber(Request request, String parameterName, Function<String, T> toNumber) {
try {
return Optional.ofNullable(request.queryParams(parameterName))
diff --git a/server/protocols/webadmin/webadmin-jmap/pom.xml b/server/protocols/webadmin/webadmin-jmap/pom.xml
index 58cf06ead9..164bcfe4cf 100644
--- a/server/protocols/webadmin/webadmin-jmap/pom.xml
+++ b/server/protocols/webadmin/webadmin-jmap/pom.xml
@@ -154,4 +154,12 @@
<scope>test</scope>
</dependency>
</dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>net.alchim31.maven</groupId>
+ <artifactId>scala-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
</project>
diff --git a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/UserIdentityRoutes.java b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/UserIdentityRoutes.java
new file mode 100644
index 0000000000..8a60771704
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/UserIdentityRoutes.java
@@ -0,0 +1,111 @@
+/****************************************************************
+ * 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.data.jmap;
+
+import static org.apache.james.webadmin.Constants.SEPARATOR;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+import org.apache.james.core.Username;
+import org.apache.james.jmap.api.identity.IdentityRepository;
+import org.apache.james.util.FunctionalUtils;
+import org.apache.james.webadmin.Routes;
+import org.apache.james.webadmin.data.jmap.dto.UserIdentity;
+import org.apache.james.webadmin.utils.ErrorResponder;
+import org.apache.james.webadmin.utils.JsonTransformer;
+import org.apache.james.webadmin.utils.ParametersExtractor;
+import org.eclipse.jetty.http.HttpStatus;
+
+import reactor.core.publisher.Flux;
+import spark.HaltException;
+import spark.Request;
+import spark.Response;
+import spark.Service;
+
+public class UserIdentityRoutes implements Routes {
+ public static final String USERS = "/users";
+ public static final String IDENTITIES = "identities";
+ private static final String USER_NAME = ":userName";
+ private Service service;
+ private final IdentityRepository identityRepository;
+ private final JsonTransformer jsonTransformer;
+
+ @Inject
+ public UserIdentityRoutes(IdentityRepository identityRepository,
+ JsonTransformer jsonTransformer) {
+ this.identityRepository = identityRepository;
+ this.jsonTransformer = jsonTransformer;
+ }
+
+ @Override
+ public String getBasePath() {
+ return USERS;
+ }
+
+ @Override
+ public void define(Service service) {
+ this.service = service;
+ getUserIdentities();
+ }
+
+ public void getUserIdentities() {
+ service.get(USERS + SEPARATOR + USER_NAME + SEPARATOR + IDENTITIES, this::listIdentities, jsonTransformer);
+ }
+
+ private List<UserIdentity> listIdentities(Request request, Response response) {
+ Username username = extractUsername(request);
+ Optional<Boolean> defaultFilter = ParametersExtractor.extractBoolean(request, "default");
+
+ List<UserIdentity> identities = Flux.from(identityRepository.list(username))
+ .map(UserIdentity::from)
+ .collectList()
+ .block();
+
+ return defaultFilter
+ .filter(FunctionalUtils.identityPredicate())
+ .map(queryDefault -> getDefaultIdentity(identities)
+ .map(List::of)
+ .orElseThrow(() -> throw404("Default identity can not be found")))
+ .orElse(identities);
+ }
+
+ private Optional<UserIdentity> getDefaultIdentity(List<UserIdentity> identities) {
+ return identities.stream()
+ .filter(UserIdentity::getMayDelete)
+ .min(Comparator.comparing(UserIdentity::getSortOrder));
+ }
+
+ private HaltException throw404(String message) {
+ throw ErrorResponder.builder()
+ .statusCode(HttpStatus.NOT_FOUND_404)
+ .type(ErrorResponder.ErrorType.NOT_FOUND)
+ .message(message)
+ .haltError();
+ }
+
+ private Username extractUsername(Request request) {
+ return Username.of(request.params(USER_NAME));
+ }
+
+}
diff --git a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/dto/UserIdentity.java b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/dto/UserIdentity.java
new file mode 100644
index 0000000000..3e58290732
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/dto/UserIdentity.java
@@ -0,0 +1,151 @@
+/****************************************************************
+ * 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.data.jmap.dto;
+
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.james.core.MailAddress;
+import org.apache.james.jmap.api.model.Identity;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import scala.jdk.javaapi.CollectionConverters;
+import scala.jdk.javaapi.OptionConverters;
+
+public class UserIdentity {
+ public static UserIdentity from(Identity identity) {
+ return new UserIdentity(
+ identity.name(),
+ identity.email().asString(),
+ identity.id().id().toString(),
+ identity.mayDelete(),
+ identity.textSignature(),
+ identity.htmlSignature(),
+ identity.sortOrder(),
+ getBccFromIdentity(identity),
+ getReplyFromIdentity(identity));
+ }
+
+ public static class EmailAddress {
+ public static EmailAddress from(org.apache.james.jmap.api.model.EmailAddress scala) {
+ return new EmailAddress(scala.nameAsString(), scala.email());
+ }
+
+ @JsonProperty("name")
+
+ private String emailerName;
+
+ @JsonProperty("email")
+ private String mailAddress;
+
+ public EmailAddress(String emailerName, MailAddress mailAddress) {
+ this.emailerName = emailerName;
+ this.mailAddress = mailAddress.asString();
+ }
+
+ public String getEmailerName() {
+ return emailerName;
+ }
+
+ public String getMailAddress() {
+ return mailAddress;
+ }
+ }
+
+ private static List<EmailAddress> getBccFromIdentity(Identity identity) {
+ return OptionConverters.toJava(identity.bcc())
+ .map(CollectionConverters::asJava)
+ .orElseGet(List::of)
+ .stream()
+ .map(EmailAddress::from)
+ .collect(Collectors.toList());
+ }
+
+ private static List<EmailAddress> getReplyFromIdentity(Identity identity) {
+ return OptionConverters.toJava(identity.replyTo())
+ .map(CollectionConverters::asJava)
+ .orElseGet(List::of)
+ .stream()
+ .map(EmailAddress::from)
+ .collect(Collectors.toList());
+ }
+
+ private String name;
+ private String email;
+ private String id;
+ private Boolean mayDelete;
+ private String textSignature;
+ private String htmlSignature;
+ private Integer sortOrder;
+ private List<EmailAddress> bcc;
+ private List<EmailAddress> replyTo;
+
+ public UserIdentity(String name, String email, String id,
+ Boolean mayDelete, String textSignature, String htmlSignature,
+ Integer sortOrder, List<EmailAddress> bcc, List<EmailAddress> replyTo) {
+ this.name = name;
+ this.email = email;
+ this.id = id;
+ this.mayDelete = mayDelete;
+ this.textSignature = textSignature;
+ this.htmlSignature = htmlSignature;
+ this.sortOrder = sortOrder;
+ this.bcc = bcc;
+ this.replyTo = replyTo;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public Boolean getMayDelete() {
+ return mayDelete;
+ }
+
+ public String getTextSignature() {
+ return textSignature;
+ }
+
+ public String getHtmlSignature() {
+ return htmlSignature;
+ }
+
+ public Integer getSortOrder() {
+ return sortOrder;
+ }
+
+ public List<EmailAddress> getBcc() {
+ return bcc;
+ }
+
+ public List<EmailAddress> getReplyTo() {
+ return replyTo;
+ }
+}
diff --git a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/UserIdentitiesRoutesTest.java b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/UserIdentitiesRoutesTest.java
new file mode 100644
index 0000000000..51038427a1
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/UserIdentitiesRoutesTest.java
@@ -0,0 +1,295 @@
+/****************************************************************
+ * 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.data.jmap;
+
+import static io.restassured.RestAssured.given;
+import static io.restassured.RestAssured.when;
+import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.Username;
+import org.apache.james.jmap.api.identity.DefaultIdentitySupplier;
+import org.apache.james.jmap.api.identity.IdentityCreationRequest;
+import org.apache.james.jmap.api.identity.IdentityRepository;
+import org.apache.james.jmap.api.model.EmailAddress;
+import org.apache.james.jmap.api.model.Identity;
+import org.apache.james.jmap.memory.identity.MemoryCustomIdentityDAO;
+import org.apache.james.json.DTOConverter;
+import org.apache.james.mime4j.dom.address.Mailbox;
+import org.apache.james.mime4j.dom.address.MailboxList;
+import org.apache.james.task.Hostname;
+import org.apache.james.task.MemoryTaskManager;
+import org.apache.james.webadmin.WebAdminServer;
+import org.apache.james.webadmin.WebAdminUtils;
+import org.apache.james.webadmin.routes.TasksRoutes;
+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.mockito.Mockito;
+
+import io.restassured.RestAssured;
+import net.javacrumbs.jsonunit.core.Option;
+import reactor.core.publisher.Mono;
+import reactor.core.scala.publisher.SMono;
+import scala.jdk.javaapi.CollectionConverters;
+
+class UserIdentitiesRoutesTest {
+
+ private static final Username BOB = Username.of("bob@domain.tld");
+ private static final String BASE_PATH = "/users";
+ private static final String GET_IDENTITIES_USERS_PATH = "/%s/identities";
+ private WebAdminServer webAdminServer;
+ private IdentityRepository identityRepository;
+ private DefaultIdentitySupplier identityFactory;
+
+ @BeforeEach
+ void setUp() {
+ MemoryTaskManager taskManager = new MemoryTaskManager(new Hostname("foo"));
+ identityFactory = mock(DefaultIdentitySupplier.class);
+ Mockito.when(identityFactory.userCanSendFrom(any(), any())).thenReturn(SMono.just(true).hasElement());
+
+ identityRepository = new IdentityRepository(new MemoryCustomIdentityDAO(), identityFactory);
+
+ JsonTransformer jsonTransformer = new JsonTransformer();
+ TasksRoutes tasksRoutes = new TasksRoutes(taskManager, jsonTransformer, DTOConverter.of(UploadCleanupTaskAdditionalInformationDTO.SERIALIZATION_MODULE));
+ UserIdentityRoutes userIdentityRoutes = new UserIdentityRoutes(identityRepository, new JsonTransformer());
+
+ webAdminServer = WebAdminUtils.createWebAdminServer(userIdentityRoutes, tasksRoutes).start();
+ RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminServer)
+ .setBasePath(BASE_PATH)
+ .build();
+ }
+
+ @AfterEach
+ void stop() {
+ webAdminServer.destroy();
+ }
+
+ @Test
+ void listIdentitiesShouldReturnBothCustomAndServerSetIdentities() throws Exception {
+ // identity: server set
+ Mockito.when(identityFactory.listIdentities(BOB))
+ .thenReturn(CollectionConverters.asScala(List.of(UserIdentitiesHelper.IDENTITY1())).toList());
+
+ IdentityCreationRequest creationRequest = IdentityCreationRequest.fromJava(
+ BOB.asMailAddress(),
+ Optional.of("identity name 1"),
+ Optional.of(EmailAddress.from(new MailboxList(
+ new Mailbox("replyTo1", "james.org"),
+ new Mailbox("replyTo2", "james.org")))),
+ Optional.of(EmailAddress.from(new MailboxList(
+ new Mailbox("bcc1", "james.org"),
+ new Mailbox("bcc2", "james.org")))),
+ Optional.of(1),
+ Optional.of("textSignature 1"),
+ Optional.of("htmlSignature 1"));
+
+ // identity: custom
+ Mono.from(identityRepository.save(BOB, creationRequest)).block();
+
+ String response = when()
+ .get(String.format(GET_IDENTITIES_USERS_PATH, BOB.asString()))
+ .then()
+ .statusCode(HttpStatus.OK_200)
+ .contentType(io.restassured.http.ContentType.JSON)
+ .extract()
+ .body()
+ .asString();
+
+ String expectedResponse = "[" +
+ " {" +
+ " \"name\": \"identity name 1\"," +
+ " \"email\": \"bob@domain.tld\"," +
+ " \"id\": \"${json-unit.ignore}\"," +
+ " \"mayDelete\": true," +
+ " \"textSignature\": \"textSignature 1\"," +
+ " \"htmlSignature\": \"htmlSignature 1\"," +
+ " \"sortOrder\": 1," +
+ " \"bcc\": [" +
+ " {" +
+ " \"name\": null," +
+ " \"email\": \"bcc1@james.org\"" +
+ " }," +
+ " {" +
+ " \"name\": null," +
+ " \"email\": \"bcc2@james.org\"" +
+ " }" +
+ " ]," +
+ " \"replyTo\": [" +
+ " {" +
+ " \"name\": null," +
+ " \"email\": \"replyTo1@james.org\"" +
+ " }," +
+ " {" +
+ " \"name\": null," +
+ " \"email\": \"replyTo2@james.org\"" +
+ " }" +
+ " ]" +
+ " }," +
+ " {" +
+ " \"name\": \"base name\"," +
+ " \"email\": \"bob@domain.tld\"," +
+ " \"id\": \"${json-unit.ignore}\"," +
+ " \"mayDelete\": false," +
+ " \"textSignature\": \"text signature base\"," +
+ " \"htmlSignature\": \"html signature base\"," +
+ " \"sortOrder\": 100," +
+ " \"bcc\": [" +
+ " {" +
+ " \"name\": \"My Boss bcc 1\"," +
+ " \"email\": \"boss_bcc_1@domain.tld\"" +
+ " }" +
+ " ]," +
+ " \"replyTo\": [" +
+ " {" +
+ " \"name\": \"My Boss 1\"," +
+ " \"email\": \"boss1@domain.tld\"" +
+ " }" +
+ " ]" +
+ " }" +
+ "]";
+ assertThatJson(response)
+ .when(Option.IGNORING_ARRAY_ORDER)
+ .isEqualTo(expectedResponse);
+ }
+
+ @Test
+ void listIdentitiesShouldSupportDefaultParam() throws Exception {
+ // identity: server set
+ Mockito.when(identityFactory.listIdentities(BOB))
+ .thenReturn(CollectionConverters.asScala(List.of(UserIdentitiesHelper.IDENTITY1())).toList());
+
+ Integer highPriorityOrder = 1;
+ Integer lowPriorityOrder = 2;
+ IdentityCreationRequest creationRequest1 = IdentityCreationRequest.fromJava(
+ BOB.asMailAddress(),
+ Optional.of("identity name 1"),
+ Optional.of(UserIdentitiesHelper.emailAddressFromJava("reply name 1", new MailAddress("reply1@domain.org"))),
+ Optional.of(UserIdentitiesHelper.emailAddressFromJava("bcc name 1", new MailAddress("bcc1@domain.org"))),
+ Optional.of(highPriorityOrder),
+ Optional.of("textSignature 1"),
+ Optional.of("htmlSignature 1"));
+
+ IdentityCreationRequest creationRequest2 = IdentityCreationRequest.fromJava(
+ BOB.asMailAddress(),
+ Optional.of("identity name 2"),
+ Optional.of(UserIdentitiesHelper.emailAddressFromJava("reply name 2", new MailAddress("reply2@domain.org"))),
+ Optional.of(UserIdentitiesHelper.emailAddressFromJava("bcc name 2", new MailAddress("bcc2@domain.org"))),
+ Optional.of(lowPriorityOrder),
+ Optional.of("textSignature 2"),
+ Optional.of("htmlSignature 2"));
+
+ // identity: custom
+ Mono.from(identityRepository.save(BOB, creationRequest1)).block();
+ Mono.from(identityRepository.save(BOB, creationRequest2)).block();
+
+ String response = given()
+ .queryParam("default", "true")
+ .get(String.format(GET_IDENTITIES_USERS_PATH, BOB.asString()))
+ .then()
+ .statusCode(HttpStatus.OK_200)
+ .contentType(io.restassured.http.ContentType.JSON)
+ .extract()
+ .body()
+ .asString();
+
+ String expectedResponse = "[" +
+ " {" +
+ " \"name\": \"identity name 1\"," +
+ " \"email\": \"bob@domain.tld\"," +
+ " \"id\": \"${json-unit.ignore}\"," +
+ " \"mayDelete\": true," +
+ " \"textSignature\": \"textSignature 1\"," +
+ " \"htmlSignature\": \"htmlSignature 1\"," +
+ " \"sortOrder\": 1," +
+ " \"bcc\": [" +
+ " {" +
+ " \"name\": \"bcc name 1\"," +
+ " \"email\": \"bcc1@domain.org\"" +
+ " }" +
+ " ]," +
+ " \"replyTo\": [" +
+ " {" +
+ " \"name\": \"reply name 1\"," +
+ " \"email\": \"reply1@domain.org\"" +
+ " }" +
+ " ]" +
+ " }" +
+ "]";
+ assertThatJson(response)
+ .when(Option.IGNORING_ARRAY_ORDER)
+ .isEqualTo(expectedResponse);
+ }
+
+ @Test
+ void listIdentitiesShouldReturnBadRequestWhenInvalidDefaultParam() {
+ Mockito.when(identityFactory.listIdentities(BOB))
+ .thenReturn(CollectionConverters.asScala(List.of(UserIdentitiesHelper.IDENTITY1())).toList());
+
+ String response = given()
+ .queryParam("default", "invalid")
+ .get(String.format(GET_IDENTITIES_USERS_PATH, BOB.asString()))
+ .then()
+ .statusCode(HttpStatus.BAD_REQUEST_400)
+ .contentType(io.restassured.http.ContentType.JSON)
+ .extract()
+ .body()
+ .asString();
+
+ assertThatJson(response)
+ .isEqualTo("{" +
+ " \"statusCode\": 400," +
+ " \"type\": \"InvalidArgument\"," +
+ " \"message\": \"Invalid arguments supplied in the user request\"," +
+ " \"details\": \"Invalid 'default' query parameter\"" +
+ "}");
+ }
+
+ @Test
+ void listIdentitiesShouldReturnNotFoundWhenCanNotQueryDefaultIdentity() {
+ Mockito.when(identityFactory.listIdentities(BOB))
+ .thenReturn(CollectionConverters.asScala(List.<Identity>of()).toList());
+
+ String response = given()
+ .queryParam("default", "true")
+ .get(String.format(GET_IDENTITIES_USERS_PATH, BOB.asString()))
+ .then()
+ .statusCode(HttpStatus.NOT_FOUND_404)
+ .contentType(io.restassured.http.ContentType.JSON)
+ .extract()
+ .body()
+ .asString();
+
+ assertThatJson(response)
+ .isEqualTo("{" +
+ " \"statusCode\": 404," +
+ " \"type\": \"notFound\"," +
+ " \"message\": \"Default identity can not be found\"," +
+ " \"details\": null" +
+ "}");
+ }
+}
diff --git a/server/protocols/webadmin/webadmin-jmap/src/test/scala/org/apache/james/webadmin/data/jmap/UserIdentitiesHelper.scala b/server/protocols/webadmin/webadmin-jmap/src/test/scala/org/apache/james/webadmin/data/jmap/UserIdentitiesHelper.scala
new file mode 100644
index 0000000000..4cbaf60b3f
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-jmap/src/test/scala/org/apache/james/webadmin/data/jmap/UserIdentitiesHelper.scala
@@ -0,0 +1,38 @@
+/****************************************************************
+ * 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.data.jmap
+
+import org.apache.james.core.{MailAddress, Username}
+import org.apache.james.jmap.api.model.{EmailAddress, EmailerName, HtmlSignature, Identity, IdentityId, IdentityName, MayDeleteIdentity, TextSignature}
+
+object UserIdentitiesHelper {
+
+ val IDENTITY1: Identity = Identity(id = IdentityId.generate,
+ name = IdentityName("base name"),
+ email = Username.of("bob@domain.tld").asMailAddress(),
+ replyTo = Some(List(EmailAddress(Some(EmailerName("My Boss 1")), new MailAddress("boss1@domain.tld")))),
+ bcc = Some(List(EmailAddress(Some(EmailerName("My Boss bcc 1")), new MailAddress("boss_bcc_1@domain.tld")))),
+ textSignature = TextSignature("text signature base"),
+ htmlSignature = HtmlSignature("html signature base"),
+ mayDelete = MayDeleteIdentity(false))
+
+ def emailAddressFromJava(name: String, email: MailAddress) : List[EmailAddress] =
+ List(EmailAddress(Some(EmailerName(name)), email))
+}
diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md
index 9212a810e9..f4e5577ab7 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -523,6 +523,53 @@ Valid status includes:
- `FAILED`: Error encountered while executing this step. Check the logs.
- `ABORTED`: Won't be executed because of previous step failures.
+
+### Retrieving the user identities
+
+```
+curl -XGET http://ip:port/users/{baseUser}/identities?default=true
+```
+
+API to get the list of identities of a user
+
+The response will look like:
+
+```
+[
+ {
+ "name":"identity name 1",
+ "email":"bob@domain.tld",
+ "id":"4c039533-75b9-45db-becc-01fb0e747aa8",
+ "mayDelete":true,
+ "textSignature":"textSignature 1",
+ "htmlSignature":"htmlSignature 1",
+ "sortOrder":1,
+ "bcc":[
+ {
+ "emailerName":"bcc name 1",
+ "mailAddress":"bcc1@domain.org"
+ }
+ ],
+ "replyTo":[
+ {
+ "emailerName":"reply name 1",
+ "mailAddress":"reply1@domain.org"
+ }
+ ]
+ }
+]
+```
+
+Query parameters:
+
+ - default: (Optional) allows getting the default identity of a user. In order to do that: `default=true`
+
+Response codes:
+
+ - 200: The list was successfully retrieved
+ - 400: The user is invalid
+ - 404: The user is unknown or the default identity can not be found.
+
## Administrating mailboxes
### All mailboxes
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org