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 2023/02/21 01:16:07 UTC

[james-project] 04/09: JAMES-3885 Webadmin route to change of username

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 c8bca8020e42a10281e034fcd279db867fbd38a4
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Feb 16 15:33:29 2023 +0700

    JAMES-3885 Webadmin route to change of username
    
    Simple webadmin wrapper around the service
    
    Tests to exercise the webadmin and service layer logic
---
 .../webadmin/routes/UsernameChangeRoutes.java      |  79 +++++++
 .../webadmin/routes/UsernameChangeRoutesTest.java  | 246 +++++++++++++++++++++
 2 files changed, 325 insertions(+)

diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/UsernameChangeRoutes.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/UsernameChangeRoutes.java
new file mode 100644
index 0000000000..40bb9c6b15
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/UsernameChangeRoutes.java
@@ -0,0 +1,79 @@
+/****************************************************************
+ * 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 javax.inject.Inject;
+
+import org.apache.james.core.Username;
+import org.apache.james.task.TaskManager;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.webadmin.Routes;
+import org.apache.james.webadmin.service.UsernameChangeService;
+import org.apache.james.webadmin.service.UsernameChangeTask;
+import org.apache.james.webadmin.tasks.TaskFromRequestRegistry;
+import org.apache.james.webadmin.tasks.TaskRegistrationKey;
+import org.apache.james.webadmin.utils.JsonTransformer;
+
+import com.google.common.base.Preconditions;
+
+import spark.Route;
+import spark.Service;
+
+public class UsernameChangeRoutes implements Routes {
+    private static final String OLD_USER_PARAM = "oldUser";
+    private static final String NEW_USER_PARAM = "newUser";
+    private static final String ROOT_PATH = "/users/:" + OLD_USER_PARAM + "/rename/:" + NEW_USER_PARAM;
+    private static final TaskRegistrationKey RENAME = TaskRegistrationKey.of("rename");
+
+    private final UsersRepository usersRepository;
+    private final UsernameChangeService service;
+    private final TaskManager taskManager;
+    private final JsonTransformer jsonTransformer;
+
+    @Inject
+    UsernameChangeRoutes(UsersRepository usersRepository, UsernameChangeService service, TaskManager taskManager, JsonTransformer jsonTransformer) {
+        this.usersRepository = usersRepository;
+        this.service = service;
+        this.taskManager = taskManager;
+        this.jsonTransformer = jsonTransformer;
+    }
+
+    @Override
+    public String getBasePath() {
+        return ROOT_PATH;
+    }
+
+    @Override
+    public void define(Service service) {
+        service.post(ROOT_PATH, changeUsername(), jsonTransformer);
+    }
+
+    public Route changeUsername() {
+        return TaskFromRequestRegistry.of(RENAME, request -> {
+            Username oldUser = Username.of(request.params(OLD_USER_PARAM));
+            Username newUser = Username.of(request.params(NEW_USER_PARAM));
+
+            Preconditions.checkArgument(usersRepository.contains(oldUser), "'oldUser' parameter should be an existing user");
+            Preconditions.checkArgument(usersRepository.contains(newUser), "'newUser' parameter should be an existing user");
+
+            return new UsernameChangeTask(service, oldUser, newUser);
+        }).asRoute(taskManager);
+    }
+}
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UsernameChangeRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UsernameChangeRoutesTest.java
new file mode 100644
index 0000000000..5d441aec07
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UsernameChangeRoutesTest.java
@@ -0,0 +1,246 @@
+/****************************************************************
+ * 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 io.restassured.RestAssured.given;
+import static io.restassured.RestAssured.with;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.mockito.Mockito.mock;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.james.core.Domain;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.json.DTOConverter;
+import org.apache.james.rrt.lib.Mapping;
+import org.apache.james.rrt.lib.MappingSource;
+import org.apache.james.task.Hostname;
+import org.apache.james.task.MemoryTaskManager;
+import org.apache.james.user.api.UsernameChangeTaskStep;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.apache.james.webadmin.WebAdminServer;
+import org.apache.james.webadmin.WebAdminUtils;
+import org.apache.james.webadmin.service.UsernameChangeService;
+import org.apache.james.webadmin.service.UsernameChangeTaskAdditionalInformationDTO;
+import org.apache.james.webadmin.utils.ErrorResponder;
+import org.apache.james.webadmin.utils.JsonTransformer;
+import org.eclipse.jetty.http.HttpStatus;
+import org.hamcrest.Matchers;
+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.reactivestreams.Publisher;
+
+import com.google.common.collect.ImmutableSet;
+
+import io.restassured.RestAssured;
+import io.restassured.http.ContentType;
+import reactor.core.publisher.Mono;
+
+class UsernameChangeRoutesTest {
+
+    private static final Username OLD_USER = Username.of("jessy.jones@domain.tld");
+    private static final Username NEW_USER = Username.of("jessy.smith@domain.tld");
+
+    public static class StepImpl implements UsernameChangeTaskStep {
+        private final StepName name;
+        private final int priority;
+        private final Mono<Void> behaviour;
+
+        public StepImpl(StepName name, int priority, Mono<Void> behaviour) {
+            this.name = name;
+            this.priority = priority;
+            this.behaviour = behaviour;
+        }
+
+        @Override
+        public StepName name() {
+            return name;
+        }
+
+        @Override
+        public int priority() {
+            return priority;
+        }
+
+        @Override
+        public Publisher<Void> changeUsername(Username oldUsername, Username newUsername) {
+            return behaviour;
+        }
+    }
+
+    private MemoryUsersRepository usersRepository;
+
+    WebAdminServer setUp(ImmutableSet<UsernameChangeTaskStep> steps) {
+        MemoryTaskManager taskManager = new MemoryTaskManager(new Hostname("foo"));
+        UsernameChangeService service = new UsernameChangeService(steps);
+        WebAdminServer webAdminServer = WebAdminUtils
+            .createWebAdminServer(new UsernameChangeRoutes(usersRepository, service, taskManager, new JsonTransformer()),
+                new TasksRoutes(taskManager, new JsonTransformer(), DTOConverter.of(UsernameChangeTaskAdditionalInformationDTO.module())))
+            .start();
+
+        RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminServer)
+            .build();
+
+        return webAdminServer;
+    }
+
+    @BeforeEach
+    void setUpUsersRepo() throws Exception {
+        DNSService dnsService = mock(DNSService.class);
+        MemoryDomainList domainList = new MemoryDomainList(dnsService);
+        domainList.configure(DomainListConfiguration.DEFAULT);
+        domainList.addDomain(Domain.of("domain.tld"));
+        usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
+    }
+
+    @Nested
+    class BasicTests {
+        private WebAdminServer webAdminServer;
+        private AtomicBoolean behaviour1;
+        private AtomicBoolean behaviour2;
+
+        @BeforeEach
+        void setUp() throws Exception {
+            behaviour1 = new AtomicBoolean(false);
+            behaviour2 = new AtomicBoolean(false);
+            webAdminServer = UsernameChangeRoutesTest.this.setUp(
+                ImmutableSet.of(new StepImpl(new UsernameChangeTaskStep.StepName("A"), 35, Mono.fromRunnable(() -> behaviour1.set(true))),
+                    new StepImpl(new UsernameChangeTaskStep.StepName("B"), 3, Mono.fromRunnable(() -> behaviour2.set(true)))));
+
+            usersRepository.addUser(OLD_USER, "pass");
+            usersRepository.addUser(NEW_USER, "pass");
+        }
+
+        @AfterEach
+        void stop() {
+            webAdminServer.destroy();
+        }
+
+        @Test
+        void shouldPerformMigration() {
+            String taskId = with()
+                .queryParam("action", "rename")
+                .post("/users/" + OLD_USER.asString() + "/rename/" + NEW_USER.asString())
+                .jsonPath()
+                .get("taskId");
+
+            given()
+                .basePath(TasksRoutes.BASE)
+            .when()
+                .get(taskId + "/await")
+            .then()
+                .body("type", is("UsernameChangeTask"))
+                .body("status", is("completed"))
+                .body("additionalInformation.type", is("UsernameChangeTask"))
+                .body("additionalInformation.oldUser", is("jessy.jones@domain.tld"))
+                .body("additionalInformation.newUser", is("jessy.smith@domain.tld"))
+                .body("additionalInformation.status.A", is("DONE"))
+                .body("additionalInformation.status.B", is("DONE"));
+
+            assertThat(behaviour1.get()).isTrue();
+            assertThat(behaviour2.get()).isTrue();
+        }
+
+        @Test
+        void shouldRejectUnknownDestinationUser() {
+            given()
+                .queryParam("action", "rename")
+            .when()
+                .post("/users/" + OLD_USER.asString() + "/rename/unknown@domain.tld")
+            .then()
+                .statusCode(HttpStatus.BAD_REQUEST_400)
+                .body("statusCode", Matchers.is(400))
+                .body("type", Matchers.is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+                .body("message", Matchers.is("Invalid arguments supplied in the user request"))
+                .body("details", Matchers.is("'newUser' parameter should be an existing user"));
+        }
+
+        @Test
+        void shouldRejectUnknownSourceUser() {
+            given()
+                .queryParam("action", "rename")
+            .when()
+                .post("/users/unknown@domain.tld/rename/" + NEW_USER.asString())
+            .then()
+                .statusCode(HttpStatus.BAD_REQUEST_400)
+                .body("statusCode", Matchers.is(400))
+                .body("type", Matchers.is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+                .body("message", Matchers.is("Invalid arguments supplied in the user request"))
+                .body("details", Matchers.is("'oldUser' parameter should be an existing user"));
+        }
+    }
+
+    @Nested
+    class ErrorTests {
+        private WebAdminServer webAdminServer;
+        private AtomicBoolean behaviour1;
+        private AtomicBoolean behaviour2;
+
+        @BeforeEach
+        void setUp() throws Exception {
+            behaviour1 = new AtomicBoolean(false);
+            behaviour2 = new AtomicBoolean(false);
+            webAdminServer = UsernameChangeRoutesTest.this.setUp(
+                ImmutableSet.of(new StepImpl(new UsernameChangeTaskStep.StepName("A"), 1, Mono.fromRunnable(() -> behaviour1.set(true))),
+                    new StepImpl(new UsernameChangeTaskStep.StepName("B"), 2, Mono.error(RuntimeException::new)),
+                    new StepImpl(new UsernameChangeTaskStep.StepName("C"), 3, Mono.fromRunnable(() -> behaviour2.set(true)))));
+
+            usersRepository.addUser(OLD_USER, "pass");
+            usersRepository.addUser(NEW_USER, "pass");
+        }
+
+        @AfterEach
+        void stop() {
+            webAdminServer.destroy();
+        }
+
+        @Test
+        void shouldReportFailures() {
+            String taskId = with()
+                .queryParam("action", "rename")
+                .post("/users/" + OLD_USER.asString() + "/rename/" + NEW_USER.asString())
+                .jsonPath()
+                .get("taskId");
+
+            given()
+                .basePath(TasksRoutes.BASE)
+            .when()
+                .get(taskId + "/await")
+            .then()
+                .body("type", is("UsernameChangeTask"))
+                .body("status", is("failed"))
+                .body("additionalInformation.type", is("UsernameChangeTask"))
+                .body("additionalInformation.oldUser", is("jessy.jones@domain.tld"))
+                .body("additionalInformation.newUser", is("jessy.smith@domain.tld"))
+                .body("additionalInformation.status.A", is("DONE"))
+                .body("additionalInformation.status.B", is("FAILED"))
+                .body("additionalInformation.status.C", is("ABORTED"));
+
+            assertThat(behaviour1.get()).isTrue();
+            assertThat(behaviour2.get()).isFalse();
+        }
+    }
+}
\ No newline at end of file


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