From 18411f8e9e3630b40ff7eb00576eb238b30a2633 Mon Sep 17 00:00:00 2001 From: Maurus Decimus <11444311+mdecimus@users.noreply.github.com> Date: Thu, 18 Jun 2026 19:05:07 +0200 Subject: [PATCH] Fix JMAP: Require `accountId` argument on requests --- CHANGELOG.md | 1 + crates/jmap-proto/src/method/availability.rs | 2 +- crates/jmap-proto/src/method/changes.rs | 2 +- crates/jmap-proto/src/method/copy.rs | 8 +- crates/jmap-proto/src/method/get.rs | 2 +- crates/jmap-proto/src/method/import.rs | 2 +- crates/jmap-proto/src/method/lookup.rs | 2 +- crates/jmap-proto/src/method/parse.rs | 2 +- crates/jmap-proto/src/method/query.rs | 2 +- crates/jmap-proto/src/method/query_changes.rs | 2 +- .../jmap-proto/src/method/search_snippet.rs | 2 +- crates/jmap-proto/src/method/set.rs | 2 +- crates/jmap-proto/src/method/upload.rs | 2 +- crates/jmap-proto/src/method/validate.rs | 2 +- crates/jmap-proto/src/request/mod.rs | 13 ++ crates/jmap/src/api/request.rs | 159 ++++++++++-------- crates/jmap/src/changes/query.rs | 38 ++++- tests/src/utils/jmap.rs | 2 + 18 files changed, 149 insertions(+), 96 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8aa645fe..3121580c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If - JMAP conformance (pass the [jmap-test-suite](https://github.com/jmapio/jmap-test-suite) tests): - Methods are only available if their capability is in `using`. - Reject requests that do not specify `application/json` in the `Content-Type` header. + - Require `accountId` argument on requests. - Default calendars and address books are not subscribed by default. - `*/set`: Unchanged immutable `id` property is rejected on update. - `*/query` and `*/queryChanges`: null` rejected as `notRequest`. diff --git a/crates/jmap-proto/src/method/availability.rs b/crates/jmap-proto/src/method/availability.rs index a97b2657..26fe5092 100644 --- a/crates/jmap-proto/src/method/availability.rs +++ b/crates/jmap-proto/src/method/availability.rs @@ -55,7 +55,7 @@ impl<'de> DeserializeArguments<'de> for GetAvailabilityRequest { { hashify::fnc_map!(key.as_bytes(), b"accountId" => { - self.account_id = map.next_value()?; + self.account_id = crate::request::deserialize_account_id(map)?; }, b"utcStart" => { self.utc_start = map.next_value()?; diff --git a/crates/jmap-proto/src/method/changes.rs b/crates/jmap-proto/src/method/changes.rs index c7d7de29..0e3423c2 100644 --- a/crates/jmap-proto/src/method/changes.rs +++ b/crates/jmap-proto/src/method/changes.rs @@ -52,7 +52,7 @@ impl<'de> DeserializeArguments<'de> for ChangesRequest { { hashify::fnc_map!(key.as_bytes(), b"accountId" => { - self.account_id = map.next_value()?; + self.account_id = crate::request::deserialize_account_id(map)?; }, b"sinceState" => { self.since_state = map.next_value()?; diff --git a/crates/jmap-proto/src/method/copy.rs b/crates/jmap-proto/src/method/copy.rs index 7a01cb4f..47eeb8cc 100644 --- a/crates/jmap-proto/src/method/copy.rs +++ b/crates/jmap-proto/src/method/copy.rs @@ -85,13 +85,13 @@ impl<'de, T: JmapObject> DeserializeArguments<'de> for CopyRequest<'de, T> { { hashify::fnc_map!(key.as_bytes(), b"accountId" => { - self.account_id = map.next_value()?; + self.account_id = crate::request::deserialize_account_id(map)?; }, b"ifInState" => { self.if_in_state = map.next_value()?; }, b"fromAccountId" => { - self.from_account_id = map.next_value()?; + self.from_account_id = crate::request::deserialize_account_id(map)?; }, b"ifFromInState" => { self.if_from_in_state = map.next_value()?; @@ -121,10 +121,10 @@ impl<'de> DeserializeArguments<'de> for CopyBlobRequest { { hashify::fnc_map!(key.as_bytes(), b"accountId" => { - self.account_id = map.next_value()?; + self.account_id = crate::request::deserialize_account_id(map)?; }, b"fromAccountId" => { - self.from_account_id = map.next_value()?; + self.from_account_id = crate::request::deserialize_account_id(map)?; }, b"blobIds" => { self.blob_ids = map.next_value()?; diff --git a/crates/jmap-proto/src/method/get.rs b/crates/jmap-proto/src/method/get.rs index f5b46f92..b6cd707f 100644 --- a/crates/jmap-proto/src/method/get.rs +++ b/crates/jmap-proto/src/method/get.rs @@ -47,7 +47,7 @@ impl<'de, T: JmapObject> DeserializeArguments<'de> for GetRequest { { hashify::fnc_map!(key.as_bytes(), b"accountId" => { - self.account_id = map.next_value()?; + self.account_id = crate::request::deserialize_account_id(map)?; }, b"ids" => { self.ids = map.next_value::>>>()?.map(MaybeResultReference::Value); diff --git a/crates/jmap-proto/src/method/import.rs b/crates/jmap-proto/src/method/import.rs index 1fc38289..535a1913 100644 --- a/crates/jmap-proto/src/method/import.rs +++ b/crates/jmap-proto/src/method/import.rs @@ -67,7 +67,7 @@ impl<'de> DeserializeArguments<'de> for ImportEmailRequest { { hashify::fnc_map!(key.as_bytes(), b"accountId" => { - self.account_id = map.next_value()?; + self.account_id = crate::request::deserialize_account_id(map)?; }, b"ifInState" => { self.if_in_state = map.next_value()?; diff --git a/crates/jmap-proto/src/method/lookup.rs b/crates/jmap-proto/src/method/lookup.rs index e5ce00d2..f724e0b1 100644 --- a/crates/jmap-proto/src/method/lookup.rs +++ b/crates/jmap-proto/src/method/lookup.rs @@ -45,7 +45,7 @@ impl<'de> DeserializeArguments<'de> for BlobLookupRequest { { hashify::fnc_map!(key.as_bytes(), b"accountId" => { - self.account_id = map.next_value()?; + self.account_id = crate::request::deserialize_account_id(map)?; }, b"typeNames" => { self.type_names = map.next_value()?; diff --git a/crates/jmap-proto/src/method/parse.rs b/crates/jmap-proto/src/method/parse.rs index 9af13f02..71ce0869 100644 --- a/crates/jmap-proto/src/method/parse.rs +++ b/crates/jmap-proto/src/method/parse.rs @@ -50,7 +50,7 @@ impl<'de, T: JmapObject> DeserializeArguments<'de> for ParseRequest { { hashify::fnc_map!(key.as_bytes(), b"accountId" => { - self.account_id = map.next_value()?; + self.account_id = crate::request::deserialize_account_id(map)?; }, b"blobIds" => { self.blob_ids = map.next_value()?; diff --git a/crates/jmap-proto/src/method/query.rs b/crates/jmap-proto/src/method/query.rs index 886c9bfd..14516f80 100644 --- a/crates/jmap-proto/src/method/query.rs +++ b/crates/jmap-proto/src/method/query.rs @@ -87,7 +87,7 @@ impl<'de, T: JmapObject> DeserializeArguments<'de> for QueryRequest { { hashify::fnc_map!(key.as_bytes(), b"accountId" => { - self.account_id = map.next_value()?; + self.account_id = crate::request::deserialize_account_id(map)?; }, b"filter" => { self.filter = map.next_value::>()?.0; diff --git a/crates/jmap-proto/src/method/query_changes.rs b/crates/jmap-proto/src/method/query_changes.rs index 343f7649..a4e3031a 100644 --- a/crates/jmap-proto/src/method/query_changes.rs +++ b/crates/jmap-proto/src/method/query_changes.rs @@ -66,7 +66,7 @@ impl<'de, T: JmapObject> DeserializeArguments<'de> for QueryChangesRequest { { hashify::fnc_map!(key.as_bytes(), b"accountId" => { - self.account_id = map.next_value()?; + self.account_id = crate::request::deserialize_account_id(map)?; }, b"filter" => { self.filter = map.next_value::>()?.0; diff --git a/crates/jmap-proto/src/method/search_snippet.rs b/crates/jmap-proto/src/method/search_snippet.rs index 70ddbe7a..71f43d78 100644 --- a/crates/jmap-proto/src/method/search_snippet.rs +++ b/crates/jmap-proto/src/method/search_snippet.rs @@ -53,7 +53,7 @@ impl<'de> DeserializeArguments<'de> for GetSearchSnippetRequest { { hashify::fnc_map!(key.as_bytes(), b"accountId" => { - self.account_id = map.next_value()?; + self.account_id = crate::request::deserialize_account_id(map)?; }, b"filter" => { self.filter = map.next_value::>()?.0; diff --git a/crates/jmap-proto/src/method/set.rs b/crates/jmap-proto/src/method/set.rs index fa4172ad..724be701 100644 --- a/crates/jmap-proto/src/method/set.rs +++ b/crates/jmap-proto/src/method/set.rs @@ -80,7 +80,7 @@ impl<'de, T: JmapObject> DeserializeArguments<'de> for SetRequest<'de, T> { { hashify::fnc_map!(key.as_bytes(), b"accountId" => { - self.account_id = map.next_value()?; + self.account_id = crate::request::deserialize_account_id(map)?; }, b"ifInState" => { self.if_in_state = map.next_value()?; diff --git a/crates/jmap-proto/src/method/upload.rs b/crates/jmap-proto/src/method/upload.rs index 3f4e1b09..a751f590 100644 --- a/crates/jmap-proto/src/method/upload.rs +++ b/crates/jmap-proto/src/method/upload.rs @@ -76,7 +76,7 @@ impl<'de> DeserializeArguments<'de> for BlobUploadRequest { { hashify::fnc_map!(key.as_bytes(), b"accountId" => { - self.account_id = map.next_value()?; + self.account_id = crate::request::deserialize_account_id(map)?; }, b"create" => { self.create = map.next_value()?; diff --git a/crates/jmap-proto/src/method/validate.rs b/crates/jmap-proto/src/method/validate.rs index c8f5b88d..dcce912f 100644 --- a/crates/jmap-proto/src/method/validate.rs +++ b/crates/jmap-proto/src/method/validate.rs @@ -35,7 +35,7 @@ impl<'de> DeserializeArguments<'de> for ValidateSieveScriptRequest { { hashify::fnc_map!(key.as_bytes(), b"accountId" => { - self.account_id = map.next_value()?; + self.account_id = crate::request::deserialize_account_id(map)?; }, b"blobId" => { self.blob_id = map.next_value()?; diff --git a/crates/jmap-proto/src/request/mod.rs b/crates/jmap-proto/src/request/mod.rs index 3b25284a..73ed18fd 100644 --- a/crates/jmap-proto/src/request/mod.rs +++ b/crates/jmap-proto/src/request/mod.rs @@ -41,8 +41,21 @@ use crate::{ }; use jmap_tools::{Null, Value}; use std::{collections::HashMap, fmt::Debug, str::FromStr}; +use types::id::Id; use utils::map::vec_map::VecMap; +pub const INVALID_ACCOUNT_ID: u64 = u64::MAX - 1; + +pub fn deserialize_account_id<'de, A>(map: &mut A) -> Result +where + A: serde::de::MapAccess<'de>, +{ + Ok(map + .next_value::>()? + .try_unwrap() + .unwrap_or_else(|| Id::from(INVALID_ACCOUNT_ID))) +} + #[derive(Debug)] pub struct Request<'x> { pub using: CapabilityIds, diff --git a/crates/jmap/src/api/request.rs b/crates/jmap/src/api/request.rs index b815df82..8ccd5445 100644 --- a/crates/jmap/src/api/request.rs +++ b/crates/jmap/src/api/request.rs @@ -49,8 +49,10 @@ use common::{Server, auth::AccessToken}; use http_proto::HttpSessionData; use jmap_proto::{ request::{ - Call, CopyRequestMethod, GetRequestMethod, ParseRequestMethod, QueryRequestMethod, Request, - RequestMethod, SetRequestMethod, capability::Capability, method::MethodName, + Call, CopyRequestMethod, GetRequestMethod, INVALID_ACCOUNT_ID, ParseRequestMethod, + QueryRequestMethod, Request, RequestMethod, SetRequestMethod, + capability::Capability, + method::{MethodName, MethodObject}, }, response::{Response, ResponseMethod, SetResponseMethod}, }; @@ -249,47 +251,47 @@ impl RequestHandler for Server { let response = match method { RequestMethod::Get(req) => match req { GetRequestMethod::Email(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::Email)?; self.email_get(*req, access_token).await?.into() } GetRequestMethod::Mailbox(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::Mailbox)?; self.mailbox_get(*req, access_token).await?.into() } GetRequestMethod::Thread(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::Email)?; self.thread_get(*req, access_token).await?.into() } GetRequestMethod::Identity(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.identity_get(*req).await?.into() } GetRequestMethod::EmailSubmission(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.email_submission_get(*req).await?.into() } GetRequestMethod::PushSubscription(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; self.push_subscription_get(*req, access_token).await?.into() } GetRequestMethod::Sieve(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.sieve_script_get(*req).await?.into() } GetRequestMethod::VacationResponse(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.vacation_response_get(*req).await?.into() @@ -298,31 +300,31 @@ impl RequestHandler for Server { self.principal_get(*req, access_token).await?.into() } GetRequestMethod::Quota(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.quota_get(*req, access_token).await?.into() } GetRequestMethod::Blob(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.blob_get(*req, access_token).await?.into() } GetRequestMethod::AddressBook(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::AddressBook)?; self.address_book_get(*req, access_token).await?.into() } GetRequestMethod::ContactCard(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::ContactCard)?; self.contact_card_get(*req, access_token).await?.into() } GetRequestMethod::FileNode(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::FileNode)?; self.file_node_get(*req, access_token).await?.into() @@ -332,19 +334,19 @@ impl RequestHandler for Server { .await? .into(), GetRequestMethod::Calendar(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::Calendar)?; self.calendar_get(*req, access_token).await?.into() } GetRequestMethod::CalendarEvent(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::CalendarEvent)?; self.calendar_event_get(*req, access_token).await?.into() } GetRequestMethod::CalendarEventNotification(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.calendar_event_notification_get(*req, access_token) @@ -352,19 +354,19 @@ impl RequestHandler for Server { .into() } GetRequestMethod::ParticipantIdentity(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.participant_identity_get(*req).await?.into() } GetRequestMethod::ShareNotification(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.share_notification_get(*req).await?.into() } GetRequestMethod::Registry(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; Box::pin(self.registry_get( @@ -378,25 +380,25 @@ impl RequestHandler for Server { }, RequestMethod::Query(req) => match req { QueryRequestMethod::Email(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::Email)?; self.email_query(*req, access_token).await?.into() } QueryRequestMethod::Mailbox(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::Mailbox)?; self.mailbox_query(*req, access_token).await?.into() } QueryRequestMethod::EmailSubmission(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.email_submission_query(*req).await?.into() } QueryRequestMethod::Sieve(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.sieve_script_query(*req).await?.into() @@ -405,43 +407,43 @@ impl RequestHandler for Server { self.principal_query(*req, access_token).await?.into() } QueryRequestMethod::Quota(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.quota_query(*req, access_token).await?.into() } QueryRequestMethod::AddressBook(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::AddressBook)?; self.address_book_query(*req, access_token).await?.into() } QueryRequestMethod::ContactCard(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::ContactCard)?; self.contact_card_query(*req, access_token).await?.into() } QueryRequestMethod::FileNode(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::FileNode)?; self.file_node_query(*req, access_token).await?.into() } QueryRequestMethod::Calendar(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::Calendar)?; self.calendar_query(*req, access_token).await?.into() } QueryRequestMethod::CalendarEvent(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::CalendarEvent)?; self.calendar_event_query(*req, access_token).await?.into() } QueryRequestMethod::CalendarEventNotification(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.calendar_event_notification_query(*req, access_token) @@ -449,13 +451,13 @@ impl RequestHandler for Server { .into() } QueryRequestMethod::ShareNotification(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.share_notification_query(*req).await?.into() } QueryRequestMethod::Registry(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; Box::pin(self.registry_query( @@ -469,25 +471,25 @@ impl RequestHandler for Server { }, RequestMethod::Set(req) => match req { SetRequestMethod::Email(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::Email)?; self.email_set(*req, access_token, session).await?.into() } SetRequestMethod::Mailbox(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::Mailbox)?; self.mailbox_set(*req, access_token).await?.into() } SetRequestMethod::Identity(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.identity_set(*req).await?.into() } SetRequestMethod::EmailSubmission(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.email_submission_set(*req, &session.instance, next_call) @@ -495,11 +497,11 @@ impl RequestHandler for Server { .into() } SetRequestMethod::PushSubscription(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; self.push_subscription_set(*req, access_token).await?.into() } SetRequestMethod::Sieve(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.sieve_script_set(*req, access_token, session) @@ -507,13 +509,13 @@ impl RequestHandler for Server { .into() } SetRequestMethod::VacationResponse(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.vacation_response_set(*req, access_token).await?.into() } SetRequestMethod::AddressBook(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::AddressBook)?; self.address_book_set(*req, access_token, session) @@ -521,7 +523,7 @@ impl RequestHandler for Server { .into() } SetRequestMethod::ContactCard(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::ContactCard)?; self.contact_card_set(*req, access_token, session) @@ -529,7 +531,7 @@ impl RequestHandler for Server { .into() } SetRequestMethod::FileNode(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::FileNode)?; self.file_node_set(*req, access_token, session) @@ -537,19 +539,19 @@ impl RequestHandler for Server { .into() } SetRequestMethod::ShareNotification(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.share_notification_set(*req).await?.into() } SetRequestMethod::Calendar(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::Calendar)?; self.calendar_set(*req, access_token, session).await?.into() } SetRequestMethod::CalendarEvent(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::CalendarEvent)?; self.calendar_event_set(*req, access_token, session) @@ -557,7 +559,7 @@ impl RequestHandler for Server { .into() } SetRequestMethod::CalendarEventNotification(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.calendar_event_notification_set(*req, access_token, session) @@ -565,13 +567,13 @@ impl RequestHandler for Server { .into() } SetRequestMethod::ParticipantIdentity(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.participant_identity_set(*req).await?.into() } SetRequestMethod::Registry(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; Box::pin(self.registry_set( @@ -585,7 +587,7 @@ impl RequestHandler for Server { } }, RequestMethod::Changes(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; self.changes(*req, method_name.obj, access_token) .await? @@ -593,8 +595,8 @@ impl RequestHandler for Server { } RequestMethod::Copy(req) => match req { CopyRequestMethod::Email(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); - set_account_id_if_missing(&mut req.from_account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; + resolve_account_id(&mut req.from_account_id, method_name.obj, access_token)?; access_token .assert_has_access(req.account_id, Collection::Email)? @@ -605,14 +607,14 @@ impl RequestHandler for Server { .into() } CopyRequestMethod::Blob(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.blob_copy(*req, access_token).await?.into() } CopyRequestMethod::ContactCard(mut req) => { - set_account_id_if_missing(&mut req.from_account_id, access_token); - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.from_account_id, method_name.obj, access_token)?; + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token .assert_has_access(req.account_id, Collection::ContactCard)? @@ -623,8 +625,8 @@ impl RequestHandler for Server { .into() } CopyRequestMethod::CalendarEvent(mut req) => { - set_account_id_if_missing(&mut req.from_account_id, access_token); - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.from_account_id, method_name.obj, access_token)?; + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token .assert_has_access(req.account_id, Collection::CalendarEvent)? @@ -635,8 +637,8 @@ impl RequestHandler for Server { .into() } CopyRequestMethod::FileNode(mut req) => { - set_account_id_if_missing(&mut req.from_account_id, access_token); - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.from_account_id, method_name.obj, access_token)?; + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token .assert_has_access(req.account_id, Collection::FileNode)? @@ -648,26 +650,26 @@ impl RequestHandler for Server { } }, RequestMethod::ImportEmail(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::Email)?; self.email_import(*req, access_token, session).await?.into() } RequestMethod::Parse(req) => match req { ParseRequestMethod::Email(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::Email)?; self.email_parse(*req, access_token).await?.into() } ParseRequestMethod::ContactCard(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::ContactCard)?; self.contact_card_parse(*req, access_token).await?.into() } ParseRequestMethod::CalendarEvent(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::CalendarEvent)?; self.calendar_event_parse(*req, access_token).await?.into() @@ -675,25 +677,25 @@ impl RequestHandler for Server { }, RequestMethod::QueryChanges(req) => self.query_changes(req, access_token).await?.into(), RequestMethod::SearchSnippet(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_has_access(req.account_id, Collection::Email)?; self.email_search_snippet(*req, access_token).await?.into() } RequestMethod::ValidateScript(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.sieve_script_validate(*req, access_token).await?.into() } RequestMethod::LookupBlob(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.blob_lookup(*req).await?.into() } RequestMethod::UploadBlob(mut req) => { - set_account_id_if_missing(&mut req.account_id, access_token); + resolve_account_id(&mut req.account_id, method_name.obj, access_token)?; access_token.assert_is_member(req.account_id)?; self.blob_upload_many(*req, access_token).await?.into() @@ -714,9 +716,24 @@ impl RequestHandler for Server { } } -#[inline] -pub(crate) fn set_account_id_if_missing(account_id: &mut Id, access_token: &AccessToken) { - if !account_id.is_valid() { +pub(crate) fn resolve_account_id( + account_id: &mut Id, + obj: MethodObject, + access_token: &AccessToken, +) -> trc::Result<()> { + if account_id.id() < INVALID_ACCOUNT_ID { + Ok(()) + } else if matches!( + obj, + MethodObject::Core | MethodObject::PushSubscription | MethodObject::Registry(_) + ) { *account_id = Id::from(access_token.account_id()); + Ok(()) + } else if account_id.id() == INVALID_ACCOUNT_ID { + Err(trc::JmapEvent::AccountNotFound.into_err()) + } else { + Err(trc::JmapEvent::InvalidArguments + .into_err() + .details("The \"accountId\" property is required.")) } } diff --git a/crates/jmap/src/changes/query.rs b/crates/jmap/src/changes/query.rs index 15662b13..4cb95fd0 100644 --- a/crates/jmap/src/changes/query.rs +++ b/crates/jmap/src/changes/query.rs @@ -6,7 +6,7 @@ use super::get::ChangesLookup; use crate::{ - api::request::set_account_id_if_missing, calendar_event::query::CalendarEventQuery, + api::request::resolve_account_id, calendar_event::query::CalendarEventQuery, calendar_event_notification::query::CalendarEventNotificationQuery, contact::query::ContactCardQuery, email::query::EmailQuery, file::query::FileNodeQuery, mailbox::query::MailboxQuery, share_notification::query::ShareNotificationQuery, @@ -47,7 +47,7 @@ impl QueryChanges for Server { match request { QueryChangesRequestMethod::Email(mut request) => { // Query changes - set_account_id_if_missing(&mut request.account_id, access_token); + resolve_account_id(&mut request.account_id, MethodObject::Email, access_token)?; changes = self .changes( build_changes_request(&request), @@ -75,7 +75,7 @@ impl QueryChanges for Server { } QueryChangesRequestMethod::Mailbox(mut request) => { // Query changes - set_account_id_if_missing(&mut request.account_id, access_token); + resolve_account_id(&mut request.account_id, MethodObject::Mailbox, access_token)?; changes = self .changes( build_changes_request(&request), @@ -97,7 +97,11 @@ impl QueryChanges for Server { } QueryChangesRequestMethod::EmailSubmission(mut request) => { // Query changes - set_account_id_if_missing(&mut request.account_id, access_token); + resolve_account_id( + &mut request.account_id, + MethodObject::EmailSubmission, + access_token, + )?; changes = self .changes( build_changes_request(&request), @@ -119,7 +123,11 @@ impl QueryChanges for Server { } QueryChangesRequestMethod::ContactCard(mut request) => { // Query changes - set_account_id_if_missing(&mut request.account_id, access_token); + resolve_account_id( + &mut request.account_id, + MethodObject::ContactCard, + access_token, + )?; changes = self .changes( build_changes_request(&request), @@ -143,7 +151,7 @@ impl QueryChanges for Server { } QueryChangesRequestMethod::FileNode(mut request) => { // Query changes - set_account_id_if_missing(&mut request.account_id, access_token); + resolve_account_id(&mut request.account_id, MethodObject::FileNode, access_token)?; changes = self .changes( build_changes_request(&request), @@ -167,7 +175,11 @@ impl QueryChanges for Server { } QueryChangesRequestMethod::CalendarEvent(mut request) => { // Query changes - set_account_id_if_missing(&mut request.account_id, access_token); + resolve_account_id( + &mut request.account_id, + MethodObject::CalendarEvent, + access_token, + )?; changes = self .changes( build_changes_request(&request), @@ -191,7 +203,11 @@ impl QueryChanges for Server { } QueryChangesRequestMethod::CalendarEventNotification(mut request) => { // Query changes - set_account_id_if_missing(&mut request.account_id, access_token); + resolve_account_id( + &mut request.account_id, + MethodObject::CalendarEventNotification, + access_token, + )?; changes = self .changes( build_changes_request(&request), @@ -215,7 +231,11 @@ impl QueryChanges for Server { } QueryChangesRequestMethod::ShareNotification(mut request) => { // Query changes - set_account_id_if_missing(&mut request.account_id, access_token); + resolve_account_id( + &mut request.account_id, + MethodObject::ShareNotification, + access_token, + )?; changes = self .changes( build_changes_request(&request), diff --git a/tests/src/utils/jmap.rs b/tests/src/utils/jmap.rs index 9a5c3955..36dbc42c 100644 --- a/tests/src/utils/jmap.rs +++ b/tests/src/utils/jmap.rs @@ -132,6 +132,7 @@ impl Account { }) .collect::>(); let arguments = [ + ("accountId".to_string(), self.id_string().into()), ("filter".to_string(), Value::Object(filter)), ("sort".to_string(), Value::Array(sort_by)), ] @@ -295,6 +296,7 @@ impl Account { self.jmap_method_calls(json!([[ format!("{object}/changes"), { + "accountId": self.id_string(), "sinceState": state.to_string() }, "0"