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