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

commit 76d999ef8a2a9676430e51b892b9ab9898e0709f
Author: Benoit TELLIER <[email protected]>
AuthorDate: Wed Mar 18 10:59:23 2026 +0100

    JAMES-4185 Allow working with the vault of deleted users
---
 .../modules/servers/partials/operate/webadmin.adoc | 28 ++++++++++++-----
 .../vault/routes/DeletedMessagesVaultRoutes.java   | 14 +++++----
 .../routes/DeletedMessagesVaultRoutesTest.java     | 33 ++++++++++++++++++++
 src/site/markdown/server/manage-webadmin.md        | 35 ++++++++++++++--------
 4 files changed, 84 insertions(+), 26 deletions(-)

diff --git a/docs/modules/servers/partials/operate/webadmin.adoc 
b/docs/modules/servers/partials/operate/webadmin.adoc
index 11b1f955fa..a707c3c20e 100644
--- a/docs/modules/servers/partials/operate/webadmin.adoc
+++ b/docs/modules/servers/partials/operate/webadmin.adoc
@@ -4603,7 +4603,7 @@ configure the DeletedMessageVault PreDeletionHook.
 Deleted messages of a specific user can be listed by calling the following 
endpoint:
 
 ....
-curl -XPOST http://ip:port/deletedMessages/users/[email protected]/messages
+curl -XPOST 
http://ip:port/deletedMessages/users/[email protected]/messages[?force=true]
 
 {
   "combinator": "and",
@@ -4642,6 +4642,9 @@ Pass an empty criteria list to retrieve all deleted 
messages:
 }
 ....
 
+*Note*: The optional `force` query parameter (`?force=true`) bypasses the user 
existence check.
+This is useful for browsing the vault of a deleted user or a virtual user.
+
 Response code:
 
 * 200: List of deleted messages matching the query
@@ -4650,7 +4653,7 @@ Response code:
 ** can not parse the JSON body
 ** Json query object contains unsupported operator, fieldName
 ** Json query object values violate parsing rules
-* 404: User not found
+* 404: User not found (bypassed when `force=true`)
 
 Response body is a JSON array:
 
@@ -4681,7 +4684,7 @@ Deleted messages of a specific user can be restored by 
calling the
 following endpoint:
 
 ....
-curl -XPOST 
http://ip:port/deletedMessages/users/[email protected]?action=restore
+curl -XPOST 
http://ip:port/deletedMessages/users/[email protected]?action=restore[&force=true]
 
 {
   "combinator": "and",
@@ -4830,6 +4833,9 @@ comparators to apply on the String data of other special 
locales stored
 in the Vault. More details at
 https://issues.apache.org/jira/browse/MAILBOX-384[JIRA]
 
+*Note*: The optional `force` query parameter (`?force=true` or 
`?action=restore&force=true`) bypasses
+the user existence check. This is useful for restoring the vault of a deleted 
user or a virtual user.
+
 Response code:
 
 * 201: Task for restoring deleted has been created
@@ -4840,7 +4846,7 @@ Response code:
 ** can not parse the JSON body
 ** Json query object contains unsupported operator, fieldName
 ** Json query object values violate parsing rules
-* 404: User not found
+* 404: User not found (bypassed when `force=true`)
 
 link:#_endpoints_returning_a_task[More details about endpoints returning
 a task].
@@ -4868,7 +4874,7 @@ Retrieve deleted messages matched with requested query 
from an user then
 share the content to a targeted mail address (exportTo)
 
 ....
-curl -XPOST 
'http://ip:port/deletedMessages/users/[email protected]?action=export&[email protected]'
+curl -XPOST 
'http://ip:port/deletedMessages/users/[email protected]?action=export&[email protected][&force=true]'
 
 BODY: is the json query has the same structure with Restore Deleted Messages 
section
 ....
@@ -4877,6 +4883,9 @@ BODY: is the json query has the same structure with 
Restore Deleted Messages sec
 restrictions like in link:#_restore_deleted_messages[Restore Deleted
 Messages]
 
+*Note*: The optional `force` query parameter (`&force=true`) bypasses the user 
existence check.
+This is useful for exporting the vault of a deleted user or a virtual user.
+
 Response code:
 
 * 201: Task for exporting has been created
@@ -4889,7 +4898,7 @@ Response code:
 ** can not parse the JSON body
 ** Json query object contains unsupported operator, fieldName
 ** Json query object values violate parsing rules
-* 404: User not found
+* 404: User not found (bypassed when `force=true`)
 
 link:#_endpoints_returning_a_task[More details about endpoints returning
 a task].
@@ -4942,19 +4951,22 @@ You may want to call this endpoint on a regular basis.
 Delete a Deleted Message with `MessageId`
 
 ....
-curl -XDELETE 
http://ip:port/deletedMessages/users/[email protected]/messages/3294a976-ce63-491e-bd52-1b6f465ed7a2
+curl -XDELETE 
http://ip:port/deletedMessages/users/[email protected]/messages/3294a976-ce63-491e-bd52-1b6f465ed7a2[?force=true]
 ....
 
 link:#_endpoints_returning_a_task[More details about endpoints returning
 a task].
 
+*Note*: The optional `force` query parameter (`?force=true`) bypasses the user 
existence check.
+This is useful for removing a message from the vault of a deleted user or a 
virtual user.
+
 Response code:
 
 * 201: Task for deleting message has been created
 * 400: Bad request:
 ** user parameter is invalid
 ** messageId parameter is invalid
-* 404: User not found
+* 404: User not found (bypassed when `force=true`)
 
 The scheduled task will have the following type
 `deleted-messages-delete` and the following `additionalInformation`:
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 deb7955b15..c3637313fe 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
@@ -70,6 +70,7 @@ public class DeletedMessagesVaultRoutes implements Routes {
     private static final String DELETE_PATH = MESSAGES_PATH + SEPARATOR + 
MESSAGE_ID_PARAM;
     private static final String SCOPE_QUERY_PARAM = "scope";
     private static final String EXPORT_TO_QUERY_PARAM = "exportTo";
+    private static final String FORCE_QUERY_PARAM = "force";
 
     private final RestoreService vaultRestore;
     private final ExportService vaultExport;
@@ -121,13 +122,13 @@ public class DeletedMessagesVaultRoutes implements Routes 
{
 
     private Task export(Request request) throws JsonExtractException {
         Username username = extractUser(request);
-        validateUserExist(username);
+        validateUserExist(request, username);
         return new DeletedMessagesVaultExportTask(vaultExport, username, 
extractQuery(request), extractMailAddress(request));
     }
 
     private Object browseMessages(Request request, spark.Response response) 
throws JsonExtractException {
         Username username = extractUser(request);
-        validateUserExist(username);
+        validateUserExist(request, username);
         Query query = extractQuery(request);
         return Flux.from(deletedMessageVault.search(username, query))
             .map(DeletedMessageDTO::from)
@@ -137,7 +138,7 @@ public class DeletedMessagesVaultRoutes implements Routes {
 
     private Task restore(Request request) throws JsonExtractException {
         Username username = extractUser(request);
-        validateUserExist(username);
+        validateUserExist(request, username);
         return new DeletedMessagesVaultRestoreTask(vaultRestore, username, 
extractQuery(request));
     }
 
@@ -150,13 +151,16 @@ public class DeletedMessagesVaultRoutes implements Routes 
{
 
     private Task deleteMessage(Request request) {
         Username username = extractUser(request);
-        validateUserExist(username);
+        validateUserExist(request, username);
         MessageId messageId = parseMessageId(request);
 
         return new DeletedMessagesVaultDeleteTask(deletedMessageVault, 
username, messageId);
     }
 
-    private void validateUserExist(Username username) {
+    private void validateUserExist(Request request, Username username) {
+        if (Boolean.parseBoolean(request.queryParams(FORCE_QUERY_PARAM))) {
+            return;
+        }
         try {
             if (!usersRepository.contains(username)) {
                 throw ErrorResponder.builder()
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 55c17a7f04..269f9d76da 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
@@ -397,6 +397,19 @@ class DeletedMessagesVaultRoutesTest {
                 .body("message", is(notNullValue()));
         }
 
+        @Test
+        void restoreShouldBypassUserExistenceCheckWhenForceIsTrue() {
+            given()
+                .queryParam("action", "restore")
+                .queryParam("force", "true")
+                .body(MATCH_ALL_QUERY)
+            .when()
+                .post(USERS + SEPARATOR + USERNAME_2.asString())
+            .then()
+                .statusCode(HttpStatus.CREATED_201)
+                .body("taskId", is(notNullValue()));
+        }
+
         @ParameterizedTest
         @ValueSource(strings = {"restore", "export"})
         void userVaultAPIShouldReturnNotFoundWhenNoUserPathParameter(String 
action) {
@@ -2308,6 +2321,15 @@ class DeletedMessagesVaultRoutesTest {
                     .body("message", is(notNullValue()));
             }
 
+            @Test
+            void deleteShouldBypassUserExistenceCheckWhenForceIsTrue() {
+                when()
+                    .delete(USERS + SEPARATOR + USERNAME_2.asString() + 
SEPARATOR + DELETED_MESSAGE_PARAM_PATH + "?force=true")
+                .then()
+                    .statusCode(HttpStatus.CREATED_201)
+                    .body("taskId", is(notNullValue()));
+            }
+
             @Test
             void deleteShouldReturnInvalidWhenMessageIdIsInvalid() {
                 when()
@@ -2380,6 +2402,17 @@ class DeletedMessagesVaultRoutesTest {
                 .statusCode(HttpStatus.NOT_FOUND_404);
         }
 
+        @Test
+        void browseMessagesShouldBypassUserExistenceCheckWhenForceIsTrue() {
+            given()
+                .queryParam("force", "true")
+                .body(MATCH_ALL_QUERY)
+            .when()
+                .post(USERS + SEPARATOR + "[email protected]" + SEPARATOR + 
MESSAGE_PATH_PARAM)
+            .then()
+                .statusCode(HttpStatus.OK_200);
+        }
+
         @Test
         void browseMessagesShouldReturn400WhenQueryBodyIsInvalid() {
             given()
diff --git a/src/site/markdown/server/manage-webadmin.md 
b/src/site/markdown/server/manage-webadmin.md
index be6e2f3d51..ed32669692 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -4682,7 +4682,7 @@ 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/users/[email protected]?action=restore
+curl -XPOST 
http://ip:port/deletedMessages/users/[email protected]?action=restore[&force=true]
 
 {
   "combinator": "and",
@@ -4794,20 +4794,23 @@ Messages in the Deleted Messages Vault of a specified 
user that are matched with
 }
 ```
 
-**Warning**: Current web-admin uses `US` locale as the default. Therefore, 
there might be some conflicts when using String `containsIgnoreCase` 
comparators to apply 
-on the String data of other special locales stored in the Vault. More details 
at [JIRA](https://issues.apache.org/jira/browse/MAILBOX-384) 
+**Warning**: Current web-admin uses `US` locale as the default. Therefore, 
there might be some conflicts when using String `containsIgnoreCase` 
comparators to apply
+on the String data of other special locales stored in the Vault. More details 
at [JIRA](https://issues.apache.org/jira/browse/MAILBOX-384)
+
+**Note**: The optional `force` query parameter (`&force=true`) bypasses the 
user existence check.
+This is useful for restoring the vault of a deleted user or a virtual user.
 
 Response code:
 
  - 201: Task for restoring deleted has been created
- - 400: Bad request: 
+ - 400: Bad request:
    - action query param is not present
    - action query param is not a valid action
    - user parameter is invalid
    - can not parse the JSON body
    - Json query object contains unsupported operator, fieldName
-   - Json query object values violate parsing rules 
- - 404: User not found
+   - Json query object values violate parsing rules
+ - 404: User not found (bypassed when `force=true`)
  
 [More details about endpoints returning a task](#Endpoints_returning_a_task).
 
@@ -4832,16 +4835,19 @@ while:
 Retrieve deleted messages matched with requested query from an user then share 
the content to a targeted mail address (exportTo)
 
 ```
-curl -XPOST 
'http://ip:port/deletedMessages/users/[email protected]?action=export&[email protected]'
+curl -XPOST 
'http://ip:port/deletedMessages/users/[email protected]?action=export&[email protected][&force=true]'
 
 BODY: is the json query has the same structure with Restore Deleted Messages 
section
 ```
 **Note**: Json query passing into the body follows the same rules & 
restrictions like in [Restore Deleted Messages](#Restore_deleted_messages)
 
+**Note**: The optional `force` query parameter (`&force=true`) bypasses the 
user existence check.
+This is useful for exporting the vault of a deleted user or a virtual user.
+
 Response code:
 
  - 201: Task for exporting has been created
- - 400: Bad request: 
+ - 400: Bad request:
    - exportTo query param is not present
    - exportTo query param is not a valid mail address
    - action query param is not present
@@ -4849,8 +4855,8 @@ Response code:
    - user parameter is invalid
    - can not parse the JSON body
    - Json query object contains unsupported operator, fieldName
-   - Json query object values violate parsing rules 
- - 404: User not found
+   - Json query object values violate parsing rules
+ - 404: User not found (bypassed when `force=true`)
 
 [More details about endpoints returning a task](#Endpoints_returning_a_task).
 
@@ -4895,18 +4901,21 @@ You may want to call this endpoint on a regular basis.
 Delete a Deleted Message with `MessageId`
 
 ```
-curl -XDELETE 
http://ip:port/deletedMessages/users/[email protected]/messages/3294a976-ce63-491e-bd52-1b6f465ed7a2
+curl -XDELETE 
http://ip:port/deletedMessages/users/[email protected]/messages/3294a976-ce63-491e-bd52-1b6f465ed7a2[?force=true]
 ```
 
 [More details about endpoints returning a task](#Endpoints_returning_a_task).
 
+**Note**: The optional `force` query parameter (`?force=true`) bypasses the 
user existence check.
+This is useful for removing a message from the vault of a deleted user or a 
virtual user.
+
 Response code:
 
  - 201: Task for deleting message has been created
- - 400: Bad request: 
+ - 400: Bad request:
    - user parameter is invalid
    - messageId parameter is invalid
- - 404: User not found
+ - 404: User not found (bypassed when `force=true`)
  
 The scheduled task will have the following type `deleted-messages-delete` and 
the following `additionalInformation`:
  


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to