mirror of
https://github.com/stalwartlabs/stalwart.git
synced 2026-09-11 16:06:28 +00:00
ACL method and reports
This commit is contained in:
@@ -14,6 +14,7 @@ pub struct DavConfig {
|
||||
pub max_lock_timeout: u64,
|
||||
pub max_locks_per_user: usize,
|
||||
pub max_changes: usize,
|
||||
pub max_match_results: usize,
|
||||
}
|
||||
|
||||
impl DavConfig {
|
||||
@@ -33,6 +34,9 @@ impl DavConfig {
|
||||
.property("dav.limits.max-locks-per-user")
|
||||
.unwrap_or(10),
|
||||
max_changes: config.property("dav.limits.max-changes").unwrap_or(1000),
|
||||
max_match_results: config
|
||||
.property("dav.limits.max-match-results")
|
||||
.unwrap_or(1000),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +170,18 @@ impl Display for ReportSet {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ReportSet::SyncCollection => write!(f, "<D:sync-collection/>"),
|
||||
ReportSet::ExpandProperty => write!(f, "<D:expand-property/>"),
|
||||
ReportSet::AddressbookQuery => write!(f, "<C:addressbook-query/>"),
|
||||
ReportSet::AddressbookMultiGet => write!(f, "<C:addressbook-multiget/>"),
|
||||
ReportSet::CalendarQuery => write!(f, "<C:calendar-query/>"),
|
||||
ReportSet::CalendarMultiGet => write!(f, "<C:calendar-multiget/>"),
|
||||
ReportSet::FreeBusyQuery => write!(f, "<C:free-busy-query/>"),
|
||||
ReportSet::AclPrincipalPropSet => write!(f, "<D:acl-principal-prop-set/>"),
|
||||
ReportSet::PrincipalMatch => write!(f, "<D:principal-match/>"),
|
||||
ReportSet::PrincipalPropertySearch => write!(f, "<D:principal-property-search/>"),
|
||||
ReportSet::PrincipalSearchPropertySet => {
|
||||
write!(f, "<D:principal-search-property-set/>")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,6 +160,16 @@ pub enum DavValue {
|
||||
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum ReportSet {
|
||||
SyncCollection,
|
||||
ExpandProperty,
|
||||
AddressbookQuery,
|
||||
AddressbookMultiGet,
|
||||
CalendarQuery,
|
||||
CalendarMultiGet,
|
||||
FreeBusyQuery,
|
||||
AclPrincipalPropSet,
|
||||
PrincipalMatch,
|
||||
PrincipalPropertySearch,
|
||||
PrincipalSearchPropertySet,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
||||
@@ -338,3 +338,9 @@ impl ArchivedDeadProperty {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PropertyUpdate {
|
||||
pub fn has_changes(&self) -> bool {
|
||||
!self.set.is_empty() || !self.remove.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ pub struct SyncToken(pub String);
|
||||
#[repr(transparent)]
|
||||
pub struct Href(pub String);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
|
||||
#[repr(transparent)]
|
||||
pub struct List<T: Display>(pub Vec<T>);
|
||||
|
||||
@@ -5,21 +5,53 @@
|
||||
*/
|
||||
|
||||
use common::{Server, auth::AccessToken, sharing::EffectiveAcl};
|
||||
use dav_proto::schema::{
|
||||
property::Privilege,
|
||||
response::{Ace, GrantDeny, Href, Principal},
|
||||
use dav_proto::{
|
||||
RequestHeaders,
|
||||
schema::{
|
||||
property::{DavProperty, Privilege, WebDavProperty},
|
||||
request::{AclPrincipalPropSet, PropFind},
|
||||
response::{Ace, BaseCondition, GrantDeny, Href, MultiStatus, Principal},
|
||||
},
|
||||
};
|
||||
use directory::{QueryBy, backend::internal::PrincipalField};
|
||||
use directory::{QueryBy, Type, backend::internal::PrincipalField};
|
||||
use groupware::{calendar::Calendar, contact::AddressBook, file::FileNode};
|
||||
use http_proto::HttpResponse;
|
||||
use hyper::StatusCode;
|
||||
use jmap_proto::types::{acl::Acl, collection::Collection, value::ArchivedAclGrant};
|
||||
use jmap_proto::types::{
|
||||
acl::Acl,
|
||||
collection::Collection,
|
||||
property::Property,
|
||||
value::{AclGrant, ArchivedAclGrant},
|
||||
};
|
||||
use rkyv::vec::ArchivedVec;
|
||||
use store::ahash::AHashSet;
|
||||
use store::{
|
||||
ahash::AHashSet,
|
||||
roaring::RoaringBitmap,
|
||||
write::{AlignedBytes, Archive},
|
||||
};
|
||||
use trc::AddContext;
|
||||
use utils::map::bitmap::Bitmap;
|
||||
|
||||
use crate::{DavError, DavResource};
|
||||
use crate::{
|
||||
DavError, DavErrorCondition, DavResource, common::uri::DavUriResource,
|
||||
principal::propfind::PrincipalPropFind,
|
||||
};
|
||||
|
||||
pub(crate) trait DavAclHandler: Sync + Send {
|
||||
fn handle_acl_prop_set(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
request: AclPrincipalPropSet,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
|
||||
fn validate_and_map_aces(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
acl: dav_proto::schema::request::Acl,
|
||||
collection: Collection,
|
||||
) -> impl Future<Output = crate::Result<Vec<AclGrant>>> + Send;
|
||||
|
||||
fn validate_and_map_parent_acl(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
@@ -48,6 +80,227 @@ pub(crate) trait DavAclHandler: Sync + Send {
|
||||
}
|
||||
|
||||
impl DavAclHandler for Server {
|
||||
async fn handle_acl_prop_set(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
mut request: AclPrincipalPropSet,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
let uri = self
|
||||
.validate_uri(access_token, headers.uri)
|
||||
.await
|
||||
.and_then(|uri| uri.into_owned_uri())?;
|
||||
let uri = self
|
||||
.map_uri_resource(uri)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
||||
|
||||
if !matches!(
|
||||
uri.collection,
|
||||
Collection::Calendar | Collection::AddressBook | Collection::FileNode
|
||||
) {
|
||||
return Err(DavError::Code(StatusCode::FORBIDDEN));
|
||||
}
|
||||
|
||||
// Validate ACLs
|
||||
self.validate_child_or_parent_acl(
|
||||
access_token,
|
||||
uri.account_id,
|
||||
uri.collection,
|
||||
uri.resource,
|
||||
None,
|
||||
Acl::Read,
|
||||
Acl::Read,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let archive = self
|
||||
.get_property::<Archive<AlignedBytes>>(
|
||||
uri.account_id,
|
||||
uri.collection,
|
||||
uri.resource,
|
||||
Property::Value,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
||||
|
||||
let acls = match uri.collection {
|
||||
Collection::FileNode => {
|
||||
&archive
|
||||
.unarchive::<FileNode>()
|
||||
.caused_by(trc::location!())?
|
||||
.acls
|
||||
}
|
||||
Collection::AddressBook => {
|
||||
&archive
|
||||
.unarchive::<AddressBook>()
|
||||
.caused_by(trc::location!())?
|
||||
.acls
|
||||
}
|
||||
Collection::Calendar => {
|
||||
&archive
|
||||
.unarchive::<Calendar>()
|
||||
.caused_by(trc::location!())?
|
||||
.acls
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let account_ids = RoaringBitmap::from_iter(acls.iter().map(|a| u32::from(a.account_id)));
|
||||
|
||||
let mut response = MultiStatus::new(Vec::with_capacity(16));
|
||||
|
||||
if !account_ids.is_empty() {
|
||||
if request.properties.is_empty() {
|
||||
request
|
||||
.properties
|
||||
.push(DavProperty::WebDav(WebDavProperty::DisplayName));
|
||||
}
|
||||
let request = PropFind::Prop(request.properties);
|
||||
self.prepare_principal_propfind_response(
|
||||
access_token,
|
||||
Collection::Principal,
|
||||
account_ids.into_iter(),
|
||||
&request,
|
||||
&mut response,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(HttpResponse::new(StatusCode::MULTI_STATUS).with_xml_body(response.to_string()))
|
||||
}
|
||||
|
||||
async fn validate_and_map_aces(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
acl: dav_proto::schema::request::Acl,
|
||||
collection: Collection,
|
||||
) -> crate::Result<Vec<AclGrant>> {
|
||||
let mut grants = Vec::with_capacity(acl.aces.len());
|
||||
for ace in acl.aces {
|
||||
if ace.invert {
|
||||
return Err(DavError::Condition(DavErrorCondition::new(
|
||||
StatusCode::FORBIDDEN,
|
||||
BaseCondition::NoInvert,
|
||||
)));
|
||||
}
|
||||
let privileges = match ace.grant_deny {
|
||||
GrantDeny::Grant(list) => list.0,
|
||||
GrantDeny::Deny(_) => {
|
||||
return Err(DavError::Condition(DavErrorCondition::new(
|
||||
StatusCode::FORBIDDEN,
|
||||
BaseCondition::GrantOnly,
|
||||
)));
|
||||
}
|
||||
};
|
||||
let principal_uri = match ace.principal {
|
||||
Principal::Href(href) => href.0,
|
||||
_ => {
|
||||
return Err(DavError::Condition(DavErrorCondition::new(
|
||||
StatusCode::FORBIDDEN,
|
||||
BaseCondition::AllowedPrincipal,
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let mut acls = Bitmap::<Acl>::default();
|
||||
for privilege in privileges {
|
||||
match privilege {
|
||||
Privilege::Read => {
|
||||
acls.insert(Acl::Read);
|
||||
acls.insert(Acl::ReadItems);
|
||||
}
|
||||
Privilege::Write => {
|
||||
acls.insert(Acl::Modify);
|
||||
acls.insert(Acl::Delete);
|
||||
acls.insert(Acl::ModifyItems);
|
||||
acls.insert(Acl::RemoveItems);
|
||||
}
|
||||
Privilege::WriteContent => {
|
||||
acls.insert(Acl::Modify);
|
||||
acls.insert(Acl::ModifyItems);
|
||||
acls.insert(Acl::RemoveItems);
|
||||
}
|
||||
Privilege::WriteProperties => {
|
||||
acls.insert(Acl::Modify);
|
||||
}
|
||||
Privilege::ReadCurrentUserPrivilegeSet
|
||||
| Privilege::Unlock
|
||||
| Privilege::Bind
|
||||
| Privilege::Unbind => {}
|
||||
Privilege::All => {
|
||||
return Err(DavError::Condition(DavErrorCondition::new(
|
||||
StatusCode::FORBIDDEN,
|
||||
BaseCondition::NoAbstract,
|
||||
)));
|
||||
}
|
||||
Privilege::ReadAcl => {}
|
||||
Privilege::WriteAcl => {
|
||||
acls.insert(Acl::Administer);
|
||||
}
|
||||
Privilege::ReadFreeBusy => {
|
||||
if collection == Collection::Calendar {
|
||||
acls.insert(Acl::ReadFreeBusy);
|
||||
} else {
|
||||
return Err(DavError::Condition(DavErrorCondition::new(
|
||||
StatusCode::FORBIDDEN,
|
||||
BaseCondition::NotSupportedPrivilege,
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if acls.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let principal_id = self
|
||||
.validate_uri(access_token, &principal_uri)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
DavError::Condition(DavErrorCondition::new(
|
||||
StatusCode::FORBIDDEN,
|
||||
BaseCondition::AllowedPrincipal,
|
||||
))
|
||||
})?
|
||||
.account_id
|
||||
.ok_or_else(|| {
|
||||
DavError::Condition(DavErrorCondition::new(
|
||||
StatusCode::FORBIDDEN,
|
||||
BaseCondition::AllowedPrincipal,
|
||||
))
|
||||
})?;
|
||||
|
||||
// Verify that the principal is a valid principal
|
||||
let principal = self
|
||||
.directory()
|
||||
.query(QueryBy::Id(principal_id), false)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or_else(|| {
|
||||
DavError::Condition(DavErrorCondition::new(
|
||||
StatusCode::FORBIDDEN,
|
||||
BaseCondition::AllowedPrincipal,
|
||||
))
|
||||
})?;
|
||||
if !matches!(principal.typ(), Type::Individual | Type::Group) {
|
||||
return Err(DavError::Condition(DavErrorCondition::new(
|
||||
StatusCode::FORBIDDEN,
|
||||
BaseCondition::AllowedPrincipal,
|
||||
)));
|
||||
}
|
||||
|
||||
grants.push(AclGrant {
|
||||
account_id: principal_id,
|
||||
grants: acls,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(grants)
|
||||
}
|
||||
|
||||
async fn validate_and_map_parent_acl(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
|
||||
@@ -11,7 +11,6 @@ use dav_proto::schema::request::{DavPropertyValue, DeadProperty};
|
||||
use dav_proto::schema::response::{BaseCondition, List, PropResponse};
|
||||
use dav_proto::{Condition, Depth, Timeout};
|
||||
use dav_proto::{RequestHeaders, schema::request::LockInfo};
|
||||
use groupware::file::hierarchy::FileHierarchy;
|
||||
use http_proto::HttpResponse;
|
||||
use hyper::StatusCode;
|
||||
use jmap_proto::types::collection::Collection;
|
||||
@@ -24,7 +23,7 @@ use store::{SERIALIZE_OBJ_02_V1, Serialize, SerializedVersion, U32_LEN};
|
||||
use trc::AddContext;
|
||||
|
||||
use super::ETag;
|
||||
use super::uri::{DavUriResource, OwnedUri, Urn};
|
||||
use super::uri::{DavUriResource, OwnedUri, UriResource, Urn};
|
||||
use crate::{DavError, DavErrorCondition, DavMethod};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
@@ -476,24 +475,17 @@ impl LockRequestHandler for Server {
|
||||
// Fetch eTag
|
||||
if needs_etag && resource_state.etag.is_none() {
|
||||
if resource_state.document_id.is_none() {
|
||||
let todo = "map cal, card";
|
||||
|
||||
resource_state.document_id = match resource_state.collection {
|
||||
Collection::FileNode => self
|
||||
.fetch_file_hierarchy(resource_state.account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.files
|
||||
.by_name(resource_state.path)
|
||||
.map(|f| f.document_id),
|
||||
Collection::Calendar => todo!(),
|
||||
Collection::CalendarEvent => todo!(),
|
||||
Collection::AddressBook => todo!(),
|
||||
Collection::ContactCard => todo!(),
|
||||
_ => None,
|
||||
}
|
||||
.unwrap_or(u32::MAX)
|
||||
.into();
|
||||
resource_state.document_id = self
|
||||
.map_uri_resource(UriResource {
|
||||
collection: resource_state.collection,
|
||||
account_id: resource_state.account_id,
|
||||
resource: resource_state.path.into(),
|
||||
})
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.map(|uri| uri.resource)
|
||||
.unwrap_or(u32::MAX)
|
||||
.into();
|
||||
}
|
||||
|
||||
if let Some(document_id) =
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::{Server, auth::AccessToken};
|
||||
use common::{
|
||||
Server,
|
||||
auth::{AccessToken, AsTenantId},
|
||||
};
|
||||
use dav_proto::{
|
||||
Depth, RequestHeaders,
|
||||
schema::{
|
||||
@@ -13,6 +16,10 @@ use dav_proto::{
|
||||
response::{BaseCondition, MultiStatus, PropStat, Response},
|
||||
},
|
||||
};
|
||||
use directory::{
|
||||
Type,
|
||||
backend::internal::{PrincipalField, manage::ManageDirectory},
|
||||
};
|
||||
use http_proto::HttpResponse;
|
||||
use hyper::StatusCode;
|
||||
use jmap_proto::types::collection::Collection;
|
||||
@@ -161,9 +168,20 @@ impl PropFindRequestHandler for Server {
|
||||
RoaringBitmap::from_iter(access_token.all_ids())
|
||||
} else {
|
||||
// Return all principals
|
||||
self.get_document_ids(u32::MAX, Collection::Principal)
|
||||
.await?
|
||||
.unwrap_or_default()
|
||||
let principals = self
|
||||
.store()
|
||||
.list_principals(
|
||||
None,
|
||||
access_token.tenant_id(),
|
||||
&[Type::Individual, Type::Group],
|
||||
&[PrincipalField::Name],
|
||||
0,
|
||||
0,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
RoaringBitmap::from_iter(principals.items.into_iter().map(|p| p.id()))
|
||||
};
|
||||
|
||||
self.prepare_principal_propfind_response(
|
||||
|
||||
@@ -9,6 +9,7 @@ use std::fmt::Display;
|
||||
use common::{Server, auth::AccessToken};
|
||||
|
||||
use directory::backend::internal::manage::ManageDirectory;
|
||||
use groupware::file::hierarchy::FileHierarchy;
|
||||
use http_proto::request::decode_path_element;
|
||||
use hyper::StatusCode;
|
||||
use jmap_proto::types::collection::Collection;
|
||||
@@ -29,7 +30,7 @@ pub(crate) enum Urn {
|
||||
|
||||
pub(crate) type UnresolvedUri<'x> = UriResource<Option<u32>, Option<&'x str>>;
|
||||
pub(crate) type OwnedUri<'x> = UriResource<u32, Option<&'x str>>;
|
||||
//pub(crate) type DocumentUri<'x> = UriResource<u32, u32>;
|
||||
pub(crate) type DocumentUri = UriResource<u32, u32>;
|
||||
|
||||
pub(crate) trait DavUriResource: Sync + Send {
|
||||
fn validate_uri<'x>(
|
||||
@@ -37,6 +38,11 @@ pub(crate) trait DavUriResource: Sync + Send {
|
||||
access_token: &AccessToken,
|
||||
uri: &'x str,
|
||||
) -> impl Future<Output = crate::Result<UnresolvedUri<'x>>> + Send;
|
||||
|
||||
fn map_uri_resource(
|
||||
&self,
|
||||
uri: OwnedUri<'_>,
|
||||
) -> impl Future<Output = trc::Result<Option<DocumentUri>>> + Send;
|
||||
}
|
||||
|
||||
impl DavUriResource for Server {
|
||||
@@ -95,6 +101,41 @@ impl DavUriResource for Server {
|
||||
|
||||
Ok(resource)
|
||||
}
|
||||
|
||||
async fn map_uri_resource(&self, uri: OwnedUri<'_>) -> trc::Result<Option<DocumentUri>> {
|
||||
let todo = "map cal, card";
|
||||
|
||||
let resource = if let Some(resource) = uri.resource {
|
||||
resource
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let document_id = match uri.collection {
|
||||
Collection::FileNode => self
|
||||
.fetch_file_hierarchy(uri.account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.files
|
||||
.by_name(resource)
|
||||
.map(|f| f.document_id),
|
||||
Collection::Calendar => todo!(),
|
||||
Collection::CalendarEvent => todo!(),
|
||||
Collection::AddressBook => todo!(),
|
||||
Collection::ContactCard => todo!(),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(document_id) = document_id {
|
||||
Ok(Some(DocumentUri {
|
||||
collection: uri.collection,
|
||||
account_id: uri.account_id,
|
||||
resource: document_id,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> UnresolvedUri<'x> {
|
||||
|
||||
@@ -13,7 +13,11 @@ use jmap_proto::types::{acl::Acl, collection::Collection, property::Property};
|
||||
use store::write::{AlignedBytes, Archive};
|
||||
use trc::AddContext;
|
||||
|
||||
use crate::{DavError, common::uri::DavUriResource, file::DavFileResource};
|
||||
use crate::{
|
||||
DavError,
|
||||
common::{acl::DavAclHandler, uri::DavUriResource},
|
||||
file::{DavFileResource, update_file_node},
|
||||
};
|
||||
|
||||
pub(crate) trait FileAclRequestHandler: Sync + Send {
|
||||
fn handle_file_acl_request(
|
||||
@@ -63,21 +67,48 @@ impl FileAclRequestHandler for Server {
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
||||
let node = node_.unarchive::<FileNode>().caused_by(trc::location!())?;
|
||||
let node = node_
|
||||
.to_unarchived::<FileNode>()
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// Validate ACL
|
||||
self.validate_file_acl(
|
||||
access_token,
|
||||
account_id,
|
||||
node,
|
||||
node.inner,
|
||||
Acl::Administer,
|
||||
Acl::Administer,
|
||||
)
|
||||
.await?;
|
||||
|
||||
for ace in request.aces {}
|
||||
let grants = self
|
||||
.validate_and_map_aces(access_token, request, Collection::FileNode)
|
||||
.await?;
|
||||
|
||||
todo!()
|
||||
if grants.len() != node.inner.acls.len()
|
||||
|| node
|
||||
.inner
|
||||
.acls
|
||||
.iter()
|
||||
.zip(grants.iter())
|
||||
.any(|(a, b)| a != b)
|
||||
{
|
||||
let mut new_node = node.deserialize().caused_by(trc::location!())?;
|
||||
new_node.acls = grants;
|
||||
update_file_node(
|
||||
self,
|
||||
access_token,
|
||||
node,
|
||||
new_node,
|
||||
account_id,
|
||||
resource.resource,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
}
|
||||
|
||||
Ok(HttpResponse::new(StatusCode::OK))
|
||||
}
|
||||
|
||||
async fn validate_file_acl(
|
||||
|
||||
@@ -365,7 +365,7 @@ async fn move_container(
|
||||
if parent_id != 0 && to_files.is_ancestor_of(from_document_id, parent_id - 1) {
|
||||
return Err(DavError::Code(StatusCode::BAD_GATEWAY));
|
||||
}
|
||||
let node = server
|
||||
let node_ = server
|
||||
.get_property::<Archive<AlignedBytes>>(
|
||||
from_account_id,
|
||||
Collection::FileNode,
|
||||
@@ -374,10 +374,11 @@ async fn move_container(
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?
|
||||
.into_deserialized::<FileNode>()
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
||||
let node = node_
|
||||
.to_unarchived::<FileNode>()
|
||||
.caused_by(trc::location!())?;
|
||||
let mut new_node = node.inner.clone();
|
||||
let mut new_node = node.deserialize().caused_by(trc::location!())?;
|
||||
new_node.parent_id = parent_id;
|
||||
if let Some(new_name) = destination.new_name {
|
||||
new_node.name = new_name;
|
||||
@@ -579,7 +580,7 @@ async fn overwrite_and_delete_item(
|
||||
let to_document_id = destination.document_id.unwrap();
|
||||
|
||||
// dest_node is the current file at the destination
|
||||
let dest_node = server
|
||||
let dest_node_ = server
|
||||
.get_property::<Archive<AlignedBytes>>(
|
||||
to_account_id,
|
||||
Collection::FileNode,
|
||||
@@ -588,12 +589,14 @@ async fn overwrite_and_delete_item(
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?
|
||||
.into_deserialized::<FileNode>()
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
||||
|
||||
let dest_node = dest_node_
|
||||
.to_unarchived::<FileNode>()
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// source_node is the file to be copied
|
||||
let source_node_ = server
|
||||
let source_node__ = server
|
||||
.get_property::<Archive<AlignedBytes>>(
|
||||
from_account_id,
|
||||
Collection::FileNode,
|
||||
@@ -602,16 +605,17 @@ async fn overwrite_and_delete_item(
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?
|
||||
.into_deserialized::<FileNode>()
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
||||
let source_node_ = source_node__
|
||||
.to_unarchived::<FileNode>()
|
||||
.caused_by(trc::location!())?;
|
||||
let mut source_node = source_node_.inner.clone();
|
||||
let mut source_node = source_node_.deserialize().caused_by(trc::location!())?;
|
||||
source_node.name = if let Some(new_name) = destination.new_name {
|
||||
new_name
|
||||
} else {
|
||||
dest_node.inner.name.clone()
|
||||
dest_node.inner.name.to_string()
|
||||
};
|
||||
source_node.parent_id = dest_node.inner.parent_id;
|
||||
source_node.parent_id = dest_node.inner.parent_id.into();
|
||||
|
||||
let etag = update_file_node(
|
||||
server,
|
||||
@@ -651,7 +655,7 @@ async fn overwrite_item(
|
||||
let to_document_id = destination.document_id.unwrap();
|
||||
|
||||
// dest_node is the current file at the destination
|
||||
let dest_node = server
|
||||
let dest_node_ = server
|
||||
.get_property::<Archive<AlignedBytes>>(
|
||||
to_account_id,
|
||||
Collection::FileNode,
|
||||
@@ -660,8 +664,10 @@ async fn overwrite_item(
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?
|
||||
.into_deserialized::<FileNode>()
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
||||
|
||||
let dest_node = dest_node_
|
||||
.to_unarchived::<FileNode>()
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// source_node is the file to be copied
|
||||
@@ -680,9 +686,9 @@ async fn overwrite_item(
|
||||
source_node.name = if let Some(new_name) = destination.new_name {
|
||||
new_name
|
||||
} else {
|
||||
dest_node.inner.name.clone()
|
||||
dest_node.inner.name.to_string()
|
||||
};
|
||||
source_node.parent_id = dest_node.inner.parent_id;
|
||||
source_node.parent_id = dest_node.inner.parent_id.into();
|
||||
|
||||
let etag = update_file_node(
|
||||
server,
|
||||
@@ -711,7 +717,7 @@ async fn move_item(
|
||||
let from_document_id = from_resource.resource.document_id;
|
||||
let parent_id = destination.document_id.map(|id| id + 1).unwrap_or(0);
|
||||
|
||||
let node = server
|
||||
let node_ = server
|
||||
.get_property::<Archive<AlignedBytes>>(
|
||||
from_account_id,
|
||||
Collection::FileNode,
|
||||
@@ -720,10 +726,11 @@ async fn move_item(
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?
|
||||
.into_deserialized::<FileNode>()
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
||||
let node = node_
|
||||
.to_unarchived::<FileNode>()
|
||||
.caused_by(trc::location!())?;
|
||||
let mut new_node = node.inner.clone();
|
||||
let mut new_node = node.deserialize().caused_by(trc::location!())?;
|
||||
new_node.parent_id = parent_id;
|
||||
if let Some(new_name) = destination.new_name {
|
||||
new_node.name = new_name;
|
||||
@@ -807,7 +814,7 @@ async fn rename_item(
|
||||
let from_account_id = from_resource.account_id;
|
||||
let from_document_id = from_resource.resource.document_id;
|
||||
|
||||
let node = server
|
||||
let node_ = server
|
||||
.get_property::<Archive<AlignedBytes>>(
|
||||
from_account_id,
|
||||
Collection::FileNode,
|
||||
@@ -816,10 +823,11 @@ async fn rename_item(
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?
|
||||
.into_deserialized::<FileNode>()
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
||||
let node = node_
|
||||
.to_unarchived::<FileNode>()
|
||||
.caused_by(trc::location!())?;
|
||||
let mut new_node = node.inner.clone();
|
||||
let mut new_node = node.deserialize().caused_by(trc::location!())?;
|
||||
if let Some(new_name) = destination.new_name {
|
||||
new_node.name = new_name;
|
||||
}
|
||||
|
||||
@@ -103,8 +103,6 @@ impl FileDeleteRequestHandler for Server {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let c = println!("DELETE files: {:?}", sorted_ids);
|
||||
|
||||
delete_files(self, access_token, account_id, sorted_ids).await?;
|
||||
|
||||
Ok(HttpResponse::new(StatusCode::NO_CONTENT))
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
use common::{FileItem, Files, Server, auth::AccessToken, storage::index::ObjectIndexBuilder};
|
||||
use groupware::file::FileNode;
|
||||
use groupware::file::{ArchivedFileNode, FileNode};
|
||||
use hyper::StatusCode;
|
||||
use jmap_proto::types::{collection::Collection, type_state::DataType};
|
||||
use store::write::{
|
||||
@@ -126,7 +126,7 @@ impl FromFileItem for FileItemId {
|
||||
pub(crate) async fn update_file_node(
|
||||
server: &Server,
|
||||
access_token: &AccessToken,
|
||||
node: Archive<FileNode>,
|
||||
node: Archive<&ArchivedFileNode>,
|
||||
mut new_node: FileNode,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
@@ -202,7 +202,7 @@ pub(crate) async fn insert_file_node(
|
||||
pub(crate) async fn delete_file_node(
|
||||
server: &Server,
|
||||
access_token: &AccessToken,
|
||||
node: Archive<FileNode>,
|
||||
node: Archive<&ArchivedFileNode>,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
) -> trc::Result<()> {
|
||||
|
||||
@@ -12,7 +12,8 @@ use dav_proto::schema::{
|
||||
},
|
||||
request::{DavPropertyValue, PropFind},
|
||||
response::{
|
||||
AclRestrictions, Href, MultiStatus, PropStat, Response, ResponseType, SupportedPrivilege,
|
||||
AclRestrictions, BaseCondition, Href, MultiStatus, PropStat, Response, ResponseType,
|
||||
SupportedPrivilege,
|
||||
},
|
||||
};
|
||||
use groupware::file::{FileNode, hierarchy::FileHierarchy};
|
||||
@@ -30,7 +31,7 @@ use trc::AddContext;
|
||||
use utils::map::bitmap::Bitmap;
|
||||
|
||||
use crate::{
|
||||
DavResource,
|
||||
DavError, DavErrorCondition, DavResource,
|
||||
common::{
|
||||
DavQuery, ETag,
|
||||
acl::{DavAclHandler, Privileges},
|
||||
@@ -146,6 +147,11 @@ impl HandleFilePropFindRequest for Server {
|
||||
return Ok(
|
||||
HttpResponse::new(StatusCode::MULTI_STATUS).with_xml_body(response.to_string())
|
||||
);
|
||||
} else if query.depth == usize::MAX && paths.len() > self.core.dav.max_match_results {
|
||||
return Err(DavError::Condition(DavErrorCondition::new(
|
||||
StatusCode::PRECONDITION_FAILED,
|
||||
BaseCondition::NumberOfMatchesWithinLimit,
|
||||
)));
|
||||
}
|
||||
|
||||
// Prepare response
|
||||
@@ -379,7 +385,11 @@ impl HandleFilePropFindRequest for Server {
|
||||
if node.file.is_none() {
|
||||
fields.push(DavPropertyValue::new(
|
||||
property.clone(),
|
||||
vec![ReportSet::SyncCollection],
|
||||
vec![
|
||||
ReportSet::SyncCollection,
|
||||
ReportSet::AclPrincipalPropSet,
|
||||
ReportSet::PrincipalMatch,
|
||||
],
|
||||
));
|
||||
} else if !is_all_prop {
|
||||
fields_not_found
|
||||
@@ -492,7 +502,9 @@ impl HandleFilePropFindRequest for Server {
|
||||
WebDavProperty::AclRestrictions => {
|
||||
fields.push(DavPropertyValue::new(
|
||||
property.clone(),
|
||||
AclRestrictions::default().with_no_invert(),
|
||||
AclRestrictions::default()
|
||||
.with_no_invert()
|
||||
.with_grant_only(),
|
||||
));
|
||||
}
|
||||
WebDavProperty::InheritedAclSet => {
|
||||
|
||||
@@ -69,6 +69,10 @@ impl FilePropPatchRequestHandler for Server {
|
||||
.caused_by(trc::location!())?;
|
||||
let resource = files.map_resource(&resource_)?;
|
||||
|
||||
if !request.has_changes() {
|
||||
return Ok(HttpResponse::new(StatusCode::NO_CONTENT));
|
||||
}
|
||||
|
||||
// Fetch node
|
||||
let node_ = self
|
||||
.get_property::<Archive<AlignedBytes>>(
|
||||
@@ -112,8 +116,7 @@ impl FilePropPatchRequestHandler for Server {
|
||||
.await?;
|
||||
|
||||
// Deserialize
|
||||
let node = node.to_deserialized().caused_by(trc::location!())?;
|
||||
let mut new_node = node.inner.clone();
|
||||
let mut new_node = node.deserialize().caused_by(trc::location!())?;
|
||||
|
||||
// Remove properties
|
||||
let mut items = Vec::with_capacity(request.remove.len() + request.set.len());
|
||||
@@ -133,7 +136,7 @@ impl FilePropPatchRequestHandler for Server {
|
||||
remove_file_properties(&mut new_node, request.remove, &mut items);
|
||||
}
|
||||
|
||||
let etag = if new_node != node.inner {
|
||||
let etag = if is_success {
|
||||
update_file_node(
|
||||
self,
|
||||
access_token,
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::{Server, auth::AccessToken};
|
||||
use dav_proto::{
|
||||
RequestHeaders, Return,
|
||||
schema::{
|
||||
property::{DavProperty, WebDavProperty},
|
||||
request::{PrincipalMatch, PropFind},
|
||||
response::MultiStatus,
|
||||
},
|
||||
};
|
||||
use http_proto::HttpResponse;
|
||||
use hyper::StatusCode;
|
||||
use jmap_proto::types::collection::Collection;
|
||||
use store::roaring::RoaringBitmap;
|
||||
|
||||
use crate::{
|
||||
DavError,
|
||||
common::{DavQuery, uri::DavUriResource},
|
||||
file::propfind::HandleFilePropFindRequest,
|
||||
};
|
||||
|
||||
use super::propfind::PrincipalPropFind;
|
||||
|
||||
pub(crate) trait PrincipalMatching: Sync + Send {
|
||||
fn handle_principal_match(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
request: PrincipalMatch,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
||||
impl PrincipalMatching for Server {
|
||||
async fn handle_principal_match(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
mut request: PrincipalMatch,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
let resource = self
|
||||
.validate_uri(access_token, headers.uri)
|
||||
.await
|
||||
.and_then(|uri| uri.into_owned_uri())?;
|
||||
|
||||
let todo = "implement cal, card";
|
||||
|
||||
match resource.collection {
|
||||
Collection::Calendar => todo!(),
|
||||
Collection::AddressBook => todo!(),
|
||||
Collection::FileNode => {
|
||||
self.handle_file_propfind_request(
|
||||
access_token,
|
||||
DavQuery {
|
||||
resource,
|
||||
base_uri: headers.uri,
|
||||
propfind: PropFind::Prop(request.properties),
|
||||
from_change_id: None,
|
||||
depth: usize::MAX,
|
||||
limit: None,
|
||||
ret: headers.ret,
|
||||
depth_no_root: headers.depth_no_root,
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
Collection::Principal => {
|
||||
let mut response = MultiStatus::new(Vec::with_capacity(16));
|
||||
if request.properties.is_empty() {
|
||||
request
|
||||
.properties
|
||||
.push(DavProperty::WebDav(WebDavProperty::DisplayName));
|
||||
}
|
||||
let request = PropFind::Prop(request.properties);
|
||||
self.prepare_principal_propfind_response(
|
||||
access_token,
|
||||
Collection::Principal,
|
||||
RoaringBitmap::from_iter(access_token.all_ids()).into_iter(),
|
||||
&request,
|
||||
&mut response,
|
||||
)
|
||||
.await?;
|
||||
Ok(HttpResponse::new(StatusCode::MULTI_STATUS).with_xml_body(response.to_string()))
|
||||
}
|
||||
_ => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,9 @@ use percent_encoding::NON_ALPHANUMERIC;
|
||||
|
||||
use crate::DavResource;
|
||||
|
||||
pub mod matching;
|
||||
pub mod propfind;
|
||||
pub mod propsearch;
|
||||
|
||||
pub trait CurrentUserPrincipal {
|
||||
fn current_user_principal(&self) -> Href;
|
||||
|
||||
@@ -141,11 +141,21 @@ impl PrincipalPropFind for Server {
|
||||
fields.push(DavPropertyValue::empty(property.clone()));
|
||||
}
|
||||
}
|
||||
WebDavProperty::SupportedReportSet if !is_principal => {
|
||||
fields.push(DavPropertyValue::new(
|
||||
property.clone(),
|
||||
vec![ReportSet::SyncCollection],
|
||||
));
|
||||
WebDavProperty::SupportedReportSet => {
|
||||
let reports = if !is_principal {
|
||||
vec![
|
||||
ReportSet::SyncCollection,
|
||||
ReportSet::AclPrincipalPropSet,
|
||||
ReportSet::PrincipalMatch,
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
ReportSet::PrincipalPropertySearch,
|
||||
ReportSet::PrincipalSearchPropertySet,
|
||||
ReportSet::PrincipalMatch,
|
||||
]
|
||||
};
|
||||
fields.push(DavPropertyValue::new(property.clone(), reports));
|
||||
}
|
||||
WebDavProperty::CurrentUserPrincipal => {
|
||||
fields.push(DavPropertyValue::new(
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::{
|
||||
Server,
|
||||
auth::{AccessToken, AsTenantId},
|
||||
};
|
||||
use dav_proto::schema::{
|
||||
property::{DavProperty, WebDavProperty},
|
||||
request::{PrincipalPropertySearch, PropFind},
|
||||
response::MultiStatus,
|
||||
};
|
||||
use directory::{
|
||||
Type,
|
||||
backend::internal::{PrincipalField, manage::ManageDirectory},
|
||||
};
|
||||
use http_proto::HttpResponse;
|
||||
use hyper::StatusCode;
|
||||
use jmap_proto::types::collection::Collection;
|
||||
use store::roaring::RoaringBitmap;
|
||||
use trc::AddContext;
|
||||
|
||||
use super::propfind::PrincipalPropFind;
|
||||
|
||||
pub(crate) trait PrincipalPropSearch: Sync + Send {
|
||||
fn handle_principal_property_search(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
request: PrincipalPropertySearch,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
||||
impl PrincipalPropSearch for Server {
|
||||
async fn handle_principal_property_search(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
mut request: PrincipalPropertySearch,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
let mut search_for = None;
|
||||
|
||||
for prop_search in request.property_search {
|
||||
if matches!(
|
||||
prop_search.property,
|
||||
DavProperty::WebDav(WebDavProperty::DisplayName)
|
||||
) && !prop_search.match_.is_empty()
|
||||
{
|
||||
search_for = Some(prop_search.match_);
|
||||
}
|
||||
}
|
||||
|
||||
let mut response = MultiStatus::new(Vec::with_capacity(16));
|
||||
if let Some(search_for) = search_for {
|
||||
// Return all principals
|
||||
let principals = self
|
||||
.store()
|
||||
.list_principals(
|
||||
search_for.as_str().into(),
|
||||
access_token.tenant_id(),
|
||||
&[Type::Individual, Type::Group],
|
||||
&[PrincipalField::Name],
|
||||
0,
|
||||
0,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
let ids = RoaringBitmap::from_iter(principals.items.into_iter().map(|p| p.id()));
|
||||
|
||||
if !ids.is_empty() {
|
||||
if request.properties.is_empty() {
|
||||
request
|
||||
.properties
|
||||
.push(DavProperty::WebDav(WebDavProperty::DisplayName));
|
||||
}
|
||||
let request = PropFind::Prop(request.properties);
|
||||
self.prepare_principal_propfind_response(
|
||||
access_token,
|
||||
Collection::Principal,
|
||||
ids.into_iter(),
|
||||
&request,
|
||||
&mut response,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(HttpResponse::new(StatusCode::MULTI_STATUS).with_xml_body(response.to_string()))
|
||||
}
|
||||
}
|
||||
+50
-20
@@ -12,8 +12,11 @@ use dav_proto::{
|
||||
parser::{DavParser, tokenizer::Tokenizer},
|
||||
schema::{
|
||||
Namespace,
|
||||
property::WebDavProperty,
|
||||
request::{Acl, LockInfo, MkCol, PropFind, PropertyUpdate, Report},
|
||||
response::{BaseCondition, ErrorResponse},
|
||||
response::{
|
||||
BaseCondition, ErrorResponse, PrincipalSearchProperty, PrincipalSearchPropertySet,
|
||||
},
|
||||
},
|
||||
};
|
||||
use directory::Permission;
|
||||
@@ -24,6 +27,7 @@ use crate::{
|
||||
DavError, DavMethod, DavResource,
|
||||
common::{
|
||||
DavQuery,
|
||||
acl::DavAclHandler,
|
||||
lock::{LockRequest, LockRequestHandler},
|
||||
propfind::PropFindRequestHandler,
|
||||
uri::DavUriResource,
|
||||
@@ -34,6 +38,7 @@ use crate::{
|
||||
mkcol::FileMkColRequestHandler, propfind::HandleFilePropFindRequest,
|
||||
proppatch::FilePropPatchRequestHandler, update::FileUpdateRequestHandler,
|
||||
},
|
||||
principal::{matching::PrincipalMatching, propsearch::PrincipalPropSearch},
|
||||
};
|
||||
|
||||
pub trait DavRequestHandler: Sync + Send {
|
||||
@@ -86,7 +91,6 @@ impl DavRequestDispatcher for Server {
|
||||
DavMethod::PROPPATCH => match resource {
|
||||
DavResource::Card => todo!(),
|
||||
DavResource::Cal => todo!(),
|
||||
DavResource::Principal => todo!(),
|
||||
DavResource::File => {
|
||||
self.handle_file_proppatch_request(
|
||||
&access_token,
|
||||
@@ -95,11 +99,11 @@ impl DavRequestDispatcher for Server {
|
||||
)
|
||||
.await
|
||||
}
|
||||
DavResource::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
},
|
||||
DavMethod::MKCOL => match resource {
|
||||
DavResource::Card => todo!(),
|
||||
DavResource::Cal => todo!(),
|
||||
DavResource::Principal => todo!(),
|
||||
DavResource::File => {
|
||||
self.handle_file_mkcol_request(
|
||||
&access_token,
|
||||
@@ -112,20 +116,20 @@ impl DavRequestDispatcher for Server {
|
||||
)
|
||||
.await
|
||||
}
|
||||
DavResource::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
},
|
||||
DavMethod::GET => match resource {
|
||||
DavResource::Card => todo!(),
|
||||
DavResource::Cal => todo!(),
|
||||
DavResource::Principal => todo!(),
|
||||
DavResource::File => {
|
||||
self.handle_file_get_request(&access_token, headers, false)
|
||||
.await
|
||||
}
|
||||
DavResource::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
},
|
||||
DavMethod::HEAD => match resource {
|
||||
DavResource::Card => todo!(),
|
||||
DavResource::Cal => todo!(),
|
||||
DavResource::Principal => todo!(),
|
||||
DavResource::File => {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
@@ -144,6 +148,7 @@ impl DavRequestDispatcher for Server {
|
||||
.await
|
||||
}
|
||||
}
|
||||
DavResource::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
},
|
||||
DavMethod::DELETE => {
|
||||
// Include any fragments in the URI
|
||||
@@ -155,53 +160,52 @@ impl DavRequestDispatcher for Server {
|
||||
match resource {
|
||||
DavResource::Card => todo!(),
|
||||
DavResource::Cal => todo!(),
|
||||
DavResource::Principal => todo!(),
|
||||
DavResource::File => {
|
||||
self.handle_file_delete_request(&access_token, headers)
|
||||
.await
|
||||
}
|
||||
DavResource::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
}
|
||||
}
|
||||
DavMethod::PUT | DavMethod::POST => match resource {
|
||||
DavResource::Card => todo!(),
|
||||
DavResource::Cal => todo!(),
|
||||
DavResource::Principal => todo!(),
|
||||
DavResource::File => {
|
||||
self.handle_file_update_request(&access_token, headers, body, false)
|
||||
.await
|
||||
}
|
||||
DavResource::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
},
|
||||
DavMethod::PATCH => match resource {
|
||||
DavResource::Card => todo!(),
|
||||
DavResource::Cal => todo!(),
|
||||
DavResource::Principal => todo!(),
|
||||
DavResource::File => {
|
||||
self.handle_file_update_request(&access_token, headers, body, true)
|
||||
.await
|
||||
}
|
||||
DavResource::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
},
|
||||
DavMethod::COPY => match resource {
|
||||
DavResource::Card => todo!(),
|
||||
DavResource::Cal => todo!(),
|
||||
DavResource::Principal => todo!(),
|
||||
DavResource::File => {
|
||||
self.handle_file_copy_move_request(&access_token, headers, false)
|
||||
.await
|
||||
}
|
||||
DavResource::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
},
|
||||
DavMethod::MOVE => match resource {
|
||||
DavResource::Card => todo!(),
|
||||
DavResource::Cal => todo!(),
|
||||
DavResource::Principal => todo!(),
|
||||
DavResource::File => {
|
||||
self.handle_file_copy_move_request(&access_token, headers, true)
|
||||
.await
|
||||
}
|
||||
DavResource::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
},
|
||||
DavMethod::LOCK => match resource {
|
||||
DavResource::Card => todo!(),
|
||||
DavResource::Cal => todo!(),
|
||||
DavResource::Principal => todo!(),
|
||||
DavResource::File => {
|
||||
self.handle_lock_request(
|
||||
&access_token,
|
||||
@@ -214,6 +218,7 @@ impl DavRequestDispatcher for Server {
|
||||
)
|
||||
.await
|
||||
}
|
||||
DavResource::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
},
|
||||
DavMethod::UNLOCK => {
|
||||
self.handle_lock_request(&access_token, headers, LockRequest::Unlock)
|
||||
@@ -253,15 +258,40 @@ impl DavRequestDispatcher for Server {
|
||||
}
|
||||
}
|
||||
}
|
||||
Report::Addressbook(addressbook_query) => todo!(),
|
||||
Report::AddressbookMultiGet(multi_get) => todo!(),
|
||||
Report::CalendarQuery(calendar_query) => todo!(),
|
||||
Report::CalendarMultiGet(multi_get) => todo!(),
|
||||
Report::FreeBusyQuery(free_busy_query) => todo!(),
|
||||
Report::AclPrincipalPropSet(acl_principal_prop_set) => todo!(),
|
||||
Report::PrincipalMatch(principal_match) => todo!(),
|
||||
Report::PrincipalPropertySearch(principal_property_search) => todo!(),
|
||||
Report::PrincipalSearchPropertySet => todo!(),
|
||||
Report::AclPrincipalPropSet(report) => {
|
||||
self.handle_acl_prop_set(&access_token, headers, report)
|
||||
.await
|
||||
}
|
||||
Report::PrincipalMatch(report) => {
|
||||
self.handle_principal_match(&access_token, headers, report)
|
||||
.await
|
||||
}
|
||||
Report::PrincipalPropertySearch(report) => {
|
||||
if resource == DavResource::Principal {
|
||||
self.handle_principal_property_search(&access_token, report)
|
||||
.await
|
||||
} else {
|
||||
Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED))
|
||||
}
|
||||
}
|
||||
Report::PrincipalSearchPropertySet => {
|
||||
if resource == DavResource::Principal {
|
||||
Ok(HttpResponse::new(StatusCode::OK).with_xml_body(
|
||||
PrincipalSearchPropertySet::new(vec![PrincipalSearchProperty::new(
|
||||
WebDavProperty::DisplayName,
|
||||
"Account or Group name",
|
||||
)])
|
||||
.to_string(),
|
||||
))
|
||||
} else {
|
||||
Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED))
|
||||
}
|
||||
}
|
||||
Report::Addressbook(report) => todo!(),
|
||||
Report::AddressbookMultiGet(report) => todo!(),
|
||||
Report::CalendarQuery(report) => todo!(),
|
||||
Report::CalendarMultiGet(report) => todo!(),
|
||||
Report::FreeBusyQuery(report) => todo!(),
|
||||
},
|
||||
DavMethod::OPTIONS => unreachable!(),
|
||||
}
|
||||
|
||||
@@ -6,13 +6,20 @@
|
||||
|
||||
use calcard::icalendar::ICalendar;
|
||||
use jmap_proto::types::{acl::Acl, value::AclGrant};
|
||||
use store::{SERIALIZE_OBJ_14_V1, SerializedVersion};
|
||||
use utils::map::vec_map::VecMap;
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct Calendar {
|
||||
pub preferences: VecMap<u32, CalendarPreferences>,
|
||||
pub acls: Vec<AclGrant>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct CalendarPreferences {
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
@@ -21,10 +28,10 @@ pub struct CalendarPreferences {
|
||||
pub is_subscribed: bool,
|
||||
pub is_default: bool,
|
||||
pub is_visible: bool,
|
||||
pub include_in_availability: IncludeInAvailability,
|
||||
/*pub include_in_availability: IncludeInAvailability,
|
||||
pub default_alerts_with_time: VecMap<String, ICalendar>,
|
||||
pub default_alerts_without_time: VecMap<String, ICalendar>,
|
||||
pub time_zone: Timezone,
|
||||
pub time_zone: Timezone,*/
|
||||
}
|
||||
|
||||
pub struct CalendarEvent {
|
||||
@@ -95,3 +102,9 @@ impl From<CalendarRight> for Acl {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializedVersion for Calendar {
|
||||
fn serialize_version() -> u8 {
|
||||
SERIALIZE_OBJ_14_V1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,12 @@
|
||||
|
||||
use calcard::vcard::VCard;
|
||||
use jmap_proto::types::{acl::Acl, value::AclGrant};
|
||||
use store::{SERIALIZE_OBJ_15_V1, SerializedVersion};
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
#[rkyv(derive(Debug))]
|
||||
pub struct AddressBook {
|
||||
pub name: String,
|
||||
pub display_name: Option<String>,
|
||||
@@ -57,3 +62,9 @@ impl From<AddressBookRight> for Acl {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializedVersion for AddressBook {
|
||||
fn serialize_version() -> u8 {
|
||||
SERIALIZE_OBJ_15_V1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,30 +34,12 @@ impl FileHierarchy for Server {
|
||||
.get(&account_id)
|
||||
.filter(|x| x.modseq == change_id)
|
||||
{
|
||||
let c = println!(
|
||||
"Hierarchy: {:?}",
|
||||
files
|
||||
.files
|
||||
.iter()
|
||||
.map(|f| f.name.clone())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
Ok(files)
|
||||
} else {
|
||||
let mut files = build_file_hierarchy(self, account_id).await?;
|
||||
files.modseq = change_id;
|
||||
let files = Arc::new(files);
|
||||
self.inner.cache.files.insert(account_id, files.clone());
|
||||
|
||||
let c = println!(
|
||||
"Hierarchy: {:?}",
|
||||
files
|
||||
.files
|
||||
.iter()
|
||||
.map(|f| f.name.clone())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
}
|
||||
@@ -68,9 +50,6 @@ async fn build_file_hierarchy(server: &Server, account_id: u32) -> trc::Result<F
|
||||
.fetch_folders::<FileNode>(account_id, Collection::FileNode)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
/*.format(|f| {
|
||||
f.name = percent_encoding::utf8_percent_encode(&f.name, NON_ALPHANUMERIC).to_string();
|
||||
});*/
|
||||
let mut files = Files {
|
||||
files: IdBimap::with_capacity(list.len()),
|
||||
size: std::mem::size_of::<Files>() as u64,
|
||||
|
||||
@@ -84,7 +84,7 @@ pub const SERIALIZE_OBJ_12_V1: u8 = 11;
|
||||
pub const SERIALIZE_OBJ_13_V1: u8 = 12;
|
||||
pub const SERIALIZE_OBJ_14_V1: u8 = 13;
|
||||
pub const SERIALIZE_OBJ_15_V1: u8 = 14;
|
||||
pub const SERIALIZE_OBJ_16_V1: u8 = 15;
|
||||
//pub const SERIALIZE_OBJ_16_V1: u8 = 15;
|
||||
|
||||
pub trait SerializedVersion {
|
||||
fn serialize_version() -> u8;
|
||||
|
||||
Reference in New Issue
Block a user