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 <hqt...@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": "ced...@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": "ced...@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-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org