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