inbuxa:HoldExport/set takes a hold, optionally some of the accounts it covers, and a reason; the collection runs in the background and get says when it's ready. The ZIP has, per account, mail as .eml under its folders, calendars as .ics, contacts as .vcf, files as stored, and the archived items the hold keeps under archived/; a manifest.csv gives each entry's account, kind, folder, date, whether it was archived, size and SHA-256, and manifest.sha256 hashes the manifest. Accounts the hold doesn't cover are left out, and items outside its date range are too: live mail by arrival, events by start, and archived items the same way, so an export doesn't carry deleted items that only another hold keeps. The finished file is a blob of whoever started the export, so only they download it, and it lasts as long as any upload (uploadTtl). Exports are records under the hold (SUBSPACE_INBUXA H/e): never changed or destroyed, each with its status, counts, size and checksum. Starting one needs sysLegalHoldExport, an active hold and a reason, and is recorded in the audit log like the audit log's own export. The build is in memory and capped at 2 GB; bigger holds fail with a message saying so, and are split by picking accounts. Tested: unit tests for safe ZIP names and the manifest and its hash; the legal_hold system test, on RocksDB, PostgreSQL and MySQL, exports a hold end to end (live and archived mail, the manifest's hash, an asked- for account the hold doesn't cover left out) and checks the refusals (no reason, a user without the permission, a released hold) and the audit record; and by hand from the console on a local server. Not covered by a test: the archived-item date range with two holds of different ranges over one account.
1193 lines
56 KiB
Rust
1193 lines
56 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
*
|
|
* Modified by Coffey Labs in 2026 for INBUXA.
|
|
*/
|
|
|
|
use crate::{
|
|
addressbook::{get::AddressBookGet, set::AddressBookSet},
|
|
api::auth::JmapAuthorization,
|
|
blob::{copy::BlobCopy, get::BlobOperations, upload::BlobUpload},
|
|
calendar::{get::CalendarGet, set::CalendarSet},
|
|
calendar_event::{
|
|
copy::JmapCalendarEventCopy, get::CalendarEventGet, parse::CalendarEventParse,
|
|
query::CalendarEventQuery, set::CalendarEventSet,
|
|
},
|
|
calendar_event_notification::{
|
|
get::CalendarEventNotificationGet, query::CalendarEventNotificationQuery,
|
|
set::CalendarEventNotificationSet,
|
|
},
|
|
changes::{get::ChangesLookup, query::QueryChanges},
|
|
contact::{
|
|
copy::JmapContactCardCopy, get::ContactCardGet, parse::ContactCardParse,
|
|
query::ContactCardQuery, set::ContactCardSet,
|
|
},
|
|
email::{
|
|
copy::JmapEmailCopy, get::EmailGet, import::EmailImport, parse::EmailParse,
|
|
query::EmailQuery, set::EmailSet, snippet::EmailSearchSnippet,
|
|
},
|
|
file::{copy::FileNodeCopy, get::FileNodeGet, query::FileNodeQuery, set::FileNodeSet},
|
|
identity::{get::IdentityGet, set::IdentitySet},
|
|
mailbox::{get::MailboxGet, query::MailboxQuery, set::MailboxSet},
|
|
participant_identity::{get::ParticipantIdentityGet, set::ParticipantIdentitySet},
|
|
principal::{availability::PrincipalGetAvailability, get::PrincipalGet, query::PrincipalQuery},
|
|
push::{get::PushSubscriptionFetch, set::PushSubscriptionSet},
|
|
quota::{get::QuotaGet, query::QuotaQuery},
|
|
registry::{get::RegistryGet, query::RegistryQuery, set::RegistrySet},
|
|
share_notification::{
|
|
get::ShareNotificationGet, query::ShareNotificationQuery, set::ShareNotificationSet,
|
|
},
|
|
sieve::{
|
|
get::SieveScriptGet, query::SieveScriptQuery, set::SieveScriptSet,
|
|
validate::SieveScriptValidate,
|
|
},
|
|
submission::{get::EmailSubmissionGet, query::EmailSubmissionQuery, set::EmailSubmissionSet},
|
|
thread::get::ThreadGet,
|
|
vacation::{get::VacationResponseGet, set::VacationResponseSet},
|
|
};
|
|
use common::{Server, auth::AccessToken};
|
|
use http_proto::HttpSessionData;
|
|
use jmap_proto::{
|
|
request::{
|
|
Call, CopyRequestMethod, GetRequestMethod, INVALID_ACCOUNT_ID, ParseRequestMethod,
|
|
QueryRequestMethod, Request, RequestMethod, SetRequestMethod,
|
|
capability::Capability,
|
|
method::{MethodName, MethodObject},
|
|
},
|
|
response::{Response, ResponseMethod, SetResponseMethod},
|
|
};
|
|
use std::future::Future;
|
|
use std::time::Instant;
|
|
use trc::JmapEvent;
|
|
use types::{collection::Collection, id::Id};
|
|
|
|
pub trait RequestHandler: Sync + Send {
|
|
fn handle_jmap_request<'x>(
|
|
&self,
|
|
request: Request<'x>,
|
|
access_token: &AccessToken,
|
|
session: &HttpSessionData,
|
|
) -> impl Future<Output = Response<'x>> + Send;
|
|
|
|
fn handle_method_call<'x>(
|
|
&self,
|
|
method: RequestMethod<'x>,
|
|
method_name: MethodName,
|
|
access_token: &AccessToken,
|
|
next_call: &mut Option<Call<RequestMethod<'x>>>,
|
|
session: &HttpSessionData,
|
|
) -> impl Future<Output = trc::Result<ResponseMethod<'x>>> + Send;
|
|
}
|
|
|
|
impl RequestHandler for Server {
|
|
async fn handle_jmap_request<'x>(
|
|
&self,
|
|
request: Request<'x>,
|
|
access_token: &AccessToken,
|
|
session: &HttpSessionData,
|
|
) -> Response<'x> {
|
|
let add_created_ids = request.created_ids.is_some();
|
|
let using = request.using;
|
|
let mut response = Response::new(
|
|
access_token.state(),
|
|
request.created_ids.unwrap_or_default(),
|
|
request.method_calls.len(),
|
|
);
|
|
|
|
// inbuxa: ST-6: reads before the request's first write may go to a
|
|
// read replica
|
|
let mut has_written = false;
|
|
|
|
for mut call in request.method_calls {
|
|
// Resolve result and id references
|
|
if let Err(error) = response.resolve_references(&mut call.method) {
|
|
let method_error = error.clone();
|
|
|
|
trc::error!(error.span_id(session.session_id));
|
|
|
|
response.push_response(call.id, MethodName::error(), method_error);
|
|
continue;
|
|
}
|
|
|
|
if !matches!(call.method, RequestMethod::Error(_)) {
|
|
let capability = call.name.obj.capability();
|
|
if capability != Capability::Stalwart && !using.contains(capability) {
|
|
response.push_response(
|
|
call.id,
|
|
MethodName::error(),
|
|
trc::JmapEvent::UnknownMethod.into_err().details(format!(
|
|
"Method {} requires capability {} which is not present in the \"using\" property.",
|
|
call.name,
|
|
capability.as_str()
|
|
)),
|
|
);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
loop {
|
|
let mut next_call = None;
|
|
|
|
// Add response
|
|
let method_name = call.name.as_str();
|
|
|
|
// inbuxa: ST-6, ST-7: a read before the first write may use a
|
|
// replica that has every change the client has seen
|
|
let eligible = !has_written
|
|
&& matches!(
|
|
call.method,
|
|
RequestMethod::Get(_)
|
|
| RequestMethod::Query(_)
|
|
| RequestMethod::Changes(_)
|
|
| RequestMethod::QueryChanges(_)
|
|
);
|
|
let is_write = matches!(
|
|
call.method,
|
|
RequestMethod::Set(_)
|
|
| RequestMethod::Copy(_)
|
|
| RequestMethod::ImportEmail(_)
|
|
| RequestMethod::UploadBlob(_)
|
|
);
|
|
if is_write {
|
|
has_written = true;
|
|
}
|
|
// inbuxa: AL-7: what a delegate makes in a locked account
|
|
// may need the lock's grants
|
|
let makes_containers = is_write
|
|
&& matches!(
|
|
call.name.obj,
|
|
MethodObject::Mailbox
|
|
| MethodObject::Calendar
|
|
| MethodObject::AddressBook
|
|
| MethodObject::FileNode
|
|
);
|
|
let call_name = call.name.as_str().into_owned();
|
|
let presented = match &call.method {
|
|
RequestMethod::Changes(changes) => match &changes.since_state {
|
|
jmap_proto::types::state::State::Exact(change_id) => {
|
|
Some((changes.account_id.document_id(), *change_id))
|
|
}
|
|
_ => None,
|
|
},
|
|
_ => None,
|
|
};
|
|
// inbuxa: AU-1.6: which accounts it reached by impersonation
|
|
let method_call = crate::inbuxa::audit::collect_access(Box::pin(
|
|
self.handle_method_call(
|
|
call.method,
|
|
call.name,
|
|
access_token,
|
|
&mut next_call,
|
|
session,
|
|
),
|
|
));
|
|
let result = if eligible {
|
|
store::backend::scaleout::replica::replica_read(
|
|
access_token.all_ids().map(|account_id| {
|
|
(
|
|
account_id,
|
|
presented
|
|
.filter(|(id, _)| *id == account_id)
|
|
.map_or(0, |(_, change_id)| change_id),
|
|
)
|
|
}),
|
|
method_call,
|
|
)
|
|
.await
|
|
} else {
|
|
method_call.await
|
|
};
|
|
let (result, reached) = result;
|
|
for account_id in reached {
|
|
// inbuxa: AL-9: a delegate's access, and what it
|
|
// changes, are recorded; anyone else here impersonated
|
|
if let Some(delegation) = access_token.delegation(account_id) {
|
|
let access = delegation.access.as_str();
|
|
self.audit_delegate(
|
|
access_token,
|
|
account_id,
|
|
access,
|
|
is_write.then_some(call_name.as_str()),
|
|
result.as_ref().err(),
|
|
)
|
|
.await;
|
|
if makes_containers
|
|
&& result.is_ok()
|
|
&& let Err(err) =
|
|
email::inbuxa_lock::reconcile(self, account_id).await
|
|
{
|
|
trc::error!(err.details("Failed to grant a lock's delegates on new folders"));
|
|
}
|
|
} else {
|
|
self.audit_foreign_access(access_token, account_id, false).await;
|
|
}
|
|
}
|
|
match result
|
|
{
|
|
Ok(mut method_response) => {
|
|
match &mut method_response {
|
|
ResponseMethod::Set(set_response) => {
|
|
// Add created ids
|
|
match set_response {
|
|
SetResponseMethod::Email(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::Mailbox(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::Identity(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::EmailSubmission(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::PushSubscription(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::Sieve(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::VacationResponse(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::MaskedEmail(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::DeletedAccount(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::AiLimits(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::AuditSettings(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::AuditExport(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::AuditVerification(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::AccountLock(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::LegalHold(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::HoldExport(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::Explanation(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::ProtocolPolicy(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::TenantProtocolPolicy(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::AddressBook(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::ContactCard(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::FileNode(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::ShareNotification(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::Calendar(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::CalendarEvent(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::ParticipantIdentity(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::CalendarEventNotification(_) => {}
|
|
SetResponseMethod::Registry(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
}
|
|
}
|
|
ResponseMethod::ImportEmail(import_response) => {
|
|
// Add created ids
|
|
import_response.update_created_ids(&mut response);
|
|
}
|
|
ResponseMethod::UploadBlob(upload_response) => {
|
|
// Add created blobIds
|
|
upload_response.update_created_ids(&mut response);
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
response.push_response(call.id, call.name, method_response);
|
|
}
|
|
Err(error) => {
|
|
let method_error = error.clone();
|
|
|
|
trc::error!(
|
|
error
|
|
.span_id(session.session_id)
|
|
.ctx_unique(trc::Key::AccountId, access_token.account_id())
|
|
.caused_by(method_name)
|
|
);
|
|
|
|
response.push_error(call.id, method_error);
|
|
}
|
|
}
|
|
|
|
// Process next call
|
|
if let Some(next_call) = next_call {
|
|
call = next_call;
|
|
call.id
|
|
.clone_from(&response.method_responses.last().unwrap().id);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if !add_created_ids {
|
|
response.created_ids.clear();
|
|
}
|
|
|
|
response
|
|
}
|
|
|
|
async fn handle_method_call<'x>(
|
|
&self,
|
|
method: RequestMethod<'x>,
|
|
method_name: MethodName,
|
|
access_token: &AccessToken,
|
|
next_call: &mut Option<Call<RequestMethod<'x>>>,
|
|
session: &HttpSessionData,
|
|
) -> trc::Result<ResponseMethod<'x>> {
|
|
let op_start = Instant::now();
|
|
|
|
// Check permissions
|
|
access_token.assert_has_jmap_permission(&method, method_name.obj)?;
|
|
|
|
// Handle method
|
|
let response = match method {
|
|
RequestMethod::Get(req) => match req {
|
|
GetRequestMethod::Email(mut req) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
// inbuxa: AL-8: a delegate may send as a locked account
|
|
access_token.assert_can_send(req.account_id)?;
|
|
|
|
self.identity_get(*req).await?.into()
|
|
}
|
|
GetRequestMethod::EmailSubmission(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
// inbuxa: AL-8: a delegate may send as a locked account
|
|
access_token.assert_can_send(req.account_id)?;
|
|
|
|
self.email_submission_get(*req).await?.into()
|
|
}
|
|
GetRequestMethod::PushSubscription(mut req) => {
|
|
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) => {
|
|
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) => {
|
|
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()
|
|
}
|
|
// inbuxa: Fastmail's MaskedEmail/get
|
|
GetRequestMethod::MaskedEmail(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::fastmail::get(self, access_token, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: inbuxa:DeletedAccount/get (UD-17)
|
|
GetRequestMethod::DeletedAccount(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::deleted_account::get(self, access_token, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: inbuxa:AiLimits/get
|
|
GetRequestMethod::AiLimits(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::ai_limits::get(self, access_token, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: account lock with delegation (AL-1)
|
|
GetRequestMethod::AccountLock(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::account_lock::get(self, access_token, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: legal hold (LH-1)
|
|
// inbuxa: legal hold exports (LH-12)
|
|
GetRequestMethod::HoldExport(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::hold_export_api::get(self, *req).await?.into()
|
|
}
|
|
GetRequestMethod::LegalHold(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::legal_hold::get(self, *req).await?.into()
|
|
}
|
|
// inbuxa: the audit log (AU-9)
|
|
GetRequestMethod::AuditEvent(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::audit_log::event_get(self, access_token, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
GetRequestMethod::AuditSettings(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::audit_log::settings_get(self, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: inbuxa:ProtocolPolicy/get (legacy protocols off)
|
|
GetRequestMethod::ProtocolPolicy(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::protocol_policy::get(self, access_token, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: inbuxa:TenantProtocolPolicy/get (legacy protocols off, per tenant)
|
|
GetRequestMethod::TenantProtocolPolicy(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::tenant_protocol_policy::get(self, access_token, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
GetRequestMethod::Principal(req) => {
|
|
self.principal_get(*req, access_token).await?.into()
|
|
}
|
|
GetRequestMethod::Quota(mut req) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
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()
|
|
}
|
|
GetRequestMethod::PrincipalAvailability(req) => self
|
|
.principal_get_availability(*req, access_token)
|
|
.await?
|
|
.into(),
|
|
GetRequestMethod::Calendar(mut req) => {
|
|
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) => {
|
|
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) => {
|
|
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)
|
|
.await?
|
|
.into()
|
|
}
|
|
GetRequestMethod::ParticipantIdentity(mut req) => {
|
|
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) => {
|
|
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) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
assert_registry_account(self, method_name.obj, access_token, req.account_id)
|
|
.await?;
|
|
|
|
Box::pin(self.registry_get(
|
|
method_name.obj.unwrap_registry(),
|
|
*req,
|
|
access_token,
|
|
))
|
|
.await?
|
|
.into()
|
|
}
|
|
},
|
|
RequestMethod::Query(req) => match req {
|
|
QueryRequestMethod::Email(mut req) => {
|
|
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) => {
|
|
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) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
// inbuxa: AL-8: a delegate may send as a locked account
|
|
access_token.assert_can_send(req.account_id)?;
|
|
|
|
self.email_submission_query(*req).await?.into()
|
|
}
|
|
QueryRequestMethod::Sieve(mut req) => {
|
|
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()
|
|
}
|
|
QueryRequestMethod::Principal(req) => {
|
|
self.principal_query(*req, access_token).await?.into()
|
|
}
|
|
QueryRequestMethod::Quota(mut req) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
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)
|
|
.await?
|
|
.into()
|
|
}
|
|
QueryRequestMethod::ShareNotification(mut req) => {
|
|
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()
|
|
}
|
|
// inbuxa: the audit log (AU-9)
|
|
QueryRequestMethod::AuditEvent(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::audit_log::event_query(self, access_token, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
QueryRequestMethod::Registry(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
assert_registry_account(self, method_name.obj, access_token, req.account_id)
|
|
.await?;
|
|
|
|
Box::pin(self.registry_query(
|
|
method_name.obj.unwrap_registry(),
|
|
*req,
|
|
access_token,
|
|
))
|
|
.await?
|
|
.into()
|
|
}
|
|
},
|
|
RequestMethod::Set(req) => match req {
|
|
SetRequestMethod::Email(mut req) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
// inbuxa: AL-8: a delegate may send as a locked account
|
|
access_token.assert_can_send(req.account_id)?;
|
|
|
|
self.email_submission_set(*req, &session.instance, next_call)
|
|
.await?
|
|
.into()
|
|
}
|
|
SetRequestMethod::PushSubscription(mut req) => {
|
|
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) => {
|
|
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)
|
|
.await?
|
|
.into()
|
|
}
|
|
SetRequestMethod::VacationResponse(mut req) => {
|
|
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()
|
|
}
|
|
// inbuxa: Fastmail's MaskedEmail/set
|
|
SetRequestMethod::MaskedEmail(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
// inbuxa: AU-1.2, AU-3
|
|
crate::inbuxa::audit::recorded(
|
|
self,
|
|
access_token,
|
|
session,
|
|
&method_name.obj.to_string(),
|
|
None,
|
|
None,
|
|
*req,
|
|
|req| Box::pin(crate::inbuxa::fastmail::set(self, access_token, req)),
|
|
)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: inbuxa:DeletedAccount/set (UD-17)
|
|
SetRequestMethod::DeletedAccount(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
// inbuxa: AU-1.2, AU-3
|
|
crate::inbuxa::audit::recorded(
|
|
self,
|
|
access_token,
|
|
session,
|
|
&method_name.obj.to_string(),
|
|
None,
|
|
None,
|
|
*req,
|
|
|req| Box::pin(crate::inbuxa::deleted_account::set(self, access_token, req)),
|
|
)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: inbuxa:AiLimits/set
|
|
SetRequestMethod::AiLimits(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
// inbuxa: AU-1.2, AU-3
|
|
crate::inbuxa::audit::recorded(
|
|
self,
|
|
access_token,
|
|
session,
|
|
&method_name.obj.to_string(),
|
|
None,
|
|
None,
|
|
*req,
|
|
|req| Box::pin(crate::inbuxa::ai_limits::set(self, access_token, req)),
|
|
)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: the audit log (AU-7, AU-11, AU-6)
|
|
SetRequestMethod::AuditSettings(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::audit::recorded(
|
|
self,
|
|
access_token,
|
|
session,
|
|
&method_name.obj.to_string(),
|
|
None,
|
|
None,
|
|
*req,
|
|
|req| Box::pin(crate::inbuxa::audit_log::settings_set(self, access_token, req)),
|
|
)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: account lock with delegation, recorded with its
|
|
// reason (AL-1, AU-12)
|
|
SetRequestMethod::AccountLock(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
let reason = req.arguments.reason.clone().or_else(|| {
|
|
req.create.as_ref().and_then(|create| {
|
|
create.values().find_map(|value| {
|
|
serde_json::to_value(value)
|
|
.ok()?
|
|
.get("reason")?
|
|
.as_str()
|
|
.map(str::to_string)
|
|
})
|
|
})
|
|
});
|
|
crate::inbuxa::audit::recorded(
|
|
self,
|
|
access_token,
|
|
session,
|
|
&method_name.obj.to_string(),
|
|
None,
|
|
reason,
|
|
*req,
|
|
|req| Box::pin(crate::inbuxa::account_lock::set(self, access_token, req)),
|
|
)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: legal hold (LH-1), each change recorded with its
|
|
// reason (AU-12)
|
|
// inbuxa: legal hold exports, recorded with their reason
|
|
// (AU-1.9, AU-12)
|
|
SetRequestMethod::HoldExport(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
let reason = req.arguments.reason.clone().or_else(|| {
|
|
req.create.as_ref().and_then(|create| {
|
|
create.values().find_map(|value| {
|
|
serde_json::to_value(value)
|
|
.ok()?
|
|
.get("reason")?
|
|
.as_str()
|
|
.map(str::to_string)
|
|
})
|
|
})
|
|
});
|
|
crate::inbuxa::audit::recorded(
|
|
self,
|
|
access_token,
|
|
session,
|
|
&method_name.obj.to_string(),
|
|
None,
|
|
reason,
|
|
*req,
|
|
|req| Box::pin(crate::inbuxa::hold_export_api::set(self, access_token, req)),
|
|
)
|
|
.await?
|
|
.into()
|
|
}
|
|
SetRequestMethod::LegalHold(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
let reason = req.arguments.reason.clone().or_else(|| {
|
|
req.create.as_ref().and_then(|create| {
|
|
create.values().find_map(|value| {
|
|
serde_json::to_value(value)
|
|
.ok()?
|
|
.get("reason")?
|
|
.as_str()
|
|
.map(str::to_string)
|
|
})
|
|
})
|
|
});
|
|
crate::inbuxa::audit::recorded(
|
|
self,
|
|
access_token,
|
|
session,
|
|
&method_name.obj.to_string(),
|
|
None,
|
|
reason,
|
|
*req,
|
|
|req| Box::pin(crate::inbuxa::legal_hold::set(self, access_token, req)),
|
|
)
|
|
.await?
|
|
.into()
|
|
}
|
|
SetRequestMethod::AuditExport(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::audit_log::export_set(self, access_token, session, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
SetRequestMethod::AuditVerification(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::audit_log::verification_set(self, access_token, session, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: inbuxa:Explanation/set ("Explain this")
|
|
SetRequestMethod::Explanation(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::explanation::set(self, access_token, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: inbuxa:ProtocolPolicy/set (legacy protocols off)
|
|
SetRequestMethod::ProtocolPolicy(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
// inbuxa: AU-1.2, AU-3
|
|
crate::inbuxa::audit::recorded(
|
|
self,
|
|
access_token,
|
|
session,
|
|
&method_name.obj.to_string(),
|
|
None,
|
|
None,
|
|
*req,
|
|
|req| Box::pin(crate::inbuxa::protocol_policy::set(self, access_token, req)),
|
|
)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: inbuxa:TenantProtocolPolicy/set (legacy protocols off, per tenant)
|
|
SetRequestMethod::TenantProtocolPolicy(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
// inbuxa: AU-1.2, AU-3
|
|
crate::inbuxa::audit::recorded(
|
|
self,
|
|
access_token,
|
|
session,
|
|
&method_name.obj.to_string(),
|
|
None,
|
|
None,
|
|
*req,
|
|
|req| Box::pin(crate::inbuxa::tenant_protocol_policy::set(self, access_token, req)),
|
|
)
|
|
.await?
|
|
.into()
|
|
}
|
|
SetRequestMethod::AddressBook(mut req) => {
|
|
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)
|
|
.await?
|
|
.into()
|
|
}
|
|
SetRequestMethod::ContactCard(mut req) => {
|
|
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)
|
|
.await?
|
|
.into()
|
|
}
|
|
SetRequestMethod::FileNode(mut req) => {
|
|
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)
|
|
.await?
|
|
.into()
|
|
}
|
|
SetRequestMethod::ShareNotification(mut req) => {
|
|
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) => {
|
|
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) => {
|
|
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)
|
|
.await?
|
|
.into()
|
|
}
|
|
SetRequestMethod::CalendarEventNotification(mut req) => {
|
|
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)
|
|
.await?
|
|
.into()
|
|
}
|
|
SetRequestMethod::ParticipantIdentity(mut req) => {
|
|
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) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
assert_registry_account(self, method_name.obj, access_token, req.account_id)
|
|
.await?;
|
|
|
|
// inbuxa: AU-1.1, AU-3: recorded before and after
|
|
let object_type = method_name.obj.unwrap_registry();
|
|
crate::inbuxa::audit::recorded(
|
|
self,
|
|
access_token,
|
|
session,
|
|
&method_name.obj.to_string(),
|
|
Some(object_type),
|
|
None,
|
|
*req,
|
|
|req| Box::pin(self.registry_set(object_type, req, access_token, session)),
|
|
)
|
|
.await?
|
|
.into()
|
|
}
|
|
},
|
|
RequestMethod::Changes(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
|
|
// inbuxa: x:MaskedEmail/changes and x:ArchivedItem/changes
|
|
if method_name.obj
|
|
== MethodObject::Registry(registry::schema::prelude::ObjectType::ArchivedItem)
|
|
{
|
|
crate::inbuxa::undelete::changes(self, access_token, *req).await?
|
|
} else if matches!(method_name.obj, MethodObject::Registry(_)) {
|
|
crate::inbuxa::masked_email::changes(self, access_token, *req).await?
|
|
} else {
|
|
self.changes(*req, method_name.obj, access_token)
|
|
.await?
|
|
.into_method_response()
|
|
}
|
|
}
|
|
RequestMethod::Copy(req) => match req {
|
|
CopyRequestMethod::Email(mut req) => {
|
|
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)?
|
|
.assert_has_access(req.from_account_id, Collection::Email)?;
|
|
|
|
self.email_copy(*req, access_token, next_call, session)
|
|
.await?
|
|
.into()
|
|
}
|
|
CopyRequestMethod::Blob(mut req) => {
|
|
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) => {
|
|
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)?
|
|
.assert_has_access(req.from_account_id, Collection::ContactCard)?;
|
|
|
|
self.contact_card_copy(*req, access_token, next_call, session)
|
|
.await?
|
|
.into()
|
|
}
|
|
CopyRequestMethod::CalendarEvent(mut req) => {
|
|
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)?
|
|
.assert_has_access(req.from_account_id, Collection::CalendarEvent)?;
|
|
|
|
self.calendar_event_copy(*req, access_token, next_call, session)
|
|
.await?
|
|
.into()
|
|
}
|
|
CopyRequestMethod::FileNode(mut req) => {
|
|
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)?
|
|
.assert_has_access(req.from_account_id, Collection::FileNode)?;
|
|
|
|
self.file_node_copy(*req, access_token, next_call, session)
|
|
.await?
|
|
.into()
|
|
}
|
|
},
|
|
RequestMethod::ImportEmail(mut req) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
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()
|
|
}
|
|
},
|
|
RequestMethod::QueryChanges(req) => self.query_changes(req, access_token).await?.into(),
|
|
RequestMethod::SearchSnippet(mut req) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
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()
|
|
}
|
|
RequestMethod::Echo(req) => req.into(),
|
|
RequestMethod::Error(error) => return Err(error),
|
|
};
|
|
|
|
trc::event!(
|
|
Jmap(JmapEvent::MethodCall),
|
|
Id = method_name.as_str(),
|
|
SpanId = session.session_id,
|
|
AccountId = access_token.account_id(),
|
|
Elapsed = op_start.elapsed(),
|
|
);
|
|
|
|
Ok(response)
|
|
}
|
|
}
|
|
|
|
// inbuxa: ME-19: a tenant administrator reaches the masks of its tenant's
|
|
// accounts without impersonate, and a tenant principal never reaches beyond
|
|
// its tenant
|
|
async fn assert_registry_account(
|
|
server: &Server,
|
|
obj: MethodObject,
|
|
access_token: &AccessToken,
|
|
account_id: Id,
|
|
) -> trc::Result<()> {
|
|
if matches!(
|
|
obj,
|
|
MethodObject::Registry(
|
|
registry::schema::prelude::ObjectType::MaskedEmail
|
|
| registry::schema::prelude::ObjectType::ArchivedItem
|
|
)
|
|
) {
|
|
crate::inbuxa::masked_email::assert_can_manage(
|
|
server,
|
|
access_token,
|
|
account_id.document_id(),
|
|
)
|
|
.await
|
|
} else {
|
|
access_token.assert_is_member(account_id).map(|_| ())
|
|
}
|
|
}
|
|
|
|
pub(crate) fn resolve_account_id(
|
|
account_id: &mut Id,
|
|
obj: MethodObject,
|
|
access_token: &AccessToken,
|
|
) -> trc::Result<()> {
|
|
if account_id.id() < INVALID_ACCOUNT_ID {
|
|
// inbuxa: AU-1.6
|
|
crate::inbuxa::audit::note_access(account_id.document_id(), access_token);
|
|
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."))
|
|
}
|
|
}
|