Phase 4 of the journaling spec. - inbuxa:JournalEntry/query and /get (sysJournalSearch): filter by time, sender, recipient, either, direction, subject words, Message-ID and journal, newest first; the whole report only when asked for. - inbuxa:JournalExport/set (sysJournalExport): a reason is required; a ZIP of the matching reports with manifest.csv, exceptions.csv and manifest.sha256, up to 10,000 reports and 1 GB. - inbuxa:JournalVerification/set (sysJournalGet): chains and reports rechecked. - Every search, listing, read, export and check is written to the audit log before anything is returned, with existing actions only. - Catalog entries for the three objects; spec as-built notes. journal_tests: administrators can't search; a Compliance Officer searches, lists, reads a report, exports (reason required) and checks the chain; the officer can't change journals; each of those is in the audit log.
1398 lines
66 KiB
Rust
1398 lines
66 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::LogSettings(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::DlpSettings(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::MailRule(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::SecurityAcceptance(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::Journal(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::JournalExport(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::JournalVerification(set_response) => {
|
|
set_response.update_created_ids(&mut response);
|
|
}
|
|
SetResponseMethod::HeldMessage(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: inbuxa:LogSettings/get
|
|
GetRequestMethod::LogSettings(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::log_settings::get(self, access_token, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: inbuxa:DlpSettings/get
|
|
GetRequestMethod::DlpSettings(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::dlp_settings::get(self, access_token, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: inbuxa:DataInventory/get
|
|
GetRequestMethod::DataInventory(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::data_inventory::inventory_get(self, access_token, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: inbuxa:InventorySnapshot/get
|
|
GetRequestMethod::InventorySnapshot(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::data_inventory::snapshot_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: mail held for review
|
|
GetRequestMethod::HeldMessage(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::held_message::get(self, access_token, *req).await?.into()
|
|
}
|
|
// inbuxa: DLP and mail flow rules
|
|
GetRequestMethod::MailRule(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::mail_rule::get(self, access_token, *req).await?.into()
|
|
}
|
|
// inbuxa: accepted security to-do items
|
|
GetRequestMethod::SecurityAcceptance(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::security_acceptance::get(self, access_token, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: journaling
|
|
GetRequestMethod::Journal(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::journal::get(self, access_token, *req).await?.into()
|
|
}
|
|
GetRequestMethod::JournalEntry(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::journal_entry::get(self, access_token, session, *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()
|
|
}
|
|
// inbuxa: journaling (JR-15)
|
|
QueryRequestMethod::JournalEntry(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::journal_entry::query(self, access_token, session, *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: inbuxa:LogSettings/set
|
|
SetRequestMethod::LogSettings(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::log_settings::set(self, access_token, req)),
|
|
)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: inbuxa:DlpSettings/set
|
|
SetRequestMethod::DlpSettings(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::dlp_settings::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::HeldMessage(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
let reason = req.arguments.reason.clone();
|
|
crate::inbuxa::audit::recorded(
|
|
self,
|
|
access_token,
|
|
session,
|
|
&method_name.obj.to_string(),
|
|
None,
|
|
reason,
|
|
*req,
|
|
|req| Box::pin(crate::inbuxa::held_message::set(self, access_token, req)),
|
|
)
|
|
.await?
|
|
.into()
|
|
}
|
|
SetRequestMethod::MailRule(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
let reason = req.arguments.reason.clone();
|
|
crate::inbuxa::audit::recorded(
|
|
self,
|
|
access_token,
|
|
session,
|
|
&method_name.obj.to_string(),
|
|
None,
|
|
reason,
|
|
*req,
|
|
|req| Box::pin(crate::inbuxa::mail_rule::set(self, access_token, req)),
|
|
)
|
|
.await?
|
|
.into()
|
|
}
|
|
// inbuxa: SS-26, every acceptance made or removed is in the
|
|
// audit log
|
|
SetRequestMethod::SecurityAcceptance(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::security_acceptance::set(
|
|
self,
|
|
access_token,
|
|
req,
|
|
))
|
|
},
|
|
)
|
|
.await?
|
|
.into()
|
|
}
|
|
SetRequestMethod::Journal(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
let reason = req.arguments.reason.clone();
|
|
crate::inbuxa::audit::recorded(
|
|
self,
|
|
access_token,
|
|
session,
|
|
&method_name.obj.to_string(),
|
|
None,
|
|
reason,
|
|
*req,
|
|
|req| Box::pin(crate::inbuxa::journal::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: journaling (JR-6, JR-16)
|
|
SetRequestMethod::JournalExport(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::journal_entry::export_set(self, access_token, session, *req)
|
|
.await?
|
|
.into()
|
|
}
|
|
SetRequestMethod::JournalVerification(mut req) => {
|
|
resolve_account_id(&mut req.account_id, method_name.obj, access_token)?;
|
|
crate::inbuxa::journal_entry::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."))
|
|
}
|
|
}
|