diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutes.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutes.java index 3fc748ad37b..edca31a66b0 100644 --- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutes.java +++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutes.java @@ -21,22 +21,31 @@ import static org.apache.james.webadmin.Constants.SEPARATOR; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + import javax.inject.Inject; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; +import org.apache.commons.lang3.NotImplementedException; +import org.apache.commons.lang3.StringUtils; import org.apache.james.core.User; +import org.apache.james.task.Task; import org.apache.james.task.TaskId; import org.apache.james.task.TaskManager; import org.apache.james.webadmin.Constants; import org.apache.james.webadmin.Routes; import org.apache.james.webadmin.dto.TaskIdDto; -import org.apache.james.webadmin.utils.ErrorResponder; import org.apache.james.webadmin.utils.JsonTransformer; import org.eclipse.jetty.http.HttpStatus; +import com.github.steveash.guavate.Guavate; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; @@ -53,9 +62,39 @@ @Produces(Constants.JSON_CONTENT_TYPE) public class DeletedMessagesVaultRoutes implements Routes { + enum UserVaultAction { + RESTORE("restore"); + + static Optional getAction(String value) { + Preconditions.checkNotNull(value, "action cannot be null"); + Preconditions.checkArgument(StringUtils.isNotBlank(value), "action cannot be empty or blank"); + + return Stream.of(values()) + .filter(action -> action.value.equals(value)) + .findFirst(); + } + + private static List plainValues() { + return Stream.of(values()) + .map(UserVaultAction::getValue) + .collect(Guavate.toImmutableList()); + } + + private final String value; + + UserVaultAction(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + public static final String ROOT_PATH = "deletedMessages/user"; private static final String USER_PATH_PARAM = "user"; private static final String RESTORE_PATH = ROOT_PATH + SEPARATOR + ":" + USER_PATH_PARAM; + private static final String ACTION_QUERY_PARAM = "action"; private final RestoreService vaultRestore; private final JsonTransformer jsonTransformer; @@ -77,7 +116,7 @@ public String getBasePath() { @Override public void define(Service service) { - service.post(RESTORE_PATH, this::restore, jsonTransformer); + service.post(RESTORE_PATH, this::userActions, jsonTransformer); } @POST @@ -91,28 +130,51 @@ public void define(Service service) { dataType = "String", defaultValue = "none", example = "user@james.org", - value = "Compulsory. Needs to be a valid username represent for an user had requested to restore deleted emails") + value = "Compulsory. Needs to be a valid username represent for an user had requested to restore deleted emails"), + @ApiImplicitParam( + required = true, + dataType = "String", + name = "action", + paramType = "query", + example = "?action=restore", + value = "Compulsory. Needs to be a valid action represent for an operation to perform on the Deleted Message Vault, " + + "valid action should be in the list (restore)") }) @ApiResponses(value = { @ApiResponse(code = HttpStatus.CREATED_201, message = "Task is created", response = TaskIdDto.class), @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "Bad request - user param is invalid"), @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side.") }) - private TaskIdDto restore(Request request, Response response) { - User userToRestore = extractUser(request); - TaskId taskId = taskManager.submit(new DeletedMessagesVaultRestoreTask(userToRestore, vaultRestore)); + private TaskIdDto userActions(Request request, Response response) { + UserVaultAction requestedAction = extractUserVaultAction(request); + + Task requestedTask = generateTask(requestedAction, request); + TaskId taskId = taskManager.submit(requestedTask); return TaskIdDto.respond(response, taskId); } - private User extractUser(Request request) { - try { - return User.fromUsername(request.params(USER_PATH_PARAM)); - } catch (IllegalArgumentException e) { - throw ErrorResponder.builder() - .statusCode(HttpStatus.BAD_REQUEST_400) - .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) - .message(e.getMessage()) - .haltError(); + private Task generateTask(UserVaultAction requestedAction, Request request) { + User userToRestore = User.fromUsername(request.params(USER_PATH_PARAM)); + + switch (requestedAction) { + case RESTORE: + return new DeletedMessagesVaultRestoreTask(userToRestore, vaultRestore); + default: + throw new NotImplementedException(requestedAction + " is not yet handled."); } } + + private UserVaultAction extractUserVaultAction(Request request) { + String actionParam = request.queryParams(ACTION_QUERY_PARAM); + return Optional.ofNullable(actionParam) + .map(this::getUserVaultAction) + .orElseThrow(() -> new IllegalArgumentException("action parameter is missing")); + } + + private UserVaultAction getUserVaultAction(String actionString) { + return UserVaultAction.getAction(actionString) + .orElseThrow(() -> new IllegalArgumentException(String.format("'%s' is not a valid action. Supported values are: (%s)", + actionString, + Joiner.on(",").join(UserVaultAction.plainValues())))); + } } diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java index 7349d5ad57e..460e3c2a564 100644 --- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java +++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java @@ -116,19 +116,78 @@ void afterEach() { class ValidationTest { @Test - void restoreShouldReturnInvalidWhenUserIsInvalid() { + void restoreShouldReturnInvalidWhenActionIsMissing() { when() + .post(USER.asString()) + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is(notNullValue())) + .body("details", is(notNullValue())); + } + + @Test + void restoreShouldReturnInvalidWhenPassingEmptyAction() { + given() + .queryParam("action", "") + .when() + .post(USER.asString()) + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is(notNullValue())) + .body("details", is(notNullValue())); + } + + @Test + void restoreShouldReturnInvalidWhenActionIsInValid() { + given() + .queryParam("action", "invalid action") + .when() + .post(USER.asString()) + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is(notNullValue())) + .body("details", is(notNullValue())); + } + + @Test + void restoreShouldReturnInvalidWhenPassingCaseInsensitiveAction() { + given() + .queryParam("action", "RESTORE") + .when() + .post(USER.asString()) + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is(notNullValue())) + .body("details", is(notNullValue())); + } + + @Test + void restoreShouldReturnInvalidWhenUserIsInvalid() { + given() + .queryParam("action", "restore") + .when() .post("not@valid@user.com") .then() .statusCode(HttpStatus.BAD_REQUEST_400) .body("statusCode", is(400)) .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) - .body("message", is("The username should not contain multiple domain delimiter.")); + .body("message", is(notNullValue())) + .body("details", is(notNullValue())); } @Test void postShouldReturnNotFoundWhenNoUserPathParameter() { - when() + given() + .queryParam("action", "restore") + .when() .post() .then() .statusCode(HttpStatus.NOT_FOUND_404) @@ -151,11 +210,13 @@ void restoreShouldProduceFailedTaskWhenTheVaultGetsError() { .search(any(), any()); String taskId = with() + .queryParam("action", "restore") .post(USER.asString()) .jsonPath() .get("taskId"); given() + .queryParam("action", "restore") .basePath(TasksRoutes.BASE) .when() .get(taskId + "/await") @@ -185,6 +246,7 @@ void restoreShouldProduceFailedTaskWithErrorRestoreCountWhenMessageAppendGetsErr .appendMessage(any(), any()); String taskId = with() + .queryParam("action", "restore") .post(USER.asString()) .jsonPath() .get("taskId"); @@ -214,6 +276,7 @@ void restoreShouldProduceFailedTaskWhenMailboxMangerGetsError() throws Exception .createMailbox(any(MailboxPath.class), any(MailboxSession.class)); String taskId = with() + .queryParam("action", "restore") .post(USER.asString()) .jsonPath() .get("taskId"); @@ -236,7 +299,9 @@ void restoreShouldProduceFailedTaskWhenMailboxMangerGetsError() throws Exception @Test void restoreShouldReturnATaskCreated() { - when() + given() + .queryParam("action", "restore") + .when() .post(USER.asString()) .then() .statusCode(HttpStatus.CREATED_201) @@ -249,6 +314,7 @@ void restoreShouldProduceASuccessfulTaskWithAdditionalInformation() { vault.append(USER, DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT)).block(); String taskId = with() + .queryParam("action", "restore") .post(USER.asString()) .jsonPath() .get("taskId"); @@ -275,6 +341,7 @@ void restoreShouldKeepAllMessagesInTheVaultOfCorrespondingUser() { vault.append(USER, DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT)).block(); String taskId = with() + .queryParam("action", "restore") .post(USER.asString()) .jsonPath() .get("taskId"); @@ -304,6 +371,7 @@ void restoreShouldNotDeleteExistingMessagesInTheUserMailbox() throws Exception { vault.append(USER, DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT)).block(); String taskId = with() + .queryParam("action", "restore") .post(USER.asString()) .jsonPath() .get("taskId"); @@ -325,6 +393,7 @@ void restoreShouldAppendAllMessageFromVaultToRestoreMailboxOfCorrespondingUser() vault.append(USER, DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT)).block(); String taskId = with() + .queryParam("action", "restore") .post(USER.asString()) .jsonPath() .get("taskId"); @@ -348,6 +417,7 @@ void restoreShouldNotAppendMessagesToAnOtherUserMailbox() throws Exception { vault.append(USER, DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT)).block(); String taskId = with() + .queryParam("action", "restore") .post(USER.asString()) .jsonPath() .get("taskId"); diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md index b00a62f1f18..a66076693a2 100644 --- a/src/site/markdown/server/manage-webadmin.md +++ b/src/site/markdown/server/manage-webadmin.md @@ -2560,17 +2560,22 @@ Here are the following actions available on the 'Deleted Messages Vault' Deleted messages of a specific user can be restored by calling the following endpoint: ``` -curl -XPOST http://ip:port/deletedMessages/user/userToRestore@domain.ext +curl -XPOST http://ip:port/deletedMessages/user/userToRestore@domain.ext?action=restore ``` **All** messages in the Deleted Messages Vault of an specified user will be appended to his 'Restored-Messages' mailbox, which will be created if needed. -**Note**: Restoring matched messages by queries is not supported yet - +**Note**: + - Restoring matched messages by queries is not supported yet + - Query parameter `action` is required and should have value `restore` to represent for restoring feature. Otherwise, a bad request response will be returned + - Query parameter `action` is case sensitive Response code: - 201: Task for restoring deleted has been created - - 400: Bad request, user parameter is invalid + - 400: Bad request: + - action query param is not present + - action query param is not a valid action + - user parameter is invalid The scheduled task will have the following type `deletedMessages/restore` and the following `additionalInformation`: