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/01/11 03:24:03 UTC

[james-project] 03/04: JAMES-3756 JMAP delegation setting should only be set by account owner

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 4db7aa6a543b80e891698a286f9ce6c6da39d51c
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Mon Jan 9 16:46:54 2023 +0700

    JAMES-3756 JMAP delegation setting should only be set by account owner
---
 .../org/apache/james/mailbox/MailboxSession.java   |  6 ++
 .../rfc8621/contract/DelegateSetContract.scala     | 82 ++++++++++++++++++++++
 .../apache/james/jmap/delegation/DelegateSet.scala |  1 +
 .../jmap/method/DelegateSetCreatePerformer.scala   | 20 ++++--
 4 files changed, 102 insertions(+), 7 deletions(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxSession.java b/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxSession.java
index 3ea1460b1e..e0315c4415 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxSession.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxSession.java
@@ -82,6 +82,12 @@ public class MailboxSession {
      */
     public static long SYSTEM_SESSION_ID = 0L;
 
+    public static boolean isPrimaryAccount(MailboxSession mailboxSession) {
+        return mailboxSession.loggedInUser
+            .map(loggedInUser -> loggedInUser.equals(mailboxSession.getUser()))
+            .orElse(false);
+    }
+
     public enum SessionType {
         /**
          * Session was created via the System
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/DelegateSetContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/DelegateSetContract.scala
index 4bf9357816..8c37347389 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/DelegateSetContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/DelegateSetContract.scala
@@ -492,4 +492,86 @@ trait DelegateSetContract {
       assertThat(server.getProbe(classOf[DelegationProbe]).getAuthorizedUsers(BOB).asJavaCollection)
         .containsExactly(ANDRE))
   }
+
+  @Test
+  def shouldReturnNotFoundWhenNotDelegated(): Unit = {
+    val request =
+      s"""{
+         |	"using": ["urn:ietf:params:jmap:core", "urn:apache:james:params:jmap:delegation"],
+         |	"methodCalls": [
+         |		[
+         |			"Delegate/set", {
+         |				"accountId": "$ANDRE_ACCOUNT_ID",
+         |				"create": {
+         |					"4f29": {
+         |						"username": "cedric@domain.tld"
+         |					}
+         |				}
+         |			}, "0"
+         |		]
+         |	]
+         |}""".stripMargin
+
+    val response =  `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1]")
+      .isEqualTo(
+        s"""{
+           |	"type": "accountNotFound"
+           |}""".stripMargin)
+  }
+
+  @Test
+  def bobCanOnlyManageHisPrimaryAccountSetting(server: GuiceJamesServer): Unit = {
+    server.getProbe(classOf[DelegationProbe]).addAuthorizedUser(ANDRE, BOB)
+    val request =
+      s"""{
+         |	"using": ["urn:ietf:params:jmap:core", "urn:apache:james:params:jmap:delegation"],
+         |	"methodCalls": [
+         |		[
+         |			"Delegate/set", {
+         |				"accountId": "$ANDRE_ACCOUNT_ID",
+         |				"create": {
+         |					"4f29": {
+         |						"username": "cedric@domain.tld"
+         |					}
+         |				}
+         |			}, "0"
+         |		]
+         |	]
+         |}""".stripMargin
+
+    val response =  `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].notCreated")
+      .isEqualTo(
+        s"""{
+           |	"4f29": {
+           |		"type": "forbidden",
+           |		"description": "${BOB.asString()} can not manage ${ANDRE.asString()}'s account settings"
+           |	}
+           |}""".stripMargin)
+  }
 }
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegateSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegateSet.scala
index 203a0ca498..c2db36f606 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegateSet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegateSet.scala
@@ -44,6 +44,7 @@ case class DelegateSetResponse(accountId: AccountId,
                                notCreated: Option[Map[DelegateCreationId, SetError]])
 
 case class DelegateSetParseException(setError: SetError) extends IllegalArgumentException
+case class ForbiddenAccountManagement(accessUser: Username, targetUser: Username) extends RuntimeException(s"${accessUser.asString()} can not manage ${targetUser.asString()}'s account settings")
 
 object DelegateSetParseException {
   def from(errors: collection.Seq[(JsPath, collection.Seq[JsonValidationError])]): DelegateSetParseException = {
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateSetCreatePerformer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateSetCreatePerformer.scala
index 789ad15a13..c5e16b745a 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateSetCreatePerformer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateSetCreatePerformer.scala
@@ -23,10 +23,11 @@ import javax.inject.Inject
 import org.apache.james.jmap.core.SetError
 import org.apache.james.jmap.core.SetError.SetErrorDescription
 import org.apache.james.jmap.delegation.DelegationCreation.{knownProperties, serverSetProperty}
-import org.apache.james.jmap.delegation.{DelegateCreationId, DelegateCreationRequest, DelegateCreationResponse, DelegateSetParseException, DelegateSetRequest, DelegationCreation, DelegationId}
+import org.apache.james.jmap.delegation.{DelegateCreationId, DelegateCreationRequest, DelegateCreationResponse, DelegateSetParseException, DelegateSetRequest, DelegationCreation, DelegationId, ForbiddenAccountManagement}
 import org.apache.james.jmap.json.DelegationSerializer
 import org.apache.james.jmap.method.DelegateSetCreatePerformer.{CreationFailure, CreationResult, CreationResults, CreationSuccess}
 import org.apache.james.mailbox.MailboxSession
+import org.apache.james.mailbox.MailboxSession.isPrimaryAccount
 import org.apache.james.mailbox.exception.UserDoesNotExistException
 import org.apache.james.user.api.{DelegationStore, UsersRepository}
 import play.api.libs.json.JsObject
@@ -58,6 +59,7 @@ object DelegateSetCreatePerformer {
       case e: DelegateSetParseException => e.setError
       case e: UserDoesNotExistException => SetError.invalidArguments(SetErrorDescription(e.getMessage))
       case e: IllegalArgumentException => SetError.invalidArguments(SetErrorDescription(e.getMessage))
+      case e: ForbiddenAccountManagement => SetError.forbidden(SetErrorDescription(e.getMessage))
       case _ => SetError.serverFail(SetErrorDescription(e.getMessage))
     }
   }
@@ -83,12 +85,16 @@ class DelegateSetCreatePerformer @Inject()(delegationStore: DelegationStore,
   }
 
   private def create(delegateCreationId: DelegateCreationId, request: DelegateCreationRequest, mailboxSession: MailboxSession): SMono[CreationResult] =
-    SMono.fromPublisher(usersRepository.containsReactive(request.username))
-      .filter(bool => bool)
-      .flatMap(_ => SMono.fromPublisher(delegationStore.addAuthorizedUser(mailboxSession.getUser, request.username))
-        .`then`(SMono.just[CreationResult](CreationSuccess(delegateCreationId, evaluateCreationResponse(request, mailboxSession))))
-        .onErrorResume(e => SMono.just[CreationResult](CreationFailure(delegateCreationId, e))))
-      .switchIfEmpty(SMono.just[CreationResult](CreationFailure(delegateCreationId, new UserDoesNotExistException(request.username))))
+    if (isPrimaryAccount(mailboxSession)) {
+      SMono.fromPublisher(usersRepository.containsReactive(request.username))
+        .filter(bool => bool)
+        .flatMap(_ => SMono.fromPublisher(delegationStore.addAuthorizedUser(mailboxSession.getUser, request.username))
+          .`then`(SMono.just[CreationResult](CreationSuccess(delegateCreationId, evaluateCreationResponse(request, mailboxSession))))
+          .onErrorResume(e => SMono.just[CreationResult](CreationFailure(delegateCreationId, e))))
+        .switchIfEmpty(SMono.just[CreationResult](CreationFailure(delegateCreationId, new UserDoesNotExistException(request.username))))
+    } else {
+      SMono.just(CreationFailure(delegateCreationId, ForbiddenAccountManagement(mailboxSession.getLoggedInUser.get(), mailboxSession.getUser)))
+    }
 
   private def evaluateCreationResponse(request: DelegateCreationRequest, mailboxSession: MailboxSession): DelegateCreationResponse =
     DelegateCreationResponse(id = DelegationId.from(mailboxSession.getUser, request.username))


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