From 31719963c9645f3c0a11f16ea5bf633a74b6fe50 Mon Sep 17 00:00:00 2001 From: mdecimus Date: Thu, 21 Dec 2023 13:29:09 +0100 Subject: [PATCH] ACL storage optimization --- crates/imap/src/op/acl.rs | 163 +++++++++---------- crates/jmap-proto/src/object/index.rs | 95 +++++------ crates/jmap-proto/src/object/mod.rs | 52 +++++- crates/jmap-proto/src/types/value.rs | 16 ++ crates/jmap/src/auth/acl.rs | 218 +++++++++++++------------- crates/jmap/src/email/ingest.rs | 8 + crates/jmap/src/mailbox/get.rs | 2 +- tests/src/jmap/auth_limits.rs | 2 +- tests/src/jmap/mod.rs | 22 +-- tests/src/jmap/thread_merge.rs | 10 +- 10 files changed, 319 insertions(+), 269 deletions(-) diff --git a/crates/imap/src/op/acl.rs b/crates/imap/src/op/acl.rs index 8e8b030a..0ad6806e 100644 --- a/crates/imap/src/op/acl.rs +++ b/crates/imap/src/op/acl.rs @@ -40,8 +40,12 @@ use jmap_proto::{ error::method::MethodError, object::{index::ObjectIndexBuilder, Object}, types::{ - acl::Acl, collection::Collection, id::Id, property::Property, state::StateChange, - type_state::DataType, value::Value, + acl::Acl, + collection::Collection, + property::Property, + state::StateChange, + type_state::DataType, + value::{AclGrant, Value}, }, }; use store::write::{assert::HashedValue, log::ChangeLogBuilder, BatchBuilder}; @@ -65,64 +69,58 @@ impl Session { .inner .properties .get(&Property::Acl) - .and_then(|v| v.as_list()) + .and_then(|v| v.as_acl()) { - for item in acls.chunks_exact(2) { - if let ( - Some(Value::Id(id)), - Some(Value::UnsignedInt(acl_bits)), - ) = (item.first(), item.last()) + for item in acls { + if let Some(account_name) = data + .jmap + .directory + .query(QueryBy::Id(item.account_id), false) + .await + .unwrap_or_default() + .map(|p| p.name) { - if let Some(account_name) = data - .jmap - .directory - .query(QueryBy::Id(id.document_id()), false) - .await - .unwrap_or_default() - .map(|p| p.name) - { - let mut rights = Vec::new(); + let mut rights = Vec::new(); - for acl in Bitmap::::from(*acl_bits) { - match acl { - Acl::Read => { - rights.push(Rights::Lookup); - } - Acl::Modify => { - rights.push(Rights::CreateMailbox); - } - Acl::Delete => { - rights.push(Rights::DeleteMailbox); - } - Acl::ReadItems => { - rights.push(Rights::Read); - } - Acl::AddItems => { - rights.push(Rights::Insert); - } - Acl::ModifyItems => { - rights.push(Rights::Write); - rights.push(Rights::Seen); - } - Acl::RemoveItems => { - rights.push(Rights::DeleteMessages); - rights.push(Rights::Expunge); - } - Acl::CreateChild => { - rights.push(Rights::CreateMailbox); - } - Acl::Administer => { - rights.push(Rights::Administer); - } - Acl::Submit => { - rights.push(Rights::Post); - } - Acl::None => (), + for acl in item.grants { + match acl { + Acl::Read => { + rights.push(Rights::Lookup); } + Acl::Modify => { + rights.push(Rights::CreateMailbox); + } + Acl::Delete => { + rights.push(Rights::DeleteMailbox); + } + Acl::ReadItems => { + rights.push(Rights::Read); + } + Acl::AddItems => { + rights.push(Rights::Insert); + } + Acl::ModifyItems => { + rights.push(Rights::Write); + rights.push(Rights::Seen); + } + Acl::RemoveItems => { + rights.push(Rights::DeleteMessages); + rights.push(Rights::Expunge); + } + Acl::CreateChild => { + rights.push(Rights::CreateMailbox); + } + Acl::Administer => { + rights.push(Rights::Administer); + } + Acl::Submit => { + rights.push(Rights::Post); + } + Acl::None => (), } - - permissions.push((account_name, rights)); } + + permissions.push((account_name, rights)); } } } @@ -245,13 +243,13 @@ impl Session { }; // Obtain principal id - let (acl_account_id, id) = match data + let acl_account_id = match data .jmap .directory .query(QueryBy::Name(arguments.identifier.as_ref().unwrap()), false) .await { - Ok(Some(principal)) => (principal.id, Value::Id(Id::from(principal.id))), + Ok(Some(principal)) => principal.id, Ok(None) => { data.write_bytes( StatusResponse::no("Account does not exist") @@ -283,7 +281,7 @@ impl Session { ) }) .unwrap_or_else(|| (ModRightsOp::Replace, Bitmap::new())); - let acl = if let Value::List(acl) = + let acl = if let Value::Acl(acl) = changes .properties .get_mut_or_insert_with(Property::Acl, || { @@ -292,7 +290,7 @@ impl Session { .properties .get(&Property::Acl) .cloned() - .unwrap_or_else(|| Value::List(Vec::new())) + .unwrap_or_else(|| Value::Acl(Vec::new())) }) { acl } else { @@ -305,48 +303,37 @@ impl Session { return; }; - if let Some(idx) = acl.iter().position(|item| item == &id) { + if let Some(item) = acl + .iter_mut() + .find(|item| item.account_id == acl_account_id) + { match op { ModRightsOp::Replace => { if !rights.is_empty() { - acl[idx + 1] = Value::UnsignedInt(rights.into()); + item.grants = rights; } else { - acl.remove(idx); - acl.remove(idx); + acl.retain(|item| item.account_id != acl_account_id); } } - ModRightsOp::Add | ModRightsOp::Remove => { - if let Some(Value::UnsignedInt(current)) = acl.get_mut(idx + 1) { - let mut bitmap = Bitmap::from(*current); - if op == ModRightsOp::Add { - bitmap.union(&rights); - } else { - for right in rights { - bitmap.remove(right); - } - } - if !bitmap.is_empty() { - *current = bitmap.into(); - } else { - acl.remove(idx); - acl.remove(idx); - } - } else { - data.write_bytes( - StatusResponse::database_failure() - .with_tag(arguments.tag) - .into_bytes(), - ) - .await; - return; + ModRightsOp::Add => { + item.grants.union(&rights); + } + ModRightsOp::Remove => { + for right in rights { + item.grants.remove(right); + } + if item.grants.is_empty() { + acl.retain(|item| item.account_id != acl_account_id); } } } } else if !rights.is_empty() { match op { ModRightsOp::Add | ModRightsOp::Replace => { - acl.push(id); - acl.push(Value::UnsignedInt(rights.into())); + acl.push(AclGrant { + account_id: acl_account_id, + grants: rights, + }); } ModRightsOp::Remove => (), } diff --git a/crates/jmap-proto/src/object/index.rs b/crates/jmap-proto/src/object/index.rs index 50a98c0b..7ba65eea 100644 --- a/crates/jmap-proto/src/object/index.rs +++ b/crates/jmap-proto/src/object/index.rs @@ -376,64 +376,51 @@ fn merge_batch( } IndexAs::Acl => { match (current_value, &value) { - (Value::List(current_value), Value::List(value)) => { + (Value::Acl(current_value), Value::Acl(value)) => { // Remove deleted ACLs - for item in current_value.chunks_exact(2) { - if let Some(Value::Id(id)) = item.first() { - if !value.contains(&Value::Id(*id)) { - batch.ops.push(Operation::acl(id.document_id(), None)); - } + for current_item in current_value { + if !value + .iter() + .any(|item| item.account_id == current_item.account_id) + { + batch + .ops + .push(Operation::acl(current_item.account_id, None)); } } // Update ACLs - for item in value.chunks_exact(2) { - if let (Some(Value::Id(id)), Some(Value::UnsignedInt(acl))) = - (item.first(), item.last()) - { - let mut add_item = true; - for current_item in current_value.chunks_exact(2) { - if let ( - Some(Value::Id(current_id)), - Some(Value::UnsignedInt(current_acl)), - ) = (current_item.first(), current_item.last()) - { - if id == current_id { - if acl == current_acl { - add_item = false; - } - break; - } + for item in value { + let mut add_item = true; + for current_item in current_value { + if item.account_id == current_item.account_id { + if item.grants == current_item.grants { + add_item = false; } - } - if add_item { - batch.ops.push(Operation::acl( - id.document_id(), - acl.serialize().into(), - )); + break; } } - } - } - (Value::Null, Value::List(values)) => { - // Add all ACLs - for item in values.chunks_exact(2) { - if let (Some(Value::Id(id)), Some(Value::UnsignedInt(acl))) = - (item.first(), item.last()) - { + if add_item { batch.ops.push(Operation::acl( - id.document_id(), - acl.serialize().into(), + item.account_id, + item.grants.bitmap.serialize().into(), )); } } } - (Value::List(current_values), Value::Null) => { + (Value::Null, Value::Acl(values)) => { + // Add all ACLs + for item in values { + batch.ops.push(Operation::acl( + item.account_id, + item.grants.bitmap.serialize().into(), + )); + } + } + (Value::Acl(current_values), Value::Null) => { // Remove all ACLs - for item in current_values.chunks_exact(2) { - if let Some(Value::Id(id)) = item.first() { - batch.ops.push(Operation::acl(id.document_id(), None)); - } + for item in current_values { + batch.ops.push(Operation::acl(item.account_id, None)); } } _ => {} @@ -556,16 +543,16 @@ fn build_batch( }); } } - (Value::List(values), IndexAs::Acl) => { - for item in values.chunks_exact(2) { - if let (Some(Value::Id(id)), Some(Value::UnsignedInt(acl))) = - (item.first(), item.last()) - { - batch.ops.push(Operation::acl( - id.document_id(), - if set { acl.serialize().into() } else { None }, - )); - } + (Value::Acl(values), IndexAs::Acl) => { + for item in values { + batch.ops.push(Operation::acl( + item.account_id, + if set { + item.grants.bitmap.serialize().into() + } else { + None + }, + )); } } (value, IndexAs::HasProperty) if value != &Value::Null => { diff --git a/crates/jmap-proto/src/object/mod.rs b/crates/jmap-proto/src/object/mod.rs index ceaea041..fae0c00b 100644 --- a/crates/jmap-proto/src/object/mod.rs +++ b/crates/jmap-proto/src/object/mod.rs @@ -32,15 +32,20 @@ use std::slice::Iter; use store::{ write::{DeserializeFrom, SerializeInto, ToBitmaps}, - Deserialize, Serialize, + Deserialize, Serialize, U64_LEN, }; use utils::{ codec::leb128::{Leb128Iterator, Leb128Vec}, - map::vec_map::VecMap, + map::{bitmap::Bitmap, vec_map::VecMap}, }; use crate::types::{ - blob::BlobId, date::UTCDate, id::Id, keyword::Keyword, property::Property, value::Value, + blob::BlobId, + date::UTCDate, + id::Id, + keyword::Keyword, + property::Property, + value::{AclGrant, Value}, }; #[derive(Debug, Clone, Default, serde::Serialize, PartialEq, Eq)] @@ -122,7 +127,8 @@ const BLOB: u8 = 7; const KEYWORD: u8 = 8; const LIST: u8 = 9; const OBJECT: u8 = 10; -const NULL: u8 = 11; +const ACL: u8 = 11; +const NULL: u8 = 12; impl Serialize for Value { fn serialize(self) -> Vec { @@ -169,6 +175,29 @@ impl SerializeInto for Object { } } } + +impl SerializeInto for AclGrant { + fn serialize_into(&self, buf: &mut Vec) { + buf.push_leb128(self.account_id); + buf.extend_from_slice(self.grants.bitmap.to_be_bytes().as_slice()); + } +} + +impl DeserializeFrom for AclGrant { + fn deserialize_from(bytes: &mut Iter<'_, u8>) -> Option { + let account_id = bytes.next_leb128()?; + let mut grants = [0u8; U64_LEN]; + for byte in grants.iter_mut() { + *byte = *bytes.next()?; + } + + Some(Self { + account_id, + grants: Bitmap::from(u64::from_be_bytes(grants)), + }) + } +} + impl DeserializeFrom for Object { fn deserialize_from(bytes: &mut Iter<'_, u8>) -> Option> { let len = bytes.next_leb128()?; @@ -227,6 +256,13 @@ impl SerializeInto for Value { buf.push(BLOB); v.serialize_into(buf); } + Value::Acl(v) => { + buf.push(ACL); + buf.push_leb128(v.len()); + for i in v { + i.serialize_into(buf); + } + } Value::Null => { buf.push(NULL); } @@ -257,6 +293,14 @@ impl DeserializeFrom for Value { } OBJECT => Some(Value::Object(Object::deserialize_from(bytes)?)), BLOB => Some(Value::Blob(Vec::deserialize_from(bytes)?)), + ACL => { + let len = bytes.next_leb128()?; + let mut items = Vec::with_capacity(len); + for _ in 0..len { + items.push(AclGrant::deserialize_from(bytes)?); + } + Some(Value::Acl(items)) + } NULL => Some(Value::Null), _ => None, } diff --git a/crates/jmap-proto/src/types/value.rs b/crates/jmap-proto/src/types/value.rs index 59fdce87..6bb86dc6 100644 --- a/crates/jmap-proto/src/types/value.rs +++ b/crates/jmap-proto/src/types/value.rs @@ -25,6 +25,7 @@ use std::{borrow::Cow, fmt::Display}; use mail_parser::{Addr, DateTime, Group}; use serde::Serialize; +use utils::map::bitmap::Bitmap; use crate::{ object::Object, @@ -33,6 +34,7 @@ use crate::{ }; use super::{ + acl::Acl, any_id::AnyId, blob::BlobId, date::UTCDate, @@ -53,11 +55,18 @@ pub enum Value { Keyword(Keyword), List(Vec), Object(Object), + Acl(Vec), Blob(Vec), #[default] Null, } +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)] +pub struct AclGrant { + pub account_id: u32, + pub grants: Bitmap, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum SetValue { Value(Value), @@ -282,6 +291,13 @@ impl Value { } } + pub fn as_acl(&self) -> Option<&Vec> { + match self { + Value::Acl(l) => Some(l), + _ => None, + } + } + pub fn as_uint(&self) -> Option { match self { Value::UnsignedInt(u) => Some(*u), diff --git a/crates/jmap/src/auth/acl.rs b/crates/jmap/src/auth/acl.rs index 7193faad..edccb84e 100644 --- a/crates/jmap/src/auth/acl.rs +++ b/crates/jmap/src/auth/acl.rs @@ -29,7 +29,7 @@ use jmap_proto::{ acl::Acl, collection::Collection, property::Property, - value::{MaybePatchValue, Value}, + value::{AclGrant, MaybePatchValue, Value}, }, }; use store::{ @@ -272,14 +272,13 @@ impl JMAP { ) -> Result<(), SetError> { match acl_changes { MaybePatchValue::Value(Value::List(values)) => { - changes.properties.set( - Property::Acl, - Value::List(self.map_acl_accounts(values).await?), - ); + changes + .properties + .set(Property::Acl, Value::Acl(self.map_acl_set(values).await?)); } MaybePatchValue::Patch(patch) => { - let patch = self.map_acl_accounts(patch).await?; - let acl = if let Value::List(acl) = + let (mut patch, is_update) = self.map_acl_patch(patch).await?; + let acl = if let Value::Acl(acl) = changes .properties .get_mut_or_insert_with(Property::Acl, || { @@ -287,7 +286,7 @@ impl JMAP { .and_then(|current| { current.inner.properties.get(&Property::Acl).cloned() }) - .unwrap_or_else(|| Value::List(Vec::new())) + .unwrap_or_else(|| Value::Acl(Vec::new())) }) { acl } else { @@ -295,56 +294,37 @@ impl JMAP { .with_property(Property::Acl) .with_description("Invalid ACL value found.")); }; - let account_id = patch.first().unwrap().as_id().unwrap(); - match patch.len() { - 2 => { - let acl_update = patch.last().unwrap().as_uint().unwrap(); - if let Some(idx) = - acl.iter().position(|item| item.as_id() == Some(account_id)) + + if let Some(is_set) = is_update { + if !patch.grants.is_empty() { + if let Some(acl_item) = acl + .iter_mut() + .find(|item| item.account_id == patch.account_id) { - if acl_update != 0 { - acl[idx + 1] = Value::UnsignedInt(acl_update); - } else if acl.len() > 2 { - acl.remove(idx); - acl.remove(idx); + let item = patch.grants.pop().unwrap(); + if is_set { + acl_item.grants.insert(item); } else { - acl.clear(); + acl_item.grants.remove(item); + if acl_item.grants.is_empty() { + acl.retain(|item| item.account_id != patch.account_id); + } } - } else if acl_update != 0 { - acl.push(Value::Id(*account_id)); - acl.push(Value::UnsignedInt(acl_update)); + } else if is_set { + acl.push(patch); } } - 3 => { - let acl_item = Acl::from(patch[1].as_uint().unwrap()); - let set = patch[2].as_bool().unwrap_or(false); - if let Some(idx) = - acl.iter().position(|item| item.as_id() == Some(account_id)) - { - if let Some(Value::UnsignedInt(current)) = acl.get_mut(idx + 1) { - let mut bitmap = Bitmap::from(*current); - if set { - bitmap.insert(acl_item); - } else { - bitmap.remove(acl_item); - } - if !bitmap.is_empty() { - *current = bitmap.into(); - } else { - acl.remove(idx); - acl.remove(idx); - } - } else { - return Err(SetError::invalid_properties() - .with_property(Property::Acl) - .with_description("Invalid ACL value found.")); - } - } else if set { - acl.push(Value::Id(*account_id)); - acl.push(Value::UnsignedInt(Bitmap::new().with_item(acl_item).into())); - } + } else if !patch.grants.is_empty() { + if let Some(acl_item) = acl + .iter_mut() + .find(|item| item.account_id == patch.account_id) + { + acl_item.grants = patch.grants; + } else { + acl.push(patch); } - _ => unreachable!(), + } else { + acl.retain(|item| item.account_id != patch.account_id); } } _ => { @@ -358,38 +338,29 @@ impl JMAP { pub async fn acl_get( &self, - value: &[Value], + value: &[AclGrant], access_token: &AccessToken, account_id: u32, ) -> Value { if access_token.is_member(account_id) - || value.chunks_exact(2).any(|item| { - access_token.is_member( - item.first() - .and_then(|v| v.as_id().map(|id| id.document_id())) - .unwrap_or(u32::MAX), - ) && Bitmap::from(item.last().and_then(|a| a.as_uint()).unwrap_or_default()) - .contains(Acl::Administer) + || value.iter().any(|item| { + access_token.is_member(item.account_id) && item.grants.contains(Acl::Administer) }) { let mut acl_obj = Object::with_capacity(value.len() / 2); - for item in value.chunks_exact(2) { - if let (Some(Value::Id(id)), Some(Value::UnsignedInt(acl_bits))) = - (item.first(), item.last()) + for item in value { + if let Some(principal) = self + .directory + .query(QueryBy::Id(item.account_id), false) + .await + .unwrap_or_default() { - if let Some(principal) = self - .directory - .query(QueryBy::Id(id.document_id()), false) - .await - .unwrap_or_default() - { - acl_obj.append( - Property::_T(principal.name), - Bitmap::::from(*acl_bits) - .map(|acl_item| Value::Text(acl_item.to_string())) - .collect::>(), - ); - } + acl_obj.append( + Property::_T(principal.name), + item.grants + .map(|acl_item| Value::Text(acl_item.to_string())) + .collect::>(), + ); } } @@ -404,61 +375,59 @@ impl JMAP { changes: &Object, current: &Option>>, ) { - if let Value::List(acl_changes) = changes.get(&Property::Acl) { + if let Value::Acl(acl_changes) = changes.get(&Property::Acl) { let access_tokens = &self.access_tokens; - if let Some(Value::List(acl_current)) = current + if let Some(Value::Acl(acl_current)) = current .as_ref() .and_then(|current| current.inner.properties.get(&Property::Acl)) { - for current_item in acl_current.chunks_exact(2) { + for current_item in acl_current { let mut invalidate = true; - for change_item in acl_changes.chunks_exact(2) { - if change_item.first() == current_item.first() { - invalidate = change_item.last() != current_item.last(); + for change_item in acl_changes { + if change_item.account_id == current_item.account_id { + invalidate = change_item.grants != current_item.grants; break; } } if invalidate { - if let Some(Value::Id(id)) = current_item.first() { - access_tokens.remove(&id.document_id()); - } + access_tokens.remove(¤t_item.account_id); } } - for change_item in acl_changes.chunks_exact(2) { + for change_item in acl_changes { let mut invalidate = true; - for current_item in acl_current.chunks_exact(2) { - if change_item.first() == current_item.first() { - invalidate = change_item.last() != current_item.last(); + for current_item in acl_current { + if change_item.account_id == current_item.account_id { + invalidate = change_item.grants != current_item.grants; break; } } if invalidate { - if let Some(Value::Id(id)) = change_item.first() { - access_tokens.remove(&id.document_id()); - } + access_tokens.remove(&change_item.account_id); } } } else { for value in acl_changes { - if let Value::Id(id) = value { - access_tokens.remove(&id.document_id()); - } + access_tokens.remove(&value.account_id); } } } } - async fn map_acl_accounts(&self, mut acl_set: Vec) -> Result, SetError> { - for item in &mut acl_set { - if let Value::Text(account_name) = item { + async fn map_acl_set(&self, acl_set: Vec) -> Result, SetError> { + let mut acls = Vec::with_capacity(acl_set.len() / 2); + for item in acl_set.chunks_exact(2) { + if let (Value::Text(account_name), Value::UnsignedInt(grants)) = (&item[0], &item[1]) { match self .directory .query(QueryBy::Name(account_name), false) .await { Ok(Some(principal)) => { - *item = Value::Id(principal.id.into()); + acls.push(AclGrant { + account_id: principal.id, + grants: Bitmap::from(*grants), + }); } Ok(None) => { return Err(SetError::invalid_properties() @@ -471,10 +440,47 @@ impl JMAP { .with_description("Temporary server failure during lookup")); } } + } else { + return Err(SetError::invalid_properties() + .with_property(Property::Acl) + .with_description("Invalid ACL value found.")); } } - Ok(acl_set) + Ok(acls) + } + + async fn map_acl_patch( + &self, + acl_patch: Vec, + ) -> Result<(AclGrant, Option), SetError> { + if let (Value::Text(account_name), Value::UnsignedInt(grants)) = + (&acl_patch[0], &acl_patch[1]) + { + match self + .directory + .query(QueryBy::Name(account_name), false) + .await + { + Ok(Some(principal)) => Ok(( + AclGrant { + account_id: principal.id, + grants: Bitmap::from(*grants), + }, + acl_patch.get(2).map(|v| v.as_bool().unwrap_or(false)), + )), + Ok(None) => Err(SetError::invalid_properties() + .with_property(Property::Acl) + .with_description(format!("Account {account_name} does not exist."))), + _ => Err(SetError::forbidden() + .with_property(Property::Acl) + .with_description("Temporary server failure during lookup")), + } + } else { + Err(SetError::invalid_properties() + .with_property(Property::Acl) + .with_description("Invalid ACL value found.")) + } } } @@ -485,14 +491,10 @@ pub trait EffectiveAcl { impl EffectiveAcl for Object { fn effective_acl(&self, access_token: &AccessToken) -> Bitmap { let mut acl = Bitmap::::new(); - if let Some(Value::List(permissions)) = self.properties.get(&Property::Acl) { - for item in permissions.chunks(2) { - if let (Some(Value::Id(account_id)), Some(Value::UnsignedInt(acl_bits))) = - (item.first(), item.last()) - { - if access_token.is_member(account_id.document_id()) { - acl.union(&Bitmap::from(*acl_bits)); - } + if let Some(Value::Acl(permissions)) = self.properties.get(&Property::Acl) { + for item in permissions { + if access_token.is_member(item.account_id) { + acl.union(&item.grants); } } } diff --git a/crates/jmap/src/email/ingest.rs b/crates/jmap/src/email/ingest.rs index 69b9ed32..5f6132d0 100644 --- a/crates/jmap/src/email/ingest.rs +++ b/crates/jmap/src/email/ingest.rs @@ -181,6 +181,14 @@ impl JMAP { .results .is_empty() { + tracing::debug!( + context = "email_ingest", + event = "skip", + account_id = ?params.account_id, + from = ?message.from(), + message_id = message_id, + "Duplicate message skipped."); + return Ok(IngestedEmail { id: Id::default(), change_id: u64::MAX, diff --git a/crates/jmap/src/mailbox/get.rs b/crates/jmap/src/mailbox/get.rs index 985bfc44..5133e3ed 100644 --- a/crates/jmap/src/mailbox/get.rs +++ b/crates/jmap/src/mailbox/get.rs @@ -233,7 +233,7 @@ impl JMAP { values .properties .get(&Property::Acl) - .and_then(|v| v.as_list()) + .and_then(|v| v.as_acl()) .map(|v| &v[..]) .unwrap_or_else(|| &[]), access_token, diff --git a/tests/src/jmap/auth_limits.rs b/tests/src/jmap/auth_limits.rs index fe809fee..b1775af5 100644 --- a/tests/src/jmap/auth_limits.rs +++ b/tests/src/jmap/auth_limits.rs @@ -128,7 +128,7 @@ pub async fn test(params: &mut JMAPTest) { ); assert!(client.session().account(&account_id).unwrap().is_personal()); - // Uploads up to 50000000 bytes should be allowed + // Uploads up to 5000000 bytes should be allowed assert_eq!( client .upload(None, vec![b'A'; 5000000], None) diff --git a/tests/src/jmap/mod.rs b/tests/src/jmap/mod.rs index 8872ead3..71e639be 100644 --- a/tests/src/jmap/mod.rs +++ b/tests/src/jmap/mod.rs @@ -190,7 +190,7 @@ max-objects = 100000 max-concurrent = 8 [jmap.protocol.upload] -max-size = 50000000 +max-size = 5000000 max-concurrent = 4 ttl = "1m" @@ -289,30 +289,30 @@ pub async fn jmap_tests() { //assert_is_empty(params.server.clone()).await; let coco = 1; - //email_query::test(&mut params, delete).await; - /*email_get::test(&mut params).await; + /*email_query::test(&mut params, delete).await; + email_get::test(&mut params).await; email_set::test(&mut params).await; email_parse::test(&mut params).await; email_search_snippet::test(&mut params).await; email_changes::test(&mut params).await; email_query_changes::test(&mut params).await; email_copy::test(&mut params).await; - thread_get::test(&mut params).await;*/ - //thread_merge::test(&mut params).await; - //mailbox::test(&mut params).await; - //delivery::test(&mut params).await; - /*auth_acl::test(&mut params).await; + thread_get::test(&mut params).await; + thread_merge::test(&mut params).await; + mailbox::test(&mut params).await; + delivery::test(&mut params).await; + auth_acl::test(&mut params).await;*/ auth_limits::test(&mut params).await; auth_oauth::test(&mut params).await; event_source::test(&mut params).await; push_subscription::test(&mut params).await; sieve_script::test(&mut params).await; - vacation_response::test(&mut params).await;*/ + vacation_response::test(&mut params).await; email_submission::test(&mut params).await; - /*websocket::test(&mut params).await; + websocket::test(&mut params).await; quota::test(&mut params).await; crypto::test(&mut params).await; - blob::test(&mut params).await;*/ + blob::test(&mut params).await; if delete { params.temp_dir.delete(); diff --git a/tests/src/jmap/thread_merge.rs b/tests/src/jmap/thread_merge.rs index b796cb83..67607997 100644 --- a/tests/src/jmap/thread_merge.rs +++ b/tests/src/jmap/thread_merge.rs @@ -29,7 +29,7 @@ use crate::{ }; use jmap::{email::ingest::IngestEmail, IngestError}; use jmap_client::{email, mailbox::Role}; -use jmap_proto::types::{collection::Collection, id::Id, property::Property}; +use jmap_proto::types::{collection::Collection, id::Id}; use mail_parser::{mailbox::mbox::MessageIterator, MessageParser}; use store::{ ahash::{AHashMap, AHashSet}, @@ -236,6 +236,12 @@ async fn test_multi_thread(params: &mut JMAPTest) { .await .unwrap() .take_id(); + params + .client + .set_default_account_id(Id::new(0u64).to_string()) + .mailbox_create("Other mailbox", None::, Role::None) + .await + .unwrap(); let mailbox_id = Id::from_bytes(mailbox_id_str.as_bytes()) .unwrap() .document_id(); @@ -294,7 +300,7 @@ async fn test_multi_thread(params: &mut JMAPTest) { messages as u64, params .server - .get_tag(0, Collection::Email, Property::MailboxIds, mailbox_id,) + .get_document_ids(0, Collection::Email) .await .unwrap() .unwrap()