mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-10-26 20:35:55 +08:00
ACL storage optimization
This commit is contained in:
parent
be78a26193
commit
31719963c9
10 changed files with 319 additions and 269 deletions
|
|
@ -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<T: AsyncRead> Session<T> {
|
|||
.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::<Acl>::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<T: AsyncRead> Session<T> {
|
|||
};
|
||||
|
||||
// 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<T: AsyncRead> Session<T> {
|
|||
)
|
||||
})
|
||||
.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<T: AsyncRead> Session<T> {
|
|||
.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<T: AsyncRead> Session<T> {
|
|||
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 => (),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
|
|
@ -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<u8> {
|
||||
|
|
@ -169,6 +175,29 @@ impl SerializeInto for Object<Value> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeInto for AclGrant {
|
||||
fn serialize_into(&self, buf: &mut Vec<u8>) {
|
||||
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<Self> {
|
||||
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<Value> {
|
||||
fn deserialize_from(bytes: &mut Iter<'_, u8>) -> Option<Object<Value>> {
|
||||
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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Value>),
|
||||
Object(Object<Value>),
|
||||
Acl(Vec<AclGrant>),
|
||||
Blob(Vec<u8>),
|
||||
#[default]
|
||||
Null,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct AclGrant {
|
||||
pub account_id: u32,
|
||||
pub grants: Bitmap<Acl>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SetValue {
|
||||
Value(Value),
|
||||
|
|
@ -282,6 +291,13 @@ impl Value {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn as_acl(&self) -> Option<&Vec<AclGrant>> {
|
||||
match self {
|
||||
Value::Acl(l) => Some(l),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_uint(&self) -> Option<u64> {
|
||||
match self {
|
||||
Value::UnsignedInt(u) => Some(*u),
|
||||
|
|
|
|||
|
|
@ -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::<Acl>::from(*acl_bits)
|
||||
.map(|acl_item| Value::Text(acl_item.to_string()))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
acl_obj.append(
|
||||
Property::_T(principal.name),
|
||||
item.grants
|
||||
.map(|acl_item| Value::Text(acl_item.to_string()))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -404,61 +375,59 @@ impl JMAP {
|
|||
changes: &Object<Value>,
|
||||
current: &Option<HashedValue<Object<Value>>>,
|
||||
) {
|
||||
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<Value>) -> Result<Vec<Value>, SetError> {
|
||||
for item in &mut acl_set {
|
||||
if let Value::Text(account_name) = item {
|
||||
async fn map_acl_set(&self, acl_set: Vec<Value>) -> Result<Vec<AclGrant>, 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<Value>,
|
||||
) -> Result<(AclGrant, Option<bool>), 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<Value> {
|
||||
fn effective_acl(&self, access_token: &AccessToken) -> Bitmap<Acl> {
|
||||
let mut acl = Bitmap::<Acl>::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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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::<String>, 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()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue