You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2022/06/03 08:41:27 UTC

[james-project] 01/03: JAMES-3756 Webadmin API for delegation

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 2cd086a6bfa2612da6a0929b74873fc9579a0eef
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Thu May 26 17:13:55 2022 +0700

    JAMES-3756 Webadmin API for delegation
---
 .../apache/james/webadmin/routes/UserRoutes.java   | 113 ++++++++-
 .../james/webadmin/routes/UserRoutesTest.java      | 254 ++++++++++++++++++++-
 2 files changed, 364 insertions(+), 3 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 f6117ff8c5..90c2f9c59f 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
@@ -32,6 +32,7 @@ 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.AlreadyExistInUsersRepositoryException;
+import org.apache.james.user.api.DelegationStore;
 import org.apache.james.user.api.InvalidUsernameException;
 import org.apache.james.user.api.UsersRepositoryException;
 import org.apache.james.webadmin.Constants;
@@ -51,6 +52,8 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.collect.ImmutableList;
 
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
 import spark.HaltException;
 import spark.Request;
 import spark.Response;
@@ -59,27 +62,31 @@ import spark.Service;
 public class UserRoutes implements Routes {
 
     private static final String USER_NAME = ":userName";
+    private static final String DELEGATED_USER_NAME = ":delegatedUserName";
     private static final Logger LOGGER = LoggerFactory.getLogger(UserRoutes.class);
 
     public static final String USERS = "/users";
     private static final String FORCE_PARAM = "force";
     private static final String VERIFY = "verify";
+    private static final String AUTHORIZED_USERS = "authorizedUsers";
 
     private final UserService userService;
     private final JsonTransformer jsonTransformer;
     private final JsonExtractor<VerifyUserRequest> jsonExtractorVerify;
     private final CanSendFrom canSendFrom;
     private final JsonExtractor<AddUserRequest> jsonExtractor;
+    private final DelegationStore delegationStore;
 
     private Service service;
 
     @Inject
-    public UserRoutes(UserService userService, CanSendFrom canSendFrom, JsonTransformer jsonTransformer) {
+    public UserRoutes(UserService userService, CanSendFrom canSendFrom, JsonTransformer jsonTransformer, DelegationStore delegationStore) {
         this.userService = userService;
         this.jsonTransformer = jsonTransformer;
         this.canSendFrom = canSendFrom;
         this.jsonExtractor = new JsonExtractor<>(AddUserRequest.class);
         this.jsonExtractorVerify = new JsonExtractor<>(VerifyUserRequest.class);
+        this.delegationStore = delegationStore;
     }
 
     @Override
@@ -102,6 +109,30 @@ public class UserRoutes implements Routes {
         defineUserExist();
 
         defineVerifyUsers();
+
+        getDelegatedUsers();
+
+        clearAllDelegatedUsers();
+
+        addDelegatedUser();
+
+        removeDelegatedUser();
+    }
+
+    public void getDelegatedUsers() {
+        service.get(USERS + SEPARATOR + USER_NAME + SEPARATOR + AUTHORIZED_USERS, this::getAuthorizedUsers, jsonTransformer);
+    }
+
+    public void clearAllDelegatedUsers() {
+        service.delete(USERS + SEPARATOR + USER_NAME + SEPARATOR + AUTHORIZED_USERS, this::clearAllAuthorizedUsers);
+    }
+
+    public void addDelegatedUser() {
+        service.put(USERS + SEPARATOR + USER_NAME + SEPARATOR + AUTHORIZED_USERS + SEPARATOR + DELEGATED_USER_NAME, this::addAuthorizedUser);
+    }
+
+    public void removeDelegatedUser() {
+        service.delete(USERS + SEPARATOR + USER_NAME + SEPARATOR + AUTHORIZED_USERS + SEPARATOR + DELEGATED_USER_NAME, this::removeAuthorizedUser);
     }
 
     public void defineVerifyUsers() {
@@ -224,6 +255,82 @@ public class UserRoutes implements Routes {
         }
     }
 
+    private List<String> getAuthorizedUsers(Request request, Response response) throws UsersRepositoryException {
+        Username baseUser = extractUsername(request);
+
+        if (userService.userExists(baseUser)) {
+            return Flux.from(delegationStore.authorizedUsers(baseUser))
+                .map(Username::asString)
+                .collectList()
+                .block();
+        } else {
+            throw ErrorResponder.builder()
+                .statusCode(HttpStatus.NOT_FOUND_404)
+                .type(ErrorType.NOT_FOUND)
+                .message(String.format("User '%s' does not exist", baseUser.asString()))
+                .haltError();
+        }
+    }
+
+    private String clearAllAuthorizedUsers(Request request, Response response) throws UsersRepositoryException {
+        Username baseUser = extractUsername(request);
+
+        if (userService.userExists(baseUser)) {
+            Mono.from(delegationStore.clear(baseUser)).block();
+            return Constants.EMPTY_BODY;
+        } else {
+            throw ErrorResponder.builder()
+                .statusCode(HttpStatus.NOT_FOUND_404)
+                .type(ErrorType.NOT_FOUND)
+                .message(String.format("User '%s' does not exist", baseUser.asString()))
+                .haltError();
+        }
+    }
+
+    private String addAuthorizedUser(Request request, Response response) throws UsersRepositoryException {
+        Username baseUser = extractUsername(request);
+        Username delegatedUser = extractDelegatedUsername(request);
+
+        if (!userService.userExists(baseUser)) {
+            throw ErrorResponder.builder()
+                .statusCode(HttpStatus.NOT_FOUND_404)
+                .type(ErrorType.NOT_FOUND)
+                .message(String.format("User '%s' does not exist", baseUser.asString()))
+                .haltError();
+        } else if (!userService.userExists(delegatedUser)) {
+            throw ErrorResponder.builder()
+                .statusCode(HttpStatus.BAD_REQUEST_400)
+                .type(ErrorType.INVALID_ARGUMENT)
+                .message(String.format("Delegated user '%s' does not exist", delegatedUser.asString()))
+                .haltError();
+        } else {
+            Mono.from(delegationStore.addAuthorizedUser(baseUser, delegatedUser)).block();
+            return Constants.EMPTY_BODY;
+        }
+    }
+
+    private String removeAuthorizedUser(Request request, Response response) throws UsersRepositoryException {
+        Username baseUser = extractUsername(request);
+        Username delegatedUser = extractDelegatedUsername(request);
+
+        if (!userService.userExists(baseUser)) {
+            throw ErrorResponder.builder()
+                .statusCode(HttpStatus.NOT_FOUND_404)
+                .type(ErrorType.NOT_FOUND)
+                .message(String.format("User '%s' does not exist", baseUser.asString()))
+                .haltError();
+        } else if (!userService.userExists(delegatedUser)) {
+            throw ErrorResponder.builder()
+                .statusCode(HttpStatus.BAD_REQUEST_400)
+                .type(ErrorType.INVALID_ARGUMENT)
+                .message(String.format("Delegated user '%s' does not exist", delegatedUser.asString()))
+                .haltError();
+        } else {
+            Mono.from(delegationStore.removeAuthorizedUser(baseUser, delegatedUser)).block();
+            return Constants.EMPTY_BODY;
+        }
+    }
+
     private List<String> allowedFromHeaders(Request request, Response response) {
         Username username = extractUsername(request);
 
@@ -257,4 +364,8 @@ public class UserRoutes implements Routes {
         return Username.of(request.params(USER_NAME));
     }
 
+    private Username extractDelegatedUsername(Request request) {
+        return Username.of(request.params(DELEGATED_USER_NAME));
+    }
+
 }
\ No newline at end of file
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 bb6514aa0f..ffed30a326 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
@@ -52,9 +52,11 @@ import org.apache.james.rrt.lib.AliasReverseResolverImpl;
 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.DelegationStore;
 import org.apache.james.user.api.UsersRepository;
 import org.apache.james.user.api.UsersRepositoryException;
 import org.apache.james.user.api.model.User;
+import org.apache.james.user.memory.MemoryDelegationStore;
 import org.apache.james.user.memory.MemoryUsersRepository;
 import org.apache.james.webadmin.WebAdminServer;
 import org.apache.james.webadmin.WebAdminUtils;
@@ -80,9 +82,16 @@ import com.google.common.collect.ImmutableMap;
 
 import io.restassured.RestAssured;
 import io.restassured.http.ContentType;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
 
 class UserRoutesTest {
 
+    private static final String GET_DELEGATED_USERS_PATH = "/%s/authorizedUsers";
+    private static final String CLEAR_DELEGATED_USERS_PATH = "/%s/authorizedUsers";
+    private static final String ADD_DELEGATED_USER_PATH = "/%s/authorizedUsers/%s";
+    private static final String REMOVE_DELEGATED_USER_PATH = "/%s/authorizedUsers/%s";
+
     private static class UserRoutesExtension implements BeforeEachCallback, AfterEachCallback, ParameterResolver {
 
         static UserRoutesExtension withVirtualHosting() throws DomainListException {
@@ -106,6 +115,7 @@ class UserRoutesTest {
         final MemoryRecipientRewriteTable recipientRewriteTable;
         final AliasReverseResolver aliasReverseResolver;
         final CanSendFrom canSendFrom;
+        final MemoryDelegationStore delegationStore;
 
         WebAdminServer webAdminServer;
 
@@ -123,6 +133,7 @@ class UserRoutesTest {
             this.usersRepository.setValidator(validator);
             recipientRewriteTable.setUsersRepository(usersRepository);
             recipientRewriteTable.setUserEntityValidator(validator);
+            this.delegationStore = new MemoryDelegationStore();
         }
 
         @Override
@@ -139,7 +150,8 @@ class UserRoutesTest {
         public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
             Class<?> parameterType = parameterContext.getParameter().getType();
             return parameterType.isAssignableFrom(UsersRepository.class)
-                || parameterType.isAssignableFrom(RecipientRewriteTable.class);
+                || parameterType.isAssignableFrom(RecipientRewriteTable.class)
+                || parameterType.isAssignableFrom(DelegationStore.class);
         }
 
         @Override
@@ -151,11 +163,15 @@ class UserRoutesTest {
             if (parameterType.isAssignableFrom(RecipientRewriteTable.class)) {
                 return recipientRewriteTable;
             }
+            if (parameterType.isAssignableFrom(DelegationStore.class)) {
+                return delegationStore;
+            }
             throw new RuntimeException("Unknown parameter type: " + parameterType);
         }
 
         private WebAdminServer startServer(UsersRepository usersRepository) {
-            WebAdminServer server = WebAdminUtils.createWebAdminServer(new UserRoutes(new UserService(usersRepository), canSendFrom, new JsonTransformer()))
+            WebAdminServer server = WebAdminUtils.createWebAdminServer(new UserRoutes(new UserService(usersRepository), canSendFrom, new JsonTransformer(),
+                    delegationStore))
                 .start();
 
             RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(server)
@@ -172,6 +188,237 @@ class UserRoutesTest {
 
         interface NormalBehaviourContract {
 
+            @Test
+            default void getDelegatedUsersShouldReturnEmptyByDefault(UsersRepository usersRepository) throws UsersRepositoryException {
+                when(usersRepository.contains(ALICE)).thenReturn(true);
+
+                List<Map<String, String>> delegatedUserList = when()
+                        .get(String.format(GET_DELEGATED_USERS_PATH, ALICE.asString()))
+                    .then()
+                        .statusCode(HttpStatus.OK_200)
+                        .contentType(ContentType.JSON)
+                        .extract()
+                        .body()
+                        .jsonPath()
+                        .getList(".");
+
+                assertThat(delegatedUserList).isEmpty();
+            }
+
+            @Test
+            default void getDelegatedUsersShouldReturnAddedUsers(UsersRepository usersRepository, DelegationStore delegationStore) throws UsersRepositoryException {
+                when(usersRepository.contains(ALICE)).thenReturn(true);
+
+                Mono.from(delegationStore.addAuthorizedUser(ALICE, BOB)).block();
+                Mono.from(delegationStore.addAuthorizedUser(ALICE, ANDRE)).block();
+
+                List<String> delegatedUserList = when()
+                    .get(String.format(GET_DELEGATED_USERS_PATH, ALICE.asString()))
+                .then()
+                    .statusCode(HttpStatus.OK_200)
+                    .contentType(ContentType.JSON)
+                    .extract()
+                    .body()
+                    .jsonPath()
+                    .getList(".");
+
+                assertThat(delegatedUserList).containsOnly(ANDRE.asString(), BOB.asString());
+            }
+
+            @Test
+            default void getDelegatedUsersShouldReturn404NotFoundWhenBaseUserIsNotExisted() {
+                when()
+                    .get(String.format(GET_DELEGATED_USERS_PATH, ALICE.asString()))
+                .then()
+                    .statusCode(HttpStatus.NOT_FOUND_404)
+                    .body("statusCode", is(HttpStatus.NOT_FOUND_404))
+                    .body("type", is(ErrorResponder.ErrorType.NOT_FOUND.getType()))
+                    .body("message", is(String.format("User '%s' does not exist", ALICE.asString())));
+            }
+
+            @Test
+            default void addDelegatedUserShouldSucceed(UsersRepository usersRepository, DelegationStore delegationStore) throws UsersRepositoryException {
+                when(usersRepository.contains(ALICE)).thenReturn(true);
+                when(usersRepository.contains(BOB)).thenReturn(true);
+
+                when()
+                    .put(String.format(ADD_DELEGATED_USER_PATH, ALICE.asString(), BOB.asString()))
+                .then()
+                    .statusCode(HttpStatus.OK_200)
+                    .contentType(ContentType.JSON);
+
+                assertThat(Flux.from(delegationStore.authorizedUsers(ALICE)).collectList().block()).containsOnly(BOB);
+            }
+
+            @Test
+            default void addDelegatedUserShouldReturn404NotFoundWhenBaseUserIsNotExisted() {
+                when()
+                    .put(String.format(ADD_DELEGATED_USER_PATH, ALICE.asString(), BOB.asString()))
+                .then()
+                    .statusCode(HttpStatus.NOT_FOUND_404)
+                    .body("statusCode", is(HttpStatus.NOT_FOUND_404))
+                    .body("type", is(ErrorResponder.ErrorType.NOT_FOUND.getType()))
+                    .body("message", is(String.format("User '%s' does not exist", ALICE.asString())));
+            }
+
+            @Test
+            default void addDelegatedUserShouldReturn400BadRequestWhenDelegatedUserIsNotExisted(UsersRepository usersRepository) throws UsersRepositoryException {
+                when(usersRepository.contains(ALICE)).thenReturn(true);
+
+                when()
+                    .put(String.format(ADD_DELEGATED_USER_PATH, ALICE.asString(), BOB.asString()))
+                .then()
+                    .statusCode(HttpStatus.BAD_REQUEST_400)
+                    .body("statusCode", is(HttpStatus.BAD_REQUEST_400))
+                    .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+                    .body("message", is(String.format("Delegated user '%s' does not exist", BOB.asString())));
+            }
+
+            @Test
+            default void removeDelegatedUserShouldRemoveThatDelegatedUser(UsersRepository usersRepository, DelegationStore delegationStore) throws UsersRepositoryException {
+                when(usersRepository.contains(ALICE)).thenReturn(true);
+                when(usersRepository.contains(BOB)).thenReturn(true);
+                when(usersRepository.contains(ANDRE)).thenReturn(true);
+
+                Mono.from(delegationStore.addAuthorizedUser(ALICE, BOB)).block();
+                Mono.from(delegationStore.addAuthorizedUser(ALICE, ANDRE)).block();
+
+                when()
+                    .delete(String.format(REMOVE_DELEGATED_USER_PATH, ALICE.asString(), BOB.asString()))
+                .then()
+                    .statusCode(HttpStatus.OK_200)
+                    .contentType(ContentType.JSON);
+
+                assertThat(Flux.from(delegationStore.authorizedUsers(ALICE)).collectList().block()).containsOnly(ANDRE);
+            }
+
+            @Test
+            default void removeDelegatedUserShouldSucceedWhenEmptyAddedUsers(UsersRepository usersRepository, DelegationStore delegationStore) throws UsersRepositoryException {
+                when(usersRepository.contains(ALICE)).thenReturn(true);
+                when(usersRepository.contains(BOB)).thenReturn(true);
+
+                when()
+                    .delete(String.format(REMOVE_DELEGATED_USER_PATH, ALICE.asString(), BOB.asString()))
+                .then()
+                    .statusCode(HttpStatus.OK_200)
+                    .contentType(ContentType.JSON);
+
+                assertThat(Flux.from(delegationStore.authorizedUsers(ALICE)).collectList().block()).isEmpty();
+            }
+
+            @Test
+            default void removeDelegatedUserShouldReturn404NotFoundWhenBaseUserIsNotExisted() {
+                when()
+                    .delete(String.format(REMOVE_DELEGATED_USER_PATH, ALICE.asString(), BOB.asString()))
+                .then()
+                    .statusCode(HttpStatus.NOT_FOUND_404)
+                    .body("statusCode", is(HttpStatus.NOT_FOUND_404))
+                    .body("type", is(ErrorResponder.ErrorType.NOT_FOUND.getType()))
+                    .body("message", is(String.format("User '%s' does not exist", ALICE.asString())));
+            }
+
+            @Test
+            default void removeDelegatedUserShouldReturn400BadRequestWhenDelegatedUserIsNotExisted(UsersRepository usersRepository) throws UsersRepositoryException {
+                when(usersRepository.contains(ALICE)).thenReturn(true);
+
+                when()
+                    .delete(String.format(REMOVE_DELEGATED_USER_PATH, ALICE.asString(), BOB.asString()))
+                .then()
+                    .statusCode(HttpStatus.BAD_REQUEST_400)
+                    .body("statusCode", is(HttpStatus.BAD_REQUEST_400))
+                    .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+                    .body("message", is(String.format("Delegated user '%s' does not exist", BOB.asString())));
+            }
+
+
+            @Test
+            default void removeDelegatedUserShouldBeIdempotent(UsersRepository usersRepository, DelegationStore delegationStore) throws UsersRepositoryException {
+                when(usersRepository.contains(ALICE)).thenReturn(true);
+                when(usersRepository.contains(BOB)).thenReturn(true);
+
+                Mono.from(delegationStore.addAuthorizedUser(ALICE, BOB)).block();
+
+                when()
+                    .delete(String.format(REMOVE_DELEGATED_USER_PATH, ALICE.asString(), BOB.asString()))
+                .then()
+                    .statusCode(HttpStatus.OK_200)
+                    .contentType(ContentType.JSON);
+
+                when()
+                    .delete(String.format(REMOVE_DELEGATED_USER_PATH, ALICE.asString(), BOB.asString()))
+                .then()
+                    .statusCode(HttpStatus.OK_200)
+                    .contentType(ContentType.JSON);
+
+                assertThat(Flux.from(delegationStore.authorizedUsers(ALICE)).collectList().block()).isEmpty();
+            }
+
+            @Test
+            default void clearAllDelegatedUsersShouldSucceedWhenEmptyAddedUsers(UsersRepository usersRepository, DelegationStore delegationStore) throws UsersRepositoryException {
+                when(usersRepository.contains(ALICE)).thenReturn(true);
+
+                when()
+                    .delete(String.format(CLEAR_DELEGATED_USERS_PATH, ALICE.asString()))
+                .then()
+                    .statusCode(HttpStatus.OK_200)
+                    .contentType(ContentType.JSON);
+
+                assertThat(Flux.from(delegationStore.authorizedUsers(ALICE)).collectList().block()).isEmpty();
+            }
+
+            @Test
+            default void clearAllDelegatedUsersShouldBeIdempotent(UsersRepository usersRepository, DelegationStore delegationStore) throws UsersRepositoryException {
+                when(usersRepository.contains(ALICE)).thenReturn(true);
+                when(usersRepository.contains(BOB)).thenReturn(true);
+                when(usersRepository.contains(ANDRE)).thenReturn(true);
+
+                Mono.from(delegationStore.addAuthorizedUser(ALICE, BOB)).block();
+                Mono.from(delegationStore.addAuthorizedUser(ALICE, ANDRE)).block();
+
+                when()
+                    .delete(String.format(CLEAR_DELEGATED_USERS_PATH, ALICE.asString()))
+                .then()
+                    .statusCode(HttpStatus.OK_200)
+                    .contentType(ContentType.JSON);
+
+                when()
+                    .delete(String.format(CLEAR_DELEGATED_USERS_PATH, ALICE.asString()))
+                .then()
+                    .statusCode(HttpStatus.OK_200)
+                    .contentType(ContentType.JSON);
+
+                assertThat(Flux.from(delegationStore.authorizedUsers(ALICE)).collectList().block()).isEmpty();
+            }
+
+            @Test
+            default void clearAllDelegatedUsersShouldReturn404NotFoundWhenBaseUserIsNotExisted() {
+                when()
+                    .delete(String.format(CLEAR_DELEGATED_USERS_PATH, ALICE.asString()))
+                .then()
+                    .statusCode(HttpStatus.NOT_FOUND_404)
+                    .body("statusCode", is(HttpStatus.NOT_FOUND_404))
+                    .body("type", is(ErrorResponder.ErrorType.NOT_FOUND.getType()))
+                    .body("message", is(String.format("User '%s' does not exist", ALICE.asString())));
+            }
+
+            @Test
+            default void clearAllDelegatedUsersShouldClearAllAddedUsers(UsersRepository usersRepository, DelegationStore delegationStore) throws UsersRepositoryException {
+                when(usersRepository.contains(ALICE)).thenReturn(true);
+                when(usersRepository.contains(BOB)).thenReturn(true);
+                when(usersRepository.contains(ANDRE)).thenReturn(true);
+
+                Mono.from(delegationStore.addAuthorizedUser(ALICE, BOB)).block();
+                Mono.from(delegationStore.addAuthorizedUser(ALICE, ANDRE)).block();
+
+                when()
+                    .delete(String.format(CLEAR_DELEGATED_USERS_PATH, ALICE.asString()))
+                .then()
+                    .statusCode(HttpStatus.OK_200)
+                    .contentType(ContentType.JSON);
+
+                assertThat(Flux.from(delegationStore.authorizedUsers(ALICE)).collectList().block()).isEmpty();
+            }
+
             @Test
             default void getUsersShouldBeEmptyByDefault() {
                 List<Map<String, String>> users =
@@ -527,6 +774,9 @@ class UserRoutesTest {
         Username.fromLocalPartWithDomain(USERNAME_WITHOUT_DOMAIN.asString(), DOMAIN);
     private static final Username OTHER_USERNAME_WITH_DOMAIN =
         Username.fromLocalPartWithDomain("other", DOMAIN);
+    private static final Username ALICE  = Username.fromLocalPartWithDomain("alice", DOMAIN);
+    private static final Username BOB  = Username.fromLocalPartWithDomain("bob", DOMAIN);
+    private static final Username ANDRE  = Username.fromLocalPartWithDomain("andre", DOMAIN);
     private static final String PASSWORD = "password";
 
     @Nested


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org