Fix JMAP: Methods are only available if their capability is in using
This commit is contained in:
@@ -97,6 +97,12 @@ pub enum Capability {
|
||||
#[repr(transparent)]
|
||||
pub struct CapabilityIds(pub u32);
|
||||
|
||||
impl CapabilityIds {
|
||||
pub fn contains(&self, capability: Capability) -> bool {
|
||||
self.0 & capability as u32 != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
#[serde(untagged)]
|
||||
#[allow(dead_code)]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::request::capability::Capability;
|
||||
use registry::{
|
||||
schema::prelude::{OBJ_SINGLETON, ObjectType},
|
||||
types::EnumImpl,
|
||||
@@ -42,6 +43,31 @@ pub enum MethodObject {
|
||||
Registry(ObjectType),
|
||||
}
|
||||
|
||||
impl MethodObject {
|
||||
pub fn capability(&self) -> Capability {
|
||||
match self {
|
||||
MethodObject::Email
|
||||
| MethodObject::Mailbox
|
||||
| MethodObject::Thread
|
||||
| MethodObject::SearchSnippet => Capability::Mail,
|
||||
MethodObject::Core | MethodObject::PushSubscription => Capability::Core,
|
||||
MethodObject::Blob => Capability::Blob,
|
||||
MethodObject::Identity | MethodObject::EmailSubmission => Capability::Submission,
|
||||
MethodObject::VacationResponse => Capability::VacationResponse,
|
||||
MethodObject::SieveScript => Capability::Sieve,
|
||||
MethodObject::Principal | MethodObject::ShareNotification => Capability::Principals,
|
||||
MethodObject::Quota => Capability::Quota,
|
||||
MethodObject::Calendar
|
||||
| MethodObject::CalendarEvent
|
||||
| MethodObject::CalendarEventNotification
|
||||
| MethodObject::ParticipantIdentity => Capability::Calendars,
|
||||
MethodObject::AddressBook | MethodObject::ContactCard => Capability::Contacts,
|
||||
MethodObject::FileNode => Capability::FileNode,
|
||||
MethodObject::Registry(_) => Capability::Stalwart,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MethodFunction {
|
||||
Get,
|
||||
|
||||
@@ -11,7 +11,7 @@ use super::{
|
||||
use crate::request::{
|
||||
CopyRequestMethod, GetRequestMethod, ParseRequestMethod, QueryChangesRequestMethod,
|
||||
QueryRequestMethod, SetRequestMethod,
|
||||
deserialize::{DeserializeArguments, deserialize_request},
|
||||
deserialize::DeserializeArguments,
|
||||
};
|
||||
use serde::{
|
||||
Deserialize, Deserializer,
|
||||
@@ -713,7 +713,45 @@ impl<'de> Deserialize<'de> for Request<'de> {
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserialize_request(deserializer)
|
||||
struct RequestVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for RequestVisitor {
|
||||
type Value = Request<'de>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a JMAP request object")
|
||||
}
|
||||
|
||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: de::MapAccess<'de>,
|
||||
{
|
||||
let mut target = Request::default();
|
||||
let mut has_using = false;
|
||||
let mut has_method_calls = false;
|
||||
|
||||
while let Some(key) = map.next_key::<&str>()? {
|
||||
match key {
|
||||
"using" => has_using = true,
|
||||
"methodCalls" => has_method_calls = true,
|
||||
_ => {}
|
||||
}
|
||||
target
|
||||
.deserialize_argument(key, &mut map)
|
||||
.map_err(de::Error::custom)?;
|
||||
}
|
||||
|
||||
if !has_using || !has_method_calls {
|
||||
return Err(de::Error::custom(
|
||||
"Request is missing the \"using\" or \"methodCalls\" property.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(target)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_map(RequestVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ use http_proto::HttpSessionData;
|
||||
use jmap_proto::{
|
||||
request::{
|
||||
Call, CopyRequestMethod, GetRequestMethod, ParseRequestMethod, QueryRequestMethod, Request,
|
||||
RequestMethod, SetRequestMethod, method::MethodName,
|
||||
RequestMethod, SetRequestMethod, capability::Capability, method::MethodName,
|
||||
},
|
||||
response::{Response, ResponseMethod, SetResponseMethod},
|
||||
};
|
||||
@@ -85,6 +85,7 @@ impl RequestHandler for Server {
|
||||
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(),
|
||||
@@ -102,6 +103,22 @@ impl RequestHandler for Server {
|
||||
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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user