mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-10-26 12:26:26 +08:00
WebDAV permissions and logging (closes #1362)
This commit is contained in:
parent
fe7d646966
commit
095c501a66
50 changed files with 1031 additions and 273 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1759,6 +1759,7 @@ version = "0.11.7"
|
|||
dependencies = [
|
||||
"calcard",
|
||||
"chrono",
|
||||
"compact_str",
|
||||
"hashify",
|
||||
"hyper 1.6.0",
|
||||
"mail-parser",
|
||||
|
|
@ -1766,6 +1767,7 @@ dependencies = [
|
|||
"rkyv 0.8.10",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"trc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -462,9 +462,9 @@ impl AccessToken {
|
|||
self.permissions.get(permission.id())
|
||||
}
|
||||
|
||||
pub fn assert_has_permission(&self, permission: Permission) -> trc::Result<()> {
|
||||
pub fn assert_has_permission(&self, permission: Permission) -> trc::Result<bool> {
|
||||
if self.has_permission(permission) {
|
||||
Ok(())
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(trc::SecurityEvent::Unauthorized
|
||||
.into_err()
|
||||
|
|
@ -518,6 +518,10 @@ impl AccessToken {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn has_account_access(&self, to_account_id: u32) -> bool {
|
||||
self.is_member(to_account_id) || self.access_to.iter().any(|(id, _)| *id == to_account_id)
|
||||
}
|
||||
|
||||
pub fn assert_has_access(
|
||||
&self,
|
||||
to_account_id: Id,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ version = "0.11.7"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
trc = { path = "../trc" }
|
||||
hashify = "0.2.6"
|
||||
quick-xml = "0.37.2"
|
||||
calcard = { path = "/Users/me/code/calcard", features = ["rkyv"] }
|
||||
|
|
@ -11,6 +12,7 @@ mail-parser = "0.10.2"
|
|||
hyper = "1.6.0"
|
||||
rkyv = { version = "0.8.10", features = ["little_endian"] }
|
||||
chrono = { version = "0.4.40", features = ["serde"], optional = true }
|
||||
compact_str = "0.9.0"
|
||||
|
||||
[dev-dependencies]
|
||||
calcard = { path = "/Users/me/code/calcard", features = ["serde", "rkyv"] }
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use compact_str::{CompactString, ToCompactString};
|
||||
use trc::Value;
|
||||
|
||||
pub mod parser;
|
||||
pub mod requests;
|
||||
pub mod responses;
|
||||
|
|
@ -50,7 +53,7 @@ pub struct ResourceState<T: AsRef<str>> {
|
|||
pub state_token: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum Return {
|
||||
Minimal,
|
||||
Representation,
|
||||
|
|
@ -91,16 +94,87 @@ pub enum Depth {
|
|||
None,
|
||||
}
|
||||
|
||||
impl From<&RequestHeaders<'_>> for Value {
|
||||
fn from(headers: &RequestHeaders<'_>) -> Self {
|
||||
let mut values = Vec::with_capacity(4);
|
||||
if headers.depth != Depth::None {
|
||||
values.push(Value::String(CompactString::const_new("Depth")));
|
||||
values.push(match headers.depth {
|
||||
Depth::Zero => Value::Int(0),
|
||||
Depth::One => Value::Int(1),
|
||||
Depth::Infinity => Value::String(CompactString::const_new("infinity")),
|
||||
Depth::None => Value::None,
|
||||
});
|
||||
}
|
||||
if headers.timeout != Timeout::None {
|
||||
values.push(Value::String(CompactString::const_new("Timeout")));
|
||||
values.push(match headers.timeout {
|
||||
Timeout::Infinite => Value::String(CompactString::const_new("infinite")),
|
||||
Timeout::Second(n) => Value::Int(n as i64),
|
||||
Timeout::None => Value::None,
|
||||
});
|
||||
}
|
||||
for (name, header_value) in [
|
||||
("Content-Type", headers.content_type),
|
||||
("Destination", headers.destination),
|
||||
("Lock-Token", headers.lock_token),
|
||||
] {
|
||||
if let Some(value) = header_value {
|
||||
values.push(CompactString::const_new(name).into());
|
||||
values.push(value.to_compact_string().into());
|
||||
}
|
||||
}
|
||||
for (name, is_set) in [
|
||||
("Overwrite", headers.overwrite_fail),
|
||||
("No-Timezones", headers.no_timezones),
|
||||
("Depth-No-Root", headers.depth_no_root),
|
||||
] {
|
||||
if is_set {
|
||||
values.push(CompactString::const_new(name).into());
|
||||
}
|
||||
}
|
||||
for if_ in &headers.if_ {
|
||||
values.push(CompactString::const_new("If").into());
|
||||
let mut if_values = Vec::with_capacity(if_.list.len() * 2 + 1);
|
||||
if let Some(resource) = if_.resource {
|
||||
if_values.push(Value::String(resource.to_compact_string()));
|
||||
}
|
||||
for condition in &if_.list {
|
||||
match condition {
|
||||
Condition::StateToken { is_not, token } => {
|
||||
if *is_not {
|
||||
if_values.push(Value::String(CompactString::const_new("!State-Token")));
|
||||
} else {
|
||||
if_values.push(Value::String(CompactString::const_new("State-Token")));
|
||||
}
|
||||
if_values.push(Value::String(token.to_compact_string()));
|
||||
}
|
||||
Condition::ETag { is_not, tag } => {
|
||||
if *is_not {
|
||||
if_values.push(Value::String(CompactString::const_new("!ETag")));
|
||||
} else {
|
||||
if_values.push(Value::String(CompactString::const_new("ETag")));
|
||||
}
|
||||
if_values.push(Value::String(tag.to_compact_string()));
|
||||
}
|
||||
Condition::Exists { is_not } => {
|
||||
if *is_not {
|
||||
if_values.push(Value::String(CompactString::const_new("!Exists")));
|
||||
} else {
|
||||
if_values.push(Value::String(CompactString::const_new("Exists")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
values.push(Value::Array(if_values));
|
||||
}
|
||||
|
||||
Value::Array(values)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Allow: OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, COPY, MOVE
|
||||
Allow: MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, REPORT, ACL
|
||||
DAV: 1, 2, 3, access-control, extended-mkcol
|
||||
calendar-no-timezone
|
||||
|
||||
|
||||
TODO:
|
||||
|
||||
|
||||
Implemented:
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::{Display, Formatter},
|
||||
};
|
||||
|
||||
use quick_xml::events::BytesStart;
|
||||
use tokenizer::Tokenizer;
|
||||
|
|
@ -152,3 +155,18 @@ impl Default for RawElement<'_> {
|
|||
RawElement(BytesStart::new(""))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Error::Xml(err) => write!(f, "XML error: {}", err),
|
||||
Error::UnexpectedToken { expected, found } => {
|
||||
write!(f, "Unexpected token: {found:?}")?;
|
||||
if let Some(expected) = expected {
|
||||
write!(f, ", expected: {expected:?}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -288,6 +288,85 @@ impl MultiStatus {
|
|||
}
|
||||
}
|
||||
|
||||
impl BaseCondition {
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
BaseCondition::NoConflictingLock(_) => "NoConflictingLock",
|
||||
BaseCondition::CannotModifyProtectedProperty => "CannotModifyProtectedProperty",
|
||||
BaseCondition::LockTokenSubmitted(_) => "LockTokenSubmitted",
|
||||
BaseCondition::LockTokenMatchesRequestUri => "LockTokenMatchesRequestUri",
|
||||
BaseCondition::NoExternalEntities => "NoExternalEntities",
|
||||
BaseCondition::PreservedLiveProperties => "PreservedLiveProperties",
|
||||
BaseCondition::PropFindFiniteDepth => "PropFindFiniteDepth",
|
||||
BaseCondition::ResourceMustBeNull => "ResourceMustBeNull",
|
||||
BaseCondition::NeedPrivileges(_) => "NeedPrivileges",
|
||||
BaseCondition::NoAceConflict => "NoAceConflict",
|
||||
BaseCondition::NoProtectedAceConflict => "NoProtectedAceConflict",
|
||||
BaseCondition::NoInheritedAceConflict => "NoInheritedAceConflict",
|
||||
BaseCondition::LimitedNumberOfAces => "LimitedNumberOfAces",
|
||||
BaseCondition::DenyBeforeGrant => "DenyBeforeGrant",
|
||||
BaseCondition::GrantOnly => "GrantOnly",
|
||||
BaseCondition::NoInvert => "NoInvert",
|
||||
BaseCondition::NoAbstract => "NoAbstract",
|
||||
BaseCondition::NotSupportedPrivilege => "NotSupportedPrivilege",
|
||||
BaseCondition::MissingRequiredPrincipal => "MissingRequiredPrincipal",
|
||||
BaseCondition::RecognizedPrincipal => "RecognizedPrincipal",
|
||||
BaseCondition::AllowedPrincipal => "AllowedPrincipal",
|
||||
BaseCondition::NumberOfMatchesWithinLimit => "NumberOfMatchesWithinLimit",
|
||||
BaseCondition::QuotaNotExceeded => "QuotaNotExceeded",
|
||||
BaseCondition::ValidResourceType => "ValidResourceType",
|
||||
BaseCondition::ValidSyncToken => "ValidSyncToken",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CalCondition {
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
CalCondition::CalendarCollectionLocationOk => "CalendarCollectionLocationOk",
|
||||
CalCondition::ValidCalendarData => "ValidCalendarData",
|
||||
CalCondition::ValidFilter => "ValidFilter",
|
||||
CalCondition::ValidCalendarObjectResource => "ValidCalendarObjectResource",
|
||||
CalCondition::ValidTimezone => "ValidTimezone",
|
||||
CalCondition::NoUidConflict(_) => "NoUidConflict",
|
||||
CalCondition::InitializeCalendarCollection => "InitializeCalendarCollection",
|
||||
CalCondition::SupportedCalendarData => "SupportedCalendarData",
|
||||
CalCondition::SupportedFilter(_) => "SupportedFilter",
|
||||
CalCondition::SupportedCollation(_) => "SupportedCollation",
|
||||
CalCondition::MinDateTime => "MinDateTime",
|
||||
CalCondition::MaxDateTime => "MaxDateTime",
|
||||
CalCondition::MaxResourceSize(_) => "MaxResourceSize",
|
||||
CalCondition::MaxInstances => "MaxInstances",
|
||||
CalCondition::MaxAttendeesPerInstance => "MaxAttendeesPerInstance",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CardCondition {
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
CardCondition::SupportedAddressData => "SupportedAddressData",
|
||||
CardCondition::SupportedAddressDataConversion => "SupportedAddressDataConversion",
|
||||
CardCondition::SupportedFilter(_) => "SupportedFilter",
|
||||
CardCondition::SupportedCollation(_) => "SupportedCollation",
|
||||
CardCondition::ValidAddressData => "ValidAddressData",
|
||||
CardCondition::NoUidConflict(_) => "NoUidConflict",
|
||||
CardCondition::MaxResourceSize(_) => "MaxResourceSize",
|
||||
CardCondition::AddressBookCollectionLocationOk => "AddressBookCollectionLocationOk",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Condition {
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
Condition::Base(base) => base.display_name(),
|
||||
Condition::Cal(cal) => cal.display_name(),
|
||||
Condition::Card(card) => card.display_name(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod serde_impl {
|
||||
use super::Status;
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ pub(crate) trait CalendarCopyMoveRequestHandler: Sync + Send {
|
|||
fn handle_calendar_copy_move_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
is_move: bool,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ impl CalendarCopyMoveRequestHandler for Server {
|
|||
async fn handle_calendar_copy_move_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
is_move: bool,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate source
|
||||
|
|
@ -110,7 +110,7 @@ impl CalendarCopyMoveRequestHandler for Server {
|
|||
let to_resource = to_resources.by_path(destination_resource_name);
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![
|
||||
ResourceState {
|
||||
account_id: from_account_id,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,14 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
DavError, DavMethod,
|
||||
common::{
|
||||
ETag,
|
||||
lock::{LockRequestHandler, ResourceState},
|
||||
uri::DavUriResource,
|
||||
},
|
||||
};
|
||||
use common::{Server, auth::AccessToken, sharing::EffectiveAcl};
|
||||
use dav_proto::RequestHeaders;
|
||||
use groupware::{
|
||||
|
|
@ -20,20 +28,11 @@ use jmap_proto::types::{
|
|||
use store::write::BatchBuilder;
|
||||
use trc::AddContext;
|
||||
|
||||
use crate::{
|
||||
DavError, DavMethod,
|
||||
common::{
|
||||
ETag,
|
||||
lock::{LockRequestHandler, ResourceState},
|
||||
uri::DavUriResource,
|
||||
},
|
||||
};
|
||||
|
||||
pub(crate) trait CalendarDeleteRequestHandler: Sync + Send {
|
||||
fn handle_calendar_delete_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +40,7 @@ impl CalendarDeleteRequestHandler for Server {
|
|||
async fn handle_calendar_delete_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
let resource = self
|
||||
|
|
@ -91,7 +90,7 @@ impl CalendarDeleteRequestHandler for Server {
|
|||
// Validate headers
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: Collection::Calendar,
|
||||
|
|
@ -139,7 +138,7 @@ impl CalendarDeleteRequestHandler for Server {
|
|||
// Validate headers
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: Collection::CalendarEvent,
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ pub(crate) trait CalendarFreebusyRequestHandler: Sync + Send {
|
|||
fn handle_calendar_freebusy_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: FreeBusyQuery,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ impl CalendarFreebusyRequestHandler for Server {
|
|||
async fn handle_calendar_freebusy_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: FreeBusyQuery,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ pub(crate) trait CalendarGetRequestHandler: Sync + Send {
|
|||
fn handle_calendar_get_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
is_head: bool,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ impl CalendarGetRequestHandler for Server {
|
|||
async fn handle_calendar_get_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
is_head: bool,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
|
|
@ -90,7 +90,7 @@ impl CalendarGetRequestHandler for Server {
|
|||
let etag = event_.etag();
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: Collection::CalendarEvent,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ use dav_proto::{
|
|||
RequestHeaders, Return,
|
||||
schema::{Namespace, request::MkCol, response::MkColResponse},
|
||||
};
|
||||
use groupware::{cache::GroupwareCache, calendar::{Calendar, CalendarPreferences}};
|
||||
use groupware::{
|
||||
cache::GroupwareCache,
|
||||
calendar::{Calendar, CalendarPreferences},
|
||||
};
|
||||
use http_proto::HttpResponse;
|
||||
use hyper::StatusCode;
|
||||
use jmap_proto::types::collection::{Collection, SyncCollection};
|
||||
|
|
@ -31,7 +34,7 @@ pub(crate) trait CalendarMkColRequestHandler: Sync + Send {
|
|||
fn handle_calendar_mkcol_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: Option<MkCol>,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
|
@ -40,7 +43,7 @@ impl CalendarMkColRequestHandler for Server {
|
|||
async fn handle_calendar_mkcol_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: Option<MkCol>,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
|
|
@ -59,7 +62,6 @@ impl CalendarMkColRequestHandler for Server {
|
|||
.fetch_dav_resources(access_token, account_id, SyncCollection::Calendar)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
|
||||
.by_path(name)
|
||||
.is_some()
|
||||
{
|
||||
|
|
@ -69,7 +71,7 @@ impl CalendarMkColRequestHandler for Server {
|
|||
// Validate headers
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: resource.collection,
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ pub(crate) trait CalendarPropPatchRequestHandler: Sync + Send {
|
|||
fn handle_calendar_proppatch_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: PropertyUpdate,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ impl CalendarPropPatchRequestHandler for Server {
|
|||
async fn handle_calendar_proppatch_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
mut request: PropertyUpdate,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
|
|
@ -120,7 +120,7 @@ impl CalendarPropPatchRequestHandler for Server {
|
|||
// Validate headers
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection,
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ pub(crate) trait CalendarQueryRequestHandler: Sync + Send {
|
|||
fn handle_calendar_query_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: CalendarQuery,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
|
@ -52,7 +52,7 @@ impl CalendarQueryRequestHandler for Server {
|
|||
async fn handle_calendar_query_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: CalendarQuery,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
|
|
@ -232,7 +232,11 @@ impl CalendarQueryHandler {
|
|||
.data
|
||||
.expand(default_tz, max_time_range)
|
||||
.unwrap_or_else(|| {
|
||||
let todo = "log error";
|
||||
trc::event!(
|
||||
Calendar(trc::CalendarEvent::RuleExpansionError),
|
||||
Reason = "chrono error",
|
||||
Details = event.data.event.to_string(),
|
||||
);
|
||||
vec![]
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ pub(crate) trait CalendarUpdateRequestHandler: Sync + Send {
|
|||
fn handle_calendar_update_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
bytes: Vec<u8>,
|
||||
is_patch: bool,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
|
|
@ -55,7 +55,7 @@ impl CalendarUpdateRequestHandler for Server {
|
|||
async fn handle_calendar_update_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
bytes: Vec<u8>,
|
||||
_is_patch: bool,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
|
|
@ -124,7 +124,7 @@ impl CalendarUpdateRequestHandler for Server {
|
|||
match self
|
||||
.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: Collection::CalendarEvent,
|
||||
|
|
@ -209,7 +209,7 @@ impl CalendarUpdateRequestHandler for Server {
|
|||
// Validate headers
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: resource.collection,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ pub(crate) trait CardCopyMoveRequestHandler: Sync + Send {
|
|||
fn handle_card_copy_move_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
is_move: bool,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ impl CardCopyMoveRequestHandler for Server {
|
|||
async fn handle_card_copy_move_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
is_move: bool,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate source
|
||||
|
|
@ -110,7 +110,7 @@ impl CardCopyMoveRequestHandler for Server {
|
|||
let to_resource = to_resources.by_path(destination_resource_name);
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![
|
||||
ResourceState {
|
||||
account_id: from_account_id,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ pub(crate) trait CardDeleteRequestHandler: Sync + Send {
|
|||
fn handle_card_delete_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ impl CardDeleteRequestHandler for Server {
|
|||
async fn handle_card_delete_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
let resource = self
|
||||
|
|
@ -91,7 +91,7 @@ impl CardDeleteRequestHandler for Server {
|
|||
// Validate headers
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: Collection::AddressBook,
|
||||
|
|
@ -143,7 +143,7 @@ impl CardDeleteRequestHandler for Server {
|
|||
// Validate headers
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: Collection::ContactCard,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ pub(crate) trait CardGetRequestHandler: Sync + Send {
|
|||
fn handle_card_get_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
is_head: bool,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ impl CardGetRequestHandler for Server {
|
|||
async fn handle_card_get_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
is_head: bool,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
|
|
@ -86,7 +86,7 @@ impl CardGetRequestHandler for Server {
|
|||
let etag = card_.etag();
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: Collection::ContactCard,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ pub(crate) trait CardMkColRequestHandler: Sync + Send {
|
|||
fn handle_card_mkcol_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: Option<MkCol>,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ impl CardMkColRequestHandler for Server {
|
|||
async fn handle_card_mkcol_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: Option<MkCol>,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
|
|
@ -57,7 +57,6 @@ impl CardMkColRequestHandler for Server {
|
|||
.fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
|
||||
.by_path(name)
|
||||
.is_some()
|
||||
{
|
||||
|
|
@ -67,7 +66,7 @@ impl CardMkColRequestHandler for Server {
|
|||
// Validate headers
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: resource.collection,
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ pub(crate) trait CardPropPatchRequestHandler: Sync + Send {
|
|||
fn handle_card_proppatch_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: PropertyUpdate,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ impl CardPropPatchRequestHandler for Server {
|
|||
async fn handle_card_proppatch_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
mut request: PropertyUpdate,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
|
|
@ -117,7 +117,7 @@ impl CardPropPatchRequestHandler for Server {
|
|||
// Validate headers
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection,
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ pub(crate) trait CardQueryRequestHandler: Sync + Send {
|
|||
fn handle_card_query_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: AddressbookQuery,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ impl CardQueryRequestHandler for Server {
|
|||
async fn handle_card_query_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: AddressbookQuery,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ pub(crate) trait CardUpdateRequestHandler: Sync + Send {
|
|||
fn handle_card_update_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
bytes: Vec<u8>,
|
||||
is_patch: bool,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
|
|
@ -46,7 +46,7 @@ impl CardUpdateRequestHandler for Server {
|
|||
async fn handle_card_update_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
bytes: Vec<u8>,
|
||||
_is_patch: bool,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
|
|
@ -115,7 +115,7 @@ impl CardUpdateRequestHandler for Server {
|
|||
match self
|
||||
.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: Collection::ContactCard,
|
||||
|
|
@ -203,7 +203,7 @@ impl CardUpdateRequestHandler for Server {
|
|||
// Validate headers
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: resource.collection,
|
||||
|
|
|
|||
|
|
@ -38,14 +38,14 @@ pub(crate) trait DavAclHandler: Sync + Send {
|
|||
fn handle_acl_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: dav_proto::schema::request::Acl,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
|
||||
fn handle_acl_prop_set(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: AclPrincipalPropSet,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ impl DavAclHandler for Server {
|
|||
async fn handle_acl_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: dav_proto::schema::request::Acl,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
|
|
@ -193,7 +193,7 @@ impl DavAclHandler for Server {
|
|||
async fn handle_acl_prop_set(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
mut request: AclPrincipalPropSet,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
let uri = self
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ pub(crate) trait LockRequestHandler: Sync + Send {
|
|||
fn handle_lock_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
lock_info: LockRequest,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ impl LockRequestHandler for Server {
|
|||
async fn handle_lock_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
lock_info: LockRequest,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
let resource = self
|
||||
|
|
@ -150,7 +150,7 @@ impl LockRequestHandler for Server {
|
|||
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
resources,
|
||||
LockCaches::new_shared(account_id, resource.collection, lock_data),
|
||||
if is_lock_request {
|
||||
|
|
@ -215,7 +215,7 @@ impl LockRequestHandler for Server {
|
|||
} else if is_lock_request {
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
resources,
|
||||
Default::default(),
|
||||
DavMethod::LOCK,
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ impl<'x> DavQuery<'x> {
|
|||
pub fn propfind(
|
||||
resource: OwnedUri<'x>,
|
||||
propfind: PropFind,
|
||||
headers: RequestHeaders<'x>,
|
||||
headers: &RequestHeaders<'x>,
|
||||
) -> Self {
|
||||
Self {
|
||||
resource: DavQueryResource::Uri(resource),
|
||||
|
|
@ -164,7 +164,7 @@ impl<'x> DavQuery<'x> {
|
|||
pub fn multiget(
|
||||
multiget: MultiGet,
|
||||
collection: Collection,
|
||||
headers: RequestHeaders<'x>,
|
||||
headers: &RequestHeaders<'x>,
|
||||
) -> Self {
|
||||
Self {
|
||||
resource: DavQueryResource::Multiget {
|
||||
|
|
@ -182,7 +182,7 @@ impl<'x> DavQuery<'x> {
|
|||
pub fn addressbook_query(
|
||||
query: AddressbookQuery,
|
||||
items: Vec<PropFindItem>,
|
||||
headers: RequestHeaders<'x>,
|
||||
headers: &RequestHeaders<'x>,
|
||||
) -> Self {
|
||||
Self {
|
||||
resource: DavQueryResource::Query {
|
||||
|
|
@ -203,7 +203,7 @@ impl<'x> DavQuery<'x> {
|
|||
query: CalendarQuery,
|
||||
max_time_range: Option<TimeRange>,
|
||||
items: Vec<PropFindItem>,
|
||||
headers: RequestHeaders<'x>,
|
||||
headers: &RequestHeaders<'x>,
|
||||
) -> Self {
|
||||
Self {
|
||||
resource: DavQueryResource::Query {
|
||||
|
|
@ -226,7 +226,7 @@ impl<'x> DavQuery<'x> {
|
|||
pub fn changes(
|
||||
resource: OwnedUri<'x>,
|
||||
changes: SyncCollection,
|
||||
headers: RequestHeaders<'x>,
|
||||
headers: &RequestHeaders<'x>,
|
||||
) -> Self {
|
||||
Self {
|
||||
resource: DavQueryResource::Uri(resource),
|
||||
|
|
@ -254,7 +254,7 @@ impl<'x> DavQuery<'x> {
|
|||
pub fn expand(
|
||||
resource: OwnedUri<'x>,
|
||||
expand: ExpandProperty,
|
||||
headers: RequestHeaders<'x>,
|
||||
headers: &RequestHeaders<'x>,
|
||||
) -> Self {
|
||||
let mut props = Vec::with_capacity(expand.properties.len());
|
||||
for item in expand.properties {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ use dav_proto::{
|
|||
},
|
||||
},
|
||||
};
|
||||
use directory::{Type, backend::internal::manage::ManageDirectory};
|
||||
use directory::{Permission, Type, backend::internal::manage::ManageDirectory};
|
||||
use groupware::{
|
||||
DavCalendarResource, DavResourceName, cache::GroupwareCache, calendar::ArchivedTimezone,
|
||||
};
|
||||
|
|
@ -70,7 +70,7 @@ pub(crate) trait PropFindRequestHandler: Sync + Send {
|
|||
fn handle_propfind_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: PropFind,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ impl PropFindRequestHandler for Server {
|
|||
async fn handle_propfind_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: PropFind,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
|
|
@ -148,6 +148,18 @@ impl PropFindRequestHandler for Server {
|
|||
if let Some(account_id) = resource.account_id {
|
||||
match resource.collection {
|
||||
Collection::FileNode | Collection::Calendar | Collection::AddressBook => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(match resource.collection {
|
||||
Collection::FileNode => Permission::DavFilePropFind,
|
||||
Collection::Calendar | Collection::CalendarEvent => {
|
||||
Permission::DavCalPropFind
|
||||
}
|
||||
Collection::AddressBook | Collection::ContactCard => {
|
||||
Permission::DavCardPropFind
|
||||
}
|
||||
_ => unreachable!(),
|
||||
})?;
|
||||
|
||||
self.handle_dav_query(
|
||||
access_token,
|
||||
DavQuery::propfind(
|
||||
|
|
@ -165,16 +177,14 @@ impl PropFindRequestHandler for Server {
|
|||
Collection::Principal => {
|
||||
let mut response = MultiStatus::new(Vec::with_capacity(16));
|
||||
|
||||
if let Some(resource) = resource.resource {
|
||||
if resource.resource.is_some() {
|
||||
response.add_response(Response::new_status(
|
||||
[format!(
|
||||
"{}/{}",
|
||||
headers.base_uri().unwrap_or_default(),
|
||||
resource
|
||||
)],
|
||||
[headers.uri.to_string()],
|
||||
StatusCode::NOT_FOUND,
|
||||
));
|
||||
} else {
|
||||
} else if access_token.has_account_access(account_id)
|
||||
|| access_token.has_permission(Permission::DavPrincipalList)
|
||||
{
|
||||
self.prepare_principal_propfind_response(
|
||||
access_token,
|
||||
Collection::Principal,
|
||||
|
|
@ -183,6 +193,11 @@ impl PropFindRequestHandler for Server {
|
|||
&mut response,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
response.add_response(Response::new_status(
|
||||
[headers.uri.to_string()],
|
||||
StatusCode::FORBIDDEN,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(HttpResponse::new(StatusCode::MULTI_STATUS)
|
||||
|
|
@ -309,10 +324,21 @@ impl PropFindRequestHandler for Server {
|
|||
|
||||
if return_children {
|
||||
let ids = if !matches!(resource.collection, Collection::Principal) {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(match resource.collection {
|
||||
Collection::FileNode => Permission::DavFilePropFind,
|
||||
Collection::Calendar | Collection::CalendarEvent => {
|
||||
Permission::DavCalPropFind
|
||||
}
|
||||
Collection::AddressBook | Collection::ContactCard => {
|
||||
Permission::DavCardPropFind
|
||||
}
|
||||
_ => unreachable!(),
|
||||
})?;
|
||||
RoaringBitmap::from_iter(
|
||||
access_token.all_ids_by_collection(resource.collection),
|
||||
)
|
||||
} else {
|
||||
} else if access_token.has_permission(Permission::DavPrincipalList) {
|
||||
// Return all principals
|
||||
let principals = self
|
||||
.store()
|
||||
|
|
@ -328,6 +354,8 @@ impl PropFindRequestHandler for Server {
|
|||
.caused_by(trc::location!())?;
|
||||
|
||||
RoaringBitmap::from_iter(principals.items.into_iter().map(|p| p.id()))
|
||||
} else {
|
||||
RoaringBitmap::from_iter(access_token.all_ids())
|
||||
};
|
||||
|
||||
self.prepare_principal_propfind_response(
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ pub(crate) trait FileCopyMoveRequestHandler: Sync + Send {
|
|||
fn handle_file_copy_move_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
is_move: bool,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ impl FileCopyMoveRequestHandler for Server {
|
|||
async fn handle_file_copy_move_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
is_move: bool,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate source
|
||||
|
|
@ -117,8 +117,8 @@ impl FileCopyMoveRequestHandler for Server {
|
|||
return Ok(HttpResponse::new(StatusCode::BAD_GATEWAY));
|
||||
}
|
||||
|
||||
let mut delete_destination = None;
|
||||
// Check if the resource exists
|
||||
let mut delete_destination = None;
|
||||
let mut destination = if let Some((destination, new_name)) =
|
||||
to_resources.map_parent(destination_resource_name)
|
||||
{
|
||||
|
|
@ -144,16 +144,6 @@ impl FileCopyMoveRequestHandler for Server {
|
|||
};
|
||||
destination.account_id = to_account_id;
|
||||
|
||||
if delete_destination.is_none()
|
||||
&& from_account_id == destination.account_id
|
||||
&& from_resource.resource.parent_id == destination.document_id
|
||||
&& destination.new_name.is_some()
|
||||
&& is_move
|
||||
{
|
||||
// Rename
|
||||
return rename_item(self, access_token, from_resource, destination).await;
|
||||
}
|
||||
|
||||
// Validate destination ACLs
|
||||
if let Some(document_id) = destination.document_id {
|
||||
if let Some(delete_destination) = &delete_destination {
|
||||
|
|
@ -180,13 +170,13 @@ impl FileCopyMoveRequestHandler for Server {
|
|||
// Validate headers
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![
|
||||
ResourceState {
|
||||
account_id: from_account_id,
|
||||
collection: Collection::FileNode,
|
||||
document_id: Some(from_resource.resource.document_id),
|
||||
path: from_resource_.resource.unwrap(),
|
||||
path: from_resource_name,
|
||||
..Default::default()
|
||||
},
|
||||
ResourceState {
|
||||
|
|
@ -195,8 +185,7 @@ impl FileCopyMoveRequestHandler for Server {
|
|||
document_id: Some(
|
||||
delete_destination
|
||||
.as_ref()
|
||||
.unwrap_or(&destination)
|
||||
.document_id
|
||||
.and_then(|d| d.document_id)
|
||||
.unwrap_or(u32::MAX),
|
||||
),
|
||||
path: destination_resource_name,
|
||||
|
|
@ -212,6 +201,16 @@ impl FileCopyMoveRequestHandler for Server {
|
|||
)
|
||||
.await?;
|
||||
|
||||
if delete_destination.is_none()
|
||||
&& from_account_id == destination.account_id
|
||||
&& from_resource.resource.parent_id == destination.document_id
|
||||
&& destination.new_name.is_some()
|
||||
&& is_move
|
||||
{
|
||||
// Rename
|
||||
return rename_item(self, access_token, from_resource, destination).await;
|
||||
}
|
||||
|
||||
// Validate quota
|
||||
if !is_move || from_account_id != to_account_id {
|
||||
let space_needed = from_resources
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ pub(crate) trait FileDeleteRequestHandler: Sync + Send {
|
|||
fn handle_file_delete_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ impl FileDeleteRequestHandler for Server {
|
|||
async fn handle_file_delete_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
let resource = self
|
||||
|
|
@ -73,7 +73,7 @@ impl FileDeleteRequestHandler for Server {
|
|||
// Validate headers
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: resource.collection,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ use dav_proto::{RequestHeaders, schema::property::Rfc1123DateTime};
|
|||
use groupware::{cache::GroupwareCache, file::FileNode};
|
||||
use http_proto::HttpResponse;
|
||||
use hyper::StatusCode;
|
||||
use jmap_proto::types::{acl::Acl, collection::{Collection, SyncCollection}};
|
||||
use jmap_proto::types::{
|
||||
acl::Acl,
|
||||
collection::{Collection, SyncCollection},
|
||||
};
|
||||
use trc::AddContext;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -26,7 +29,7 @@ pub(crate) trait FileGetRequestHandler: Sync + Send {
|
|||
fn handle_file_get_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
is_head: bool,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
|
@ -35,7 +38,7 @@ impl FileGetRequestHandler for Server {
|
|||
async fn handle_file_get_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
is_head: bool,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
|
|
@ -79,7 +82,7 @@ impl FileGetRequestHandler for Server {
|
|||
let etag = node_.etag();
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: resource.collection,
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ pub(crate) trait FileMkColRequestHandler: Sync + Send {
|
|||
fn handle_file_mkcol_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: Option<MkCol>,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ impl FileMkColRequestHandler for Server {
|
|||
async fn handle_file_mkcol_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: Option<MkCol>,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
|
|
@ -69,7 +69,7 @@ impl FileMkColRequestHandler for Server {
|
|||
// Validate headers
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: resource.collection,
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ pub(crate) trait FilePropPatchRequestHandler: Sync + Send {
|
|||
fn handle_file_proppatch_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: PropertyUpdate,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ impl FilePropPatchRequestHandler for Server {
|
|||
async fn handle_file_proppatch_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
mut request: PropertyUpdate,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Validate URI
|
||||
|
|
@ -98,7 +98,7 @@ impl FilePropPatchRequestHandler for Server {
|
|||
// Validate headers
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: resource.collection,
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ pub(crate) trait FileUpdateRequestHandler: Sync + Send {
|
|||
fn handle_file_update_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
bytes: Vec<u8>,
|
||||
is_patch: bool,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
|
|
@ -46,7 +46,7 @@ impl FileUpdateRequestHandler for Server {
|
|||
async fn handle_file_update_request(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
bytes: Vec<u8>,
|
||||
_is_patch: bool,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
|
|
@ -94,7 +94,7 @@ impl FileUpdateRequestHandler for Server {
|
|||
match self
|
||||
.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: resource.collection,
|
||||
|
|
@ -215,7 +215,7 @@ impl FileUpdateRequestHandler for Server {
|
|||
// Validate headers
|
||||
self.validate_headers(
|
||||
access_token,
|
||||
&headers,
|
||||
headers,
|
||||
vec![ResourceState {
|
||||
account_id,
|
||||
collection: resource.collection,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,30 @@ pub enum DavMethod {
|
|||
ACL,
|
||||
}
|
||||
|
||||
impl From<DavMethod> for trc::WebDavEvent {
|
||||
fn from(value: DavMethod) -> Self {
|
||||
match value {
|
||||
DavMethod::GET => trc::WebDavEvent::Get,
|
||||
DavMethod::PUT => trc::WebDavEvent::Put,
|
||||
DavMethod::POST => trc::WebDavEvent::Post,
|
||||
DavMethod::DELETE => trc::WebDavEvent::Delete,
|
||||
DavMethod::HEAD => trc::WebDavEvent::Head,
|
||||
DavMethod::PATCH => trc::WebDavEvent::Patch,
|
||||
DavMethod::PROPFIND => trc::WebDavEvent::Propfind,
|
||||
DavMethod::PROPPATCH => trc::WebDavEvent::Proppatch,
|
||||
DavMethod::REPORT => trc::WebDavEvent::Report,
|
||||
DavMethod::MKCOL => trc::WebDavEvent::Mkcol,
|
||||
DavMethod::MKCALENDAR => trc::WebDavEvent::Mkcalendar,
|
||||
DavMethod::COPY => trc::WebDavEvent::Copy,
|
||||
DavMethod::MOVE => trc::WebDavEvent::Move,
|
||||
DavMethod::LOCK => trc::WebDavEvent::Lock,
|
||||
DavMethod::UNLOCK => trc::WebDavEvent::Unlock,
|
||||
DavMethod::OPTIONS => trc::WebDavEvent::Options,
|
||||
DavMethod::ACL => trc::WebDavEvent::Acl,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum DavError {
|
||||
Parse(dav_proto::parser::Error),
|
||||
Internal(trc::Error),
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ pub(crate) trait PrincipalMatching: Sync + Send {
|
|||
fn handle_principal_match(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: PrincipalMatch,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ impl PrincipalMatching for Server {
|
|||
async fn handle_principal_match(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: RequestHeaders<'_>,
|
||||
headers: &RequestHeaders<'_>,
|
||||
mut request: PrincipalMatch,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
let resource = self.validate_uri(access_token, headers.uri).await?;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ use crate::{
|
|||
principal::{matching::PrincipalMatching, propsearch::PrincipalPropSearch},
|
||||
};
|
||||
use common::{Server, auth::AccessToken};
|
||||
use compact_str::{CompactString, ToCompactString};
|
||||
use dav_proto::{
|
||||
RequestHeaders,
|
||||
parser::{DavParser, tokenizer::Tokenizer},
|
||||
|
|
@ -44,13 +45,13 @@ use dav_proto::{
|
|||
BaseCondition, ErrorResponse, PrincipalSearchProperty, PrincipalSearchPropertySet,
|
||||
},
|
||||
},
|
||||
xml_pretty_print,
|
||||
};
|
||||
use directory::Permission;
|
||||
use http_proto::{HttpRequest, HttpResponse, HttpSessionData, request::fetch_body};
|
||||
use hyper::{StatusCode, header};
|
||||
use jmap_proto::types::collection::Collection;
|
||||
use std::sync::Arc;
|
||||
use std::{sync::Arc, time::Instant};
|
||||
use trc::{EventType, LimitEvent, StoreEvent, WebDavEvent};
|
||||
|
||||
pub trait DavRequestHandler: Sync + Send {
|
||||
fn handle_dav_request(
|
||||
|
|
@ -67,6 +68,7 @@ pub(crate) trait DavRequestDispatcher: Sync + Send {
|
|||
fn dispatch_dav_request(
|
||||
&self,
|
||||
request: &HttpRequest,
|
||||
headers: &RequestHeaders<'_>,
|
||||
access_token: Arc<AccessToken>,
|
||||
resource: DavResourceName,
|
||||
method: DavMethod,
|
||||
|
|
@ -78,29 +80,25 @@ impl DavRequestDispatcher for Server {
|
|||
async fn dispatch_dav_request(
|
||||
&self,
|
||||
request: &HttpRequest,
|
||||
headers: &RequestHeaders<'_>,
|
||||
access_token: Arc<AccessToken>,
|
||||
resource: DavResourceName,
|
||||
method: DavMethod,
|
||||
body: Vec<u8>,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
// Parse headers
|
||||
let mut headers = RequestHeaders::new(request.uri().path());
|
||||
for (key, value) in request.headers() {
|
||||
headers.parse(key.as_str(), value.to_str().unwrap_or_default());
|
||||
}
|
||||
|
||||
// Dispatch
|
||||
match method {
|
||||
DavMethod::PROPFIND => {
|
||||
self.handle_propfind_request(
|
||||
&access_token,
|
||||
headers,
|
||||
PropFind::parse(&mut Tokenizer::new(&body))?,
|
||||
)
|
||||
.await
|
||||
let request = PropFind::parse(&mut Tokenizer::new(&body))?;
|
||||
|
||||
self.handle_propfind_request(&access_token, headers, request)
|
||||
.await
|
||||
}
|
||||
DavMethod::GET | DavMethod::HEAD => match resource {
|
||||
DavResourceName::Card => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavCardGet)?;
|
||||
|
||||
self.handle_card_get_request(
|
||||
&access_token,
|
||||
headers,
|
||||
|
|
@ -109,6 +107,9 @@ impl DavRequestDispatcher for Server {
|
|||
.await
|
||||
}
|
||||
DavResourceName::Cal => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavCalGet)?;
|
||||
|
||||
self.handle_calendar_get_request(
|
||||
&access_token,
|
||||
headers,
|
||||
|
|
@ -117,6 +118,9 @@ impl DavRequestDispatcher for Server {
|
|||
.await
|
||||
}
|
||||
DavResourceName::File => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavFileGet)?;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
// Deal with Litmus bug
|
||||
|
|
@ -143,6 +147,9 @@ impl DavRequestDispatcher for Server {
|
|||
},
|
||||
DavMethod::REPORT => match Report::parse(&mut Tokenizer::new(&body))? {
|
||||
Report::SyncCollection(sync_collection) => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavSyncCollection)?;
|
||||
|
||||
let uri = self
|
||||
.validate_uri(&access_token, headers.uri)
|
||||
.await
|
||||
|
|
@ -161,15 +168,24 @@ impl DavRequestDispatcher for Server {
|
|||
}
|
||||
}
|
||||
Report::AclPrincipalPropSet(report) => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavPrincipalAcl)?;
|
||||
|
||||
self.handle_acl_prop_set(&access_token, headers, report)
|
||||
.await
|
||||
}
|
||||
Report::PrincipalMatch(report) => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavPrincipalMatch)?;
|
||||
|
||||
self.handle_principal_match(&access_token, headers, report)
|
||||
.await
|
||||
}
|
||||
Report::PrincipalPropertySearch(report) => {
|
||||
if resource == DavResourceName::Principal {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavPrincipalSearch)?;
|
||||
|
||||
self.handle_principal_property_search(&access_token, report)
|
||||
.await
|
||||
} else {
|
||||
|
|
@ -178,6 +194,10 @@ impl DavRequestDispatcher for Server {
|
|||
}
|
||||
Report::PrincipalSearchPropertySet => {
|
||||
if resource == DavResourceName::Principal {
|
||||
// Validate permissions
|
||||
access_token
|
||||
.assert_has_permission(Permission::DavPrincipalSearchPropSet)?;
|
||||
|
||||
Ok(HttpResponse::new(StatusCode::OK).with_xml_body(
|
||||
PrincipalSearchPropertySet::new(vec![PrincipalSearchProperty::new(
|
||||
WebDavProperty::DisplayName,
|
||||
|
|
@ -190,10 +210,16 @@ impl DavRequestDispatcher for Server {
|
|||
}
|
||||
}
|
||||
Report::AddressbookQuery(report) => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavCardQuery)?;
|
||||
|
||||
self.handle_card_query_request(&access_token, headers, report)
|
||||
.await
|
||||
}
|
||||
Report::AddressbookMultiGet(report) => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavCardMultiGet)?;
|
||||
|
||||
self.handle_dav_query(
|
||||
&access_token,
|
||||
DavQuery::multiget(report, Collection::AddressBook, headers),
|
||||
|
|
@ -201,10 +227,16 @@ impl DavRequestDispatcher for Server {
|
|||
.await
|
||||
}
|
||||
Report::CalendarQuery(report) => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavCalQuery)?;
|
||||
|
||||
self.handle_calendar_query_request(&access_token, headers, report)
|
||||
.await
|
||||
}
|
||||
Report::CalendarMultiGet(report) => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavCalMultiGet)?;
|
||||
|
||||
self.handle_dav_query(
|
||||
&access_token,
|
||||
DavQuery::multiget(report, Collection::Calendar, headers),
|
||||
|
|
@ -212,6 +244,9 @@ impl DavRequestDispatcher for Server {
|
|||
.await
|
||||
}
|
||||
Report::FreeBusyQuery(report) => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavCalFreeBusyQuery)?;
|
||||
|
||||
self.handle_calendar_freebusy_request(&access_token, headers, report)
|
||||
.await
|
||||
}
|
||||
|
|
@ -220,6 +255,10 @@ impl DavRequestDispatcher for Server {
|
|||
.validate_uri(&access_token, headers.uri)
|
||||
.await
|
||||
.and_then(|d| d.into_owned_uri())?;
|
||||
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavExpandProperty)?;
|
||||
|
||||
match resource {
|
||||
DavResourceName::Card | DavResourceName::Cal | DavResourceName::File => {
|
||||
self.handle_dav_query(
|
||||
|
|
@ -238,14 +277,23 @@ impl DavRequestDispatcher for Server {
|
|||
let request = PropertyUpdate::parse(&mut Tokenizer::new(&body))?;
|
||||
match resource {
|
||||
DavResourceName::Card => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavCardPropPatch)?;
|
||||
|
||||
self.handle_card_proppatch_request(&access_token, headers, request)
|
||||
.await
|
||||
}
|
||||
DavResourceName::Cal => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavCalPropPatch)?;
|
||||
|
||||
self.handle_calendar_proppatch_request(&access_token, headers, request)
|
||||
.await
|
||||
}
|
||||
DavResourceName::File => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavFilePropPatch)?;
|
||||
|
||||
self.handle_file_proppatch_request(&access_token, headers, request)
|
||||
.await
|
||||
}
|
||||
|
|
@ -263,14 +311,23 @@ impl DavRequestDispatcher for Server {
|
|||
|
||||
match resource {
|
||||
DavResourceName::Card => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavCardMkCol)?;
|
||||
|
||||
self.handle_card_mkcol_request(&access_token, headers, request)
|
||||
.await
|
||||
}
|
||||
DavResourceName::Cal => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavCalMkCol)?;
|
||||
|
||||
self.handle_calendar_mkcol_request(&access_token, headers, request)
|
||||
.await
|
||||
}
|
||||
DavResourceName::File => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavFileMkCol)?;
|
||||
|
||||
self.handle_file_mkcol_request(&access_token, headers, request)
|
||||
.await
|
||||
}
|
||||
|
|
@ -279,33 +336,35 @@ impl DavRequestDispatcher for Server {
|
|||
}
|
||||
}
|
||||
}
|
||||
DavMethod::DELETE => {
|
||||
// Include any fragments in the URI
|
||||
if let Some(p) = request.uri().path_and_query() {
|
||||
// TODO: Access to the fragment part is pending, see https://github.com/hyperium/http/issues/127
|
||||
headers.uri = p.as_str();
|
||||
}
|
||||
DavMethod::DELETE => match resource {
|
||||
DavResourceName::Card => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavCardDelete)?;
|
||||
|
||||
match resource {
|
||||
DavResourceName::Card => {
|
||||
self.handle_card_delete_request(&access_token, headers)
|
||||
.await
|
||||
}
|
||||
DavResourceName::Cal => {
|
||||
self.handle_calendar_delete_request(&access_token, headers)
|
||||
.await
|
||||
}
|
||||
DavResourceName::File => {
|
||||
self.handle_file_delete_request(&access_token, headers)
|
||||
.await
|
||||
}
|
||||
DavResourceName::Principal => {
|
||||
Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED))
|
||||
}
|
||||
self.handle_card_delete_request(&access_token, headers)
|
||||
.await
|
||||
}
|
||||
}
|
||||
DavResourceName::Cal => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavCalDelete)?;
|
||||
|
||||
self.handle_calendar_delete_request(&access_token, headers)
|
||||
.await
|
||||
}
|
||||
DavResourceName::File => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavFileDelete)?;
|
||||
|
||||
self.handle_file_delete_request(&access_token, headers)
|
||||
.await
|
||||
}
|
||||
DavResourceName::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
},
|
||||
DavMethod::PUT | DavMethod::POST | DavMethod::PATCH => match resource {
|
||||
DavResourceName::Card => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavCardPut)?;
|
||||
|
||||
self.handle_card_update_request(
|
||||
&access_token,
|
||||
headers,
|
||||
|
|
@ -315,6 +374,9 @@ impl DavRequestDispatcher for Server {
|
|||
.await
|
||||
}
|
||||
DavResourceName::Cal => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavCalPut)?;
|
||||
|
||||
self.handle_calendar_update_request(
|
||||
&access_token,
|
||||
headers,
|
||||
|
|
@ -324,6 +386,9 @@ impl DavRequestDispatcher for Server {
|
|||
.await
|
||||
}
|
||||
DavResourceName::File => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavFilePut)?;
|
||||
|
||||
self.handle_file_update_request(
|
||||
&access_token,
|
||||
headers,
|
||||
|
|
@ -334,35 +399,51 @@ impl DavRequestDispatcher for Server {
|
|||
}
|
||||
DavResourceName::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
},
|
||||
DavMethod::COPY | DavMethod::MOVE => match resource {
|
||||
DavResourceName::Card => {
|
||||
self.handle_card_copy_move_request(
|
||||
&access_token,
|
||||
headers,
|
||||
matches!(method, DavMethod::MOVE),
|
||||
)
|
||||
.await
|
||||
DavMethod::COPY | DavMethod::MOVE => {
|
||||
let is_move = matches!(method, DavMethod::MOVE);
|
||||
match resource {
|
||||
DavResourceName::Card => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(if is_move {
|
||||
Permission::DavCardMove
|
||||
} else {
|
||||
Permission::DavCardCopy
|
||||
})?;
|
||||
|
||||
self.handle_card_copy_move_request(&access_token, headers, is_move)
|
||||
.await
|
||||
}
|
||||
DavResourceName::Cal => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(if is_move {
|
||||
Permission::DavCalMove
|
||||
} else {
|
||||
Permission::DavCalCopy
|
||||
})?;
|
||||
self.handle_calendar_copy_move_request(&access_token, headers, is_move)
|
||||
.await
|
||||
}
|
||||
DavResourceName::File => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(if is_move {
|
||||
Permission::DavFileMove
|
||||
} else {
|
||||
Permission::DavFileCopy
|
||||
})?;
|
||||
|
||||
self.handle_file_copy_move_request(&access_token, headers, is_move)
|
||||
.await
|
||||
}
|
||||
DavResourceName::Principal => {
|
||||
Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED))
|
||||
}
|
||||
}
|
||||
DavResourceName::Cal => {
|
||||
self.handle_calendar_copy_move_request(
|
||||
&access_token,
|
||||
headers,
|
||||
matches!(method, DavMethod::MOVE),
|
||||
)
|
||||
.await
|
||||
}
|
||||
DavResourceName::File => {
|
||||
self.handle_file_copy_move_request(
|
||||
&access_token,
|
||||
headers,
|
||||
matches!(method, DavMethod::MOVE),
|
||||
)
|
||||
.await
|
||||
}
|
||||
DavResourceName::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
},
|
||||
}
|
||||
DavMethod::MKCALENDAR => match resource {
|
||||
DavResourceName::Cal => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::DavCalMkCol)?;
|
||||
|
||||
self.handle_calendar_mkcol_request(
|
||||
&access_token,
|
||||
headers,
|
||||
|
|
@ -372,36 +453,53 @@ impl DavRequestDispatcher for Server {
|
|||
}
|
||||
_ => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
},
|
||||
DavMethod::LOCK => match resource {
|
||||
DavResourceName::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
_ => {
|
||||
self.handle_lock_request(
|
||||
&access_token,
|
||||
headers,
|
||||
if !body.is_empty() {
|
||||
LockRequest::Lock(LockInfo::parse(&mut Tokenizer::new(&body))?)
|
||||
} else {
|
||||
LockRequest::Refresh
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
},
|
||||
DavMethod::LOCK => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(match resource {
|
||||
DavResourceName::File => Permission::DavFileLock,
|
||||
DavResourceName::Cal => Permission::DavCalLock,
|
||||
DavResourceName::Card => Permission::DavCardLock,
|
||||
_ => return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
})?;
|
||||
|
||||
self.handle_lock_request(
|
||||
&access_token,
|
||||
headers,
|
||||
if !body.is_empty() {
|
||||
LockRequest::Lock(LockInfo::parse(&mut Tokenizer::new(&body))?)
|
||||
} else {
|
||||
LockRequest::Refresh
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
DavMethod::UNLOCK => {
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(match resource {
|
||||
DavResourceName::File => Permission::DavFileLock,
|
||||
DavResourceName::Cal => Permission::DavCalLock,
|
||||
DavResourceName::Card => Permission::DavCardLock,
|
||||
_ => return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
})?;
|
||||
|
||||
self.handle_lock_request(&access_token, headers, LockRequest::Unlock)
|
||||
.await
|
||||
}
|
||||
DavMethod::ACL => {
|
||||
let request = Acl::parse(&mut Tokenizer::new(&body))?;
|
||||
match resource {
|
||||
DavResourceName::Card | DavResourceName::Cal | DavResourceName::File => {
|
||||
self.handle_acl_request(&access_token, headers, request)
|
||||
.await
|
||||
}
|
||||
DavResourceName::Principal => {
|
||||
Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED))
|
||||
}
|
||||
}
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(match resource {
|
||||
DavResourceName::File => Permission::DavFileAcl,
|
||||
DavResourceName::Cal => Permission::DavCalAcl,
|
||||
DavResourceName::Card => Permission::DavCardAcl,
|
||||
_ => return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
})?;
|
||||
|
||||
self.handle_acl_request(
|
||||
&access_token,
|
||||
headers,
|
||||
Acl::parse(&mut Tokenizer::new(&body))?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
DavMethod::OPTIONS => unreachable!(),
|
||||
}
|
||||
|
|
@ -454,61 +552,128 @@ impl DavRequestHandler for Server {
|
|||
|
||||
let std_body = std::str::from_utf8(&body).unwrap_or("[binary]").to_string();
|
||||
|
||||
// Parse headers
|
||||
let mut headers = RequestHeaders::new(request.uri().path());
|
||||
for (key, value) in request.headers() {
|
||||
headers.parse(key.as_str(), value.to_str().unwrap_or_default());
|
||||
}
|
||||
|
||||
let start_time = Instant::now();
|
||||
let result = match self
|
||||
.dispatch_dav_request(&request, access_token, resource, method, body)
|
||||
.dispatch_dav_request(&request, &headers, access_token, resource, method, body)
|
||||
.await
|
||||
{
|
||||
Ok(response) => response,
|
||||
Ok(response) => {
|
||||
let event = WebDavEvent::from(method);
|
||||
|
||||
trc::event!(
|
||||
WebDav(event),
|
||||
SpanId = session.session_id,
|
||||
Url = headers.uri.to_compact_string(),
|
||||
Type = resource.name(),
|
||||
Details = &headers,
|
||||
Result = response.status().as_u16(),
|
||||
Elapsed = start_time.elapsed(),
|
||||
);
|
||||
|
||||
response
|
||||
}
|
||||
Err(DavError::Internal(err)) => {
|
||||
let err_type = err.event_type();
|
||||
|
||||
trc::error!(err.span_id(session.session_id));
|
||||
trc::error!(
|
||||
err.span_id(session.session_id)
|
||||
.ctx(trc::Key::Url, headers.uri.to_compact_string())
|
||||
.ctx(trc::Key::Type, resource.name())
|
||||
.ctx(trc::Key::Elapsed, start_time.elapsed())
|
||||
);
|
||||
|
||||
match err_type {
|
||||
trc::EventType::Limit(
|
||||
trc::LimitEvent::Quota | trc::LimitEvent::TenantQuota,
|
||||
) => HttpResponse::new(StatusCode::PRECONDITION_FAILED)
|
||||
.with_xml_body(
|
||||
ErrorResponse::new(BaseCondition::QuotaNotExceeded)
|
||||
.with_namespace(match resource {
|
||||
DavResourceName::Card => Namespace::CardDav,
|
||||
DavResourceName::Cal => Namespace::CalDav,
|
||||
DavResourceName::File | DavResourceName::Principal => {
|
||||
Namespace::Dav
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.with_no_cache(),
|
||||
trc::EventType::Store(trc::StoreEvent::AssertValueFailed) => {
|
||||
EventType::Limit(LimitEvent::Quota | LimitEvent::TenantQuota) => {
|
||||
HttpResponse::new(StatusCode::PRECONDITION_FAILED)
|
||||
.with_xml_body(
|
||||
ErrorResponse::new(BaseCondition::QuotaNotExceeded)
|
||||
.with_namespace(match resource {
|
||||
DavResourceName::Card => Namespace::CardDav,
|
||||
DavResourceName::Cal => Namespace::CalDav,
|
||||
DavResourceName::File | DavResourceName::Principal => {
|
||||
Namespace::Dav
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.with_no_cache()
|
||||
}
|
||||
EventType::Store(StoreEvent::AssertValueFailed) => {
|
||||
HttpResponse::new(StatusCode::CONFLICT)
|
||||
}
|
||||
EventType::Security(_) => HttpResponse::new(StatusCode::FORBIDDEN),
|
||||
_ => HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
Err(DavError::Parse(err)) => {
|
||||
if request
|
||||
.headers()
|
||||
.get(header::CONTENT_TYPE)
|
||||
.is_some_and(|h| h.to_str().unwrap_or_default().contains("/xml"))
|
||||
{
|
||||
HttpResponse::new(StatusCode::BAD_REQUEST)
|
||||
let result = if headers.content_type.is_some_and(|h| h.contains("/xml")) {
|
||||
StatusCode::BAD_REQUEST
|
||||
} else {
|
||||
HttpResponse::new(StatusCode::UNSUPPORTED_MEDIA_TYPE)
|
||||
}
|
||||
StatusCode::UNSUPPORTED_MEDIA_TYPE
|
||||
};
|
||||
|
||||
trc::event!(
|
||||
WebDav(WebDavEvent::Error),
|
||||
SpanId = session.session_id,
|
||||
Url = headers.uri.to_compact_string(),
|
||||
Type = resource.name(),
|
||||
Details = &headers,
|
||||
Result = result.as_u16(),
|
||||
Reason = err.to_compact_string(),
|
||||
Elapsed = start_time.elapsed(),
|
||||
);
|
||||
|
||||
HttpResponse::new(result)
|
||||
}
|
||||
Err(DavError::Condition(condition)) => {
|
||||
let event = WebDavEvent::from(method);
|
||||
|
||||
trc::event!(
|
||||
WebDav(event),
|
||||
SpanId = session.session_id,
|
||||
Url = headers.uri.to_compact_string(),
|
||||
Type = resource.name(),
|
||||
Details = &headers,
|
||||
Result = condition.code.as_u16(),
|
||||
Reason = CompactString::const_new(condition.condition.display_name()),
|
||||
Elapsed = start_time.elapsed(),
|
||||
);
|
||||
|
||||
HttpResponse::new(condition.code)
|
||||
.with_xml_body(
|
||||
ErrorResponse::new(condition.condition)
|
||||
.with_namespace(match resource {
|
||||
DavResourceName::Card => Namespace::CardDav,
|
||||
DavResourceName::Cal => Namespace::CalDav,
|
||||
DavResourceName::File | DavResourceName::Principal => {
|
||||
Namespace::Dav
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.with_no_cache()
|
||||
}
|
||||
Err(DavError::Code(code)) => {
|
||||
let event = WebDavEvent::from(method);
|
||||
|
||||
trc::event!(
|
||||
WebDav(event),
|
||||
SpanId = session.session_id,
|
||||
Url = headers.uri.to_compact_string(),
|
||||
Type = resource.name(),
|
||||
Details = &headers,
|
||||
Result = code.as_u16(),
|
||||
Elapsed = start_time.elapsed(),
|
||||
);
|
||||
|
||||
HttpResponse::new(code)
|
||||
}
|
||||
Err(DavError::Condition(condition)) => HttpResponse::new(condition.code)
|
||||
.with_xml_body(
|
||||
ErrorResponse::new(condition.condition)
|
||||
.with_namespace(match resource {
|
||||
DavResourceName::Card => Namespace::CardDav,
|
||||
DavResourceName::Cal => Namespace::CalDav,
|
||||
DavResourceName::File | DavResourceName::Principal => Namespace::Dav,
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.with_no_cache(),
|
||||
Err(DavError::Code(code)) => HttpResponse::new(code),
|
||||
};
|
||||
|
||||
/*let c = println!(
|
||||
|
|
@ -520,7 +685,7 @@ impl DavRequestHandler for Server {
|
|||
std_body,
|
||||
result.headers().unwrap(),
|
||||
match &result.body() {
|
||||
http_proto::HttpResponseBody::Text(t) => xml_pretty_print(t),
|
||||
http_proto::HttpResponseBody::Text(t) => dav_proto::xml_pretty_print(t),
|
||||
http_proto::HttpResponseBody::Empty => "[empty]".to_string(),
|
||||
_ => "[binary]".to_string(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,6 +200,52 @@ impl Permission {
|
|||
Permission::OauthClientDelete => "Remove OAuth clients",
|
||||
Permission::AiModelInteract => "Interact with AI models",
|
||||
Permission::Troubleshoot => "Perform troubleshooting",
|
||||
Permission::DavSyncCollection => "Synchronize collection changes with client",
|
||||
Permission::DavPrincipalAcl => "Set principal properties for access control",
|
||||
Permission::DavPrincipalMatch => "Match principals based on specified criteria",
|
||||
Permission::DavPrincipalSearch => "Search for principals by property values",
|
||||
Permission::DavPrincipalSearchPropSet => "Define property sets for principal searches",
|
||||
Permission::DavExpandProperty => "Expand properties that reference other resources",
|
||||
Permission::DavPrincipalList => "List available principals in the system",
|
||||
Permission::DavFilePropFind => "Retrieve properties of file resources",
|
||||
Permission::DavFilePropPatch => "Modify properties of file resources",
|
||||
Permission::DavFileGet => "Download file resources",
|
||||
Permission::DavFileMkCol => "Create new file collections or directories",
|
||||
Permission::DavFileDelete => "Remove file resources",
|
||||
Permission::DavFilePut => "Upload or modify file resources",
|
||||
Permission::DavFileCopy => "Copy file resources to new locations",
|
||||
Permission::DavFileMove => "Move file resources to new locations",
|
||||
Permission::DavFileLock => "Lock file resources to prevent concurrent modifications",
|
||||
Permission::DavFileAcl => "Manage access control lists for file resources",
|
||||
Permission::DavCardPropFind => "Retrieve properties of address book entries",
|
||||
Permission::DavCardPropPatch => "Modify properties of address book entries",
|
||||
Permission::DavCardGet => "Download address book entries",
|
||||
Permission::DavCardMkCol => "Create new address book collections",
|
||||
Permission::DavCardDelete => "Remove address book entries or collections",
|
||||
Permission::DavCardPut => "Upload or modify address book entries",
|
||||
Permission::DavCardCopy => "Copy address book entries to new locations",
|
||||
Permission::DavCardMove => "Move address book entries to new locations",
|
||||
Permission::DavCardLock => {
|
||||
"Lock address book entries to prevent concurrent modifications"
|
||||
}
|
||||
Permission::DavCardAcl => "Manage access control lists for address book entries",
|
||||
Permission::DavCardQuery => "Search for address book entries matching criteria",
|
||||
Permission::DavCardMultiGet => {
|
||||
"Retrieve multiple address book entries in a single request"
|
||||
}
|
||||
Permission::DavCalPropFind => "Retrieve properties of calendar entries",
|
||||
Permission::DavCalPropPatch => "Modify properties of calendar entries",
|
||||
Permission::DavCalGet => "Download calendar entries",
|
||||
Permission::DavCalMkCol => "Create new calendar collections",
|
||||
Permission::DavCalDelete => "Remove calendar entries or collections",
|
||||
Permission::DavCalPut => "Upload or modify calendar entries",
|
||||
Permission::DavCalCopy => "Copy calendar entries to new locations",
|
||||
Permission::DavCalMove => "Move calendar entries to new locations",
|
||||
Permission::DavCalLock => "Lock calendar entries to prevent concurrent modifications",
|
||||
Permission::DavCalAcl => "Manage access control lists for calendar entries",
|
||||
Permission::DavCalQuery => "Search for calendar entries matching criteria",
|
||||
Permission::DavCalMultiGet => "Retrieve multiple calendar entries in a single request",
|
||||
Permission::DavCalFreeBusyQuery => "Query free/busy time information for scheduling",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1367,6 +1367,46 @@ impl Permission {
|
|||
| Permission::SieveHaveSpace
|
||||
| Permission::SpamFilterClassify
|
||||
| Permission::SpamFilterTrain
|
||||
| Permission::DavSyncCollection
|
||||
| Permission::DavExpandProperty
|
||||
| Permission::DavPrincipalAcl
|
||||
| Permission::DavPrincipalMatch
|
||||
| Permission::DavPrincipalSearchPropSet
|
||||
| Permission::DavFilePropFind
|
||||
| Permission::DavFilePropPatch
|
||||
| Permission::DavFileGet
|
||||
| Permission::DavFileMkCol
|
||||
| Permission::DavFileDelete
|
||||
| Permission::DavFilePut
|
||||
| Permission::DavFileCopy
|
||||
| Permission::DavFileMove
|
||||
| Permission::DavFileLock
|
||||
| Permission::DavFileAcl
|
||||
| Permission::DavCardPropFind
|
||||
| Permission::DavCardPropPatch
|
||||
| Permission::DavCardGet
|
||||
| Permission::DavCardMkCol
|
||||
| Permission::DavCardDelete
|
||||
| Permission::DavCardPut
|
||||
| Permission::DavCardCopy
|
||||
| Permission::DavCardMove
|
||||
| Permission::DavCardLock
|
||||
| Permission::DavCardAcl
|
||||
| Permission::DavCardQuery
|
||||
| Permission::DavCardMultiGet
|
||||
| Permission::DavCalPropFind
|
||||
| Permission::DavCalPropPatch
|
||||
| Permission::DavCalGet
|
||||
| Permission::DavCalMkCol
|
||||
| Permission::DavCalDelete
|
||||
| Permission::DavCalPut
|
||||
| Permission::DavCalCopy
|
||||
| Permission::DavCalMove
|
||||
| Permission::DavCalLock
|
||||
| Permission::DavCalAcl
|
||||
| Permission::DavCalQuery
|
||||
| Permission::DavCalMultiGet
|
||||
| Permission::DavCalFreeBusyQuery
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -330,6 +330,54 @@ pub enum Permission {
|
|||
AiModelInteract,
|
||||
Troubleshoot,
|
||||
SpamFilterClassify,
|
||||
|
||||
// WebDAV permissions
|
||||
DavSyncCollection,
|
||||
DavExpandProperty,
|
||||
|
||||
DavPrincipalAcl,
|
||||
DavPrincipalList,
|
||||
DavPrincipalMatch,
|
||||
DavPrincipalSearch,
|
||||
DavPrincipalSearchPropSet,
|
||||
|
||||
DavFilePropFind,
|
||||
DavFilePropPatch,
|
||||
DavFileGet,
|
||||
DavFileMkCol,
|
||||
DavFileDelete,
|
||||
DavFilePut,
|
||||
DavFileCopy,
|
||||
DavFileMove,
|
||||
DavFileLock,
|
||||
DavFileAcl,
|
||||
|
||||
DavCardPropFind,
|
||||
DavCardPropPatch,
|
||||
DavCardGet,
|
||||
DavCardMkCol,
|
||||
DavCardDelete,
|
||||
DavCardPut,
|
||||
DavCardCopy,
|
||||
DavCardMove,
|
||||
DavCardLock,
|
||||
DavCardAcl,
|
||||
DavCardQuery,
|
||||
DavCardMultiGet,
|
||||
|
||||
DavCalPropFind,
|
||||
DavCalPropPatch,
|
||||
DavCalGet,
|
||||
DavCalMkCol,
|
||||
DavCalDelete,
|
||||
DavCalPut,
|
||||
DavCalCopy,
|
||||
DavCalMove,
|
||||
DavCalLock,
|
||||
DavCalAcl,
|
||||
DavCalQuery,
|
||||
DavCalMultiGet,
|
||||
DavCalFreeBusyQuery,
|
||||
// WARNING: add new ids at the end (TODO: use static ids)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ use calcard::{
|
|||
},
|
||||
};
|
||||
use chrono::{DateTime, TimeZone};
|
||||
use compact_str::ToCompactString;
|
||||
use dav_proto::schema::property::TimeRange;
|
||||
use std::str::FromStr;
|
||||
use store::{
|
||||
|
|
@ -123,8 +124,17 @@ impl CalendarEventData {
|
|||
});
|
||||
}
|
||||
|
||||
for error in expanded.errors {
|
||||
let todo = "log me";
|
||||
if !expanded.errors.is_empty() {
|
||||
trc::event!(
|
||||
Calendar(trc::CalendarEvent::RuleExpansionError),
|
||||
Reason = expanded
|
||||
.errors
|
||||
.into_iter()
|
||||
.map(|e| e.error.to_compact_string())
|
||||
.collect::<Vec<_>>(),
|
||||
Details = ical.to_string(),
|
||||
Limit = max_expansions,
|
||||
);
|
||||
}
|
||||
|
||||
CalendarEventData {
|
||||
|
|
|
|||
|
|
@ -52,6 +52,15 @@ impl DavResourceName {
|
|||
DavResourceName::Principal => "/dav/pal/",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
DavResourceName::Card => "CardDAV",
|
||||
DavResourceName::Cal => "CalDAV",
|
||||
DavResourceName::File => "WebDAV",
|
||||
DavResourceName::Principal => "Principal",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DavResourceName> for Collection {
|
||||
|
|
|
|||
|
|
@ -213,7 +213,10 @@ impl ParseHttp for Server {
|
|||
(Some(_), Some(DavMethod::OPTIONS)) => HttpResponse::new(StatusCode::OK)
|
||||
.with_header(
|
||||
"DAV",
|
||||
"1, 2, 3, access-control, extended-mkcol, calendar-access, addressbook",
|
||||
concat!(
|
||||
"1, 2, 3, access-control, extended-mkcol, calendar-access, ",
|
||||
"calendar-no-timezone, addressbook"
|
||||
),
|
||||
)
|
||||
.with_header(
|
||||
"Allow",
|
||||
|
|
|
|||
|
|
@ -406,12 +406,12 @@ impl<T: SessionStream> Session<T> {
|
|||
.await
|
||||
}
|
||||
|
||||
pub fn assert_has_permission(&self, permission: Permission) -> trc::Result<()> {
|
||||
pub fn assert_has_permission(&self, permission: Permission) -> trc::Result<bool> {
|
||||
match &self.state {
|
||||
State::Authenticated { data } | State::Selected { data, .. } => {
|
||||
data.access_token.assert_has_permission(permission)
|
||||
}
|
||||
State::NotAuthenticated { .. } => Ok(()),
|
||||
State::NotAuthenticated { .. } => Ok(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ impl<T: SessionStream> Session<T> {
|
|||
Ok(StatusResponse::ok("Begin TLS negotiation now").into_bytes())
|
||||
}
|
||||
|
||||
pub fn assert_has_permission(&self, permission: Permission) -> trc::Result<()> {
|
||||
pub fn assert_has_permission(&self, permission: Permission) -> trc::Result<bool> {
|
||||
match &self.state {
|
||||
State::Authenticated { access_token, .. } => {
|
||||
access_token.assert_has_permission(permission)
|
||||
}
|
||||
State::NotAuthenticated { .. } => Ok(()),
|
||||
State::NotAuthenticated { .. } => Ok(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ impl EventType {
|
|||
EventType::MessageIngest(event) => event.description(),
|
||||
EventType::Security(event) => event.description(),
|
||||
EventType::Ai(event) => event.description(),
|
||||
EventType::WebDav(event) => event.description(),
|
||||
EventType::Calendar(event) => event.description(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +102,8 @@ impl EventType {
|
|||
EventType::MessageIngest(event) => event.explain(),
|
||||
EventType::Security(event) => event.explain(),
|
||||
EventType::Ai(event) => event.explain(),
|
||||
EventType::WebDav(event) => event.explain(),
|
||||
EventType::Calendar(event) => event.explain(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1825,3 +1829,70 @@ impl AiEvent {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WebDavEvent {
|
||||
pub fn description(&self) -> &'static str {
|
||||
match self {
|
||||
WebDavEvent::Propfind => "WebDAV PROPFIND request",
|
||||
WebDavEvent::Proppatch => "WebDAV PROPPATCH request",
|
||||
WebDavEvent::Get => "WebDAV GET request",
|
||||
WebDavEvent::Report => "WebDAV REPORT request",
|
||||
WebDavEvent::Mkcol => "WebDAV MKCOL request",
|
||||
WebDavEvent::Delete => "WebDAV DELETE request",
|
||||
WebDavEvent::Put => "WebDAV PUT request",
|
||||
WebDavEvent::Post => "WebDAV POST request",
|
||||
WebDavEvent::Patch => "WebDAV PATCH request",
|
||||
WebDavEvent::Copy => "WebDAV COPY request",
|
||||
WebDavEvent::Move => "WebDAV MOVE request",
|
||||
WebDavEvent::Lock => "WebDAV LOCK request",
|
||||
WebDavEvent::Unlock => "WebDAV UNLOCK request",
|
||||
WebDavEvent::Acl => "WebDAV ACL request",
|
||||
WebDavEvent::Error => "WebDAV error",
|
||||
WebDavEvent::Head => "WebDAV HEAD request",
|
||||
WebDavEvent::Mkcalendar => "WebDAV MKCALENDAR request",
|
||||
WebDavEvent::Options => "WebDAV OPTIONS request",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn explain(&self) -> &'static str {
|
||||
match self {
|
||||
WebDavEvent::Propfind => "A PROPFIND request has been made to the server",
|
||||
WebDavEvent::Proppatch => "A PROPPATCH request has been made to the server",
|
||||
WebDavEvent::Get => "A GET request has been made to the server",
|
||||
WebDavEvent::Report => "A REPORT request has been made to the server",
|
||||
WebDavEvent::Mkcol => "A MKCOL request has been made to the server",
|
||||
WebDavEvent::Delete => "A DELETE request has been made to the server",
|
||||
WebDavEvent::Put => "A PUT request has been made to the server",
|
||||
WebDavEvent::Post => "A POST request has been made to the server",
|
||||
WebDavEvent::Patch => "A PATCH request has been made to the server",
|
||||
WebDavEvent::Copy => "A COPY request has been made to the server",
|
||||
WebDavEvent::Move => "A MOVE request has been made to the server",
|
||||
WebDavEvent::Lock => "A LOCK request has been made to the server",
|
||||
WebDavEvent::Unlock => "An UNLOCK request has been made to the server",
|
||||
WebDavEvent::Acl => {
|
||||
"An ACL request has been made to the
|
||||
server"
|
||||
}
|
||||
WebDavEvent::Error => "An error occurred with the WebDAV request",
|
||||
WebDavEvent::Head => "A HEAD request has been made to the server",
|
||||
WebDavEvent::Mkcalendar => "A MKCALENDAR request has been made to the server",
|
||||
WebDavEvent::Options => "An OPTIONS request has been made to the server",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEvent {
|
||||
pub fn description(&self) -> &'static str {
|
||||
match self {
|
||||
CalendarEvent::RuleExpansionError => "Calendar rule expansion error",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn explain(&self) -> &'static str {
|
||||
match self {
|
||||
CalendarEvent::RuleExpansionError => {
|
||||
"An error occurred while expanding calendar recurrences"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -532,6 +532,8 @@ impl EventType {
|
|||
AiEvent::LlmResponse => Level::Trace,
|
||||
AiEvent::ApiError => Level::Warn,
|
||||
},
|
||||
EventType::WebDav(_) => Level::Debug,
|
||||
EventType::Calendar(CalendarEvent::RuleExpansionError) => Level::Debug,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,6 +187,8 @@ pub enum EventType {
|
|||
Telemetry(TelemetryEvent),
|
||||
Security(SecurityEvent),
|
||||
Ai(AiEvent),
|
||||
WebDav(WebDavEvent),
|
||||
Calendar(CalendarEvent),
|
||||
}
|
||||
|
||||
#[event_type]
|
||||
|
|
@ -950,6 +952,36 @@ pub enum AiEvent {
|
|||
ApiError,
|
||||
}
|
||||
|
||||
#[event_type]
|
||||
pub enum WebDavEvent {
|
||||
// Requests
|
||||
Propfind,
|
||||
Proppatch,
|
||||
Get,
|
||||
Head,
|
||||
Report,
|
||||
Mkcol,
|
||||
Mkcalendar,
|
||||
Delete,
|
||||
Put,
|
||||
Post,
|
||||
Patch,
|
||||
Copy,
|
||||
Move,
|
||||
Lock,
|
||||
Unlock,
|
||||
Acl,
|
||||
Options,
|
||||
|
||||
// Errors
|
||||
Error,
|
||||
}
|
||||
|
||||
#[event_type]
|
||||
pub enum CalendarEvent {
|
||||
RuleExpansionError,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum MetricType {
|
||||
ServerMemory,
|
||||
|
|
|
|||
|
|
@ -867,6 +867,25 @@ impl EventType {
|
|||
EventType::Spam(SpamEvent::Pyzor) => 564,
|
||||
EventType::Queue(QueueEvent::BackPressure) => 48,
|
||||
EventType::Imap(ImapEvent::GetQuota) => 57,
|
||||
EventType::WebDav(WebDavEvent::Propfind) => 147,
|
||||
EventType::WebDav(WebDavEvent::Proppatch) => 148,
|
||||
EventType::WebDav(WebDavEvent::Get) => 335,
|
||||
EventType::WebDav(WebDavEvent::Report) => 336,
|
||||
EventType::WebDav(WebDavEvent::Mkcol) => 376,
|
||||
EventType::WebDav(WebDavEvent::Delete) => 458,
|
||||
EventType::WebDav(WebDavEvent::Put) => 459,
|
||||
EventType::WebDav(WebDavEvent::Post) => 565,
|
||||
EventType::WebDav(WebDavEvent::Patch) => 566,
|
||||
EventType::WebDav(WebDavEvent::Copy) => 567,
|
||||
EventType::WebDav(WebDavEvent::Move) => 568,
|
||||
EventType::WebDav(WebDavEvent::Lock) => 569,
|
||||
EventType::WebDav(WebDavEvent::Unlock) => 570,
|
||||
EventType::WebDav(WebDavEvent::Acl) => 571,
|
||||
EventType::WebDav(WebDavEvent::Error) => 572,
|
||||
EventType::WebDav(WebDavEvent::Options) => 573,
|
||||
EventType::WebDav(WebDavEvent::Head) => 574,
|
||||
EventType::WebDav(WebDavEvent::Mkcalendar) => 575,
|
||||
EventType::Calendar(CalendarEvent::RuleExpansionError) => 576,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1470,13 +1489,30 @@ impl EventType {
|
|||
564 => Some(EventType::Spam(SpamEvent::Pyzor)),
|
||||
48 => Some(EventType::Queue(QueueEvent::BackPressure)),
|
||||
57 => Some(EventType::Imap(ImapEvent::GetQuota)),
|
||||
147 => Some(EventType::WebDav(WebDavEvent::Propfind)),
|
||||
148 => Some(EventType::WebDav(WebDavEvent::Proppatch)),
|
||||
335 => Some(EventType::WebDav(WebDavEvent::Get)),
|
||||
336 => Some(EventType::WebDav(WebDavEvent::Report)),
|
||||
376 => Some(EventType::WebDav(WebDavEvent::Mkcol)),
|
||||
458 => Some(EventType::WebDav(WebDavEvent::Delete)),
|
||||
459 => Some(EventType::WebDav(WebDavEvent::Put)),
|
||||
565 => Some(EventType::WebDav(WebDavEvent::Post)),
|
||||
566 => Some(EventType::WebDav(WebDavEvent::Patch)),
|
||||
567 => Some(EventType::WebDav(WebDavEvent::Copy)),
|
||||
568 => Some(EventType::WebDav(WebDavEvent::Move)),
|
||||
569 => Some(EventType::WebDav(WebDavEvent::Lock)),
|
||||
570 => Some(EventType::WebDav(WebDavEvent::Unlock)),
|
||||
571 => Some(EventType::WebDav(WebDavEvent::Acl)),
|
||||
572 => Some(EventType::WebDav(WebDavEvent::Error)),
|
||||
573 => Some(EventType::WebDav(WebDavEvent::Options)),
|
||||
574 => Some(EventType::WebDav(WebDavEvent::Head)),
|
||||
575 => Some(EventType::WebDav(WebDavEvent::Mkcalendar)),
|
||||
576 => Some(EventType::Calendar(CalendarEvent::RuleExpansionError)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 147 148 335 336 376 458 459
|
||||
|
||||
impl Key {
|
||||
fn code(&self) -> u64 {
|
||||
match self {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
use ahash::AHashSet;
|
||||
use directory::{
|
||||
QueryBy, Type,
|
||||
Permission, QueryBy, Type,
|
||||
backend::{
|
||||
RcptType,
|
||||
internal::{
|
||||
|
|
@ -774,6 +774,7 @@ pub trait TestInternalDirectory {
|
|||
async fn create_test_group(&self, login: &str, name: &str, emails: &[&str]) -> u32;
|
||||
async fn create_test_list(&self, login: &str, name: &str, emails: &[&str]) -> u32;
|
||||
async fn set_test_quota(&self, login: &str, quota: u32);
|
||||
async fn add_permissions(&self, login: &str, permissions: impl IntoIterator<Item = Permission>);
|
||||
async fn add_to_group(&self, login: &str, group: &str) -> ChangedPrincipals;
|
||||
async fn remove_from_group(&self, login: &str, group: &str) -> ChangedPrincipals;
|
||||
async fn remove_test_alias(&self, login: &str, alias: &str);
|
||||
|
|
@ -898,6 +899,28 @@ impl TestInternalDirectory for Store {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
async fn add_permissions(
|
||||
&self,
|
||||
login: &str,
|
||||
permissions: impl IntoIterator<Item = Permission>,
|
||||
) {
|
||||
self.update_principal(
|
||||
UpdatePrincipal::by_name(login).with_updates(
|
||||
permissions
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
PrincipalUpdate::add_item(
|
||||
PrincipalField::EnabledPermissions,
|
||||
PrincipalValue::String(p.name().to_string()),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn add_to_group(&self, login: &str, group: &str) -> ChangedPrincipals {
|
||||
self.update_principal(UpdatePrincipal::by_name(login).with_updates(vec![
|
||||
PrincipalUpdate::add_item(
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ pub async fn test(test: &WebDavTest) {
|
|||
.await
|
||||
.with_header(
|
||||
"dav",
|
||||
"1, 2, 3, access-control, extended-mkcol, calendar-access, addressbook",
|
||||
concat!(
|
||||
"1, 2, 3, access-control, extended-mkcol, ",
|
||||
"calendar-access, calendar-no-timezone, addressbook"
|
||||
),
|
||||
)
|
||||
.with_header(
|
||||
"allow",
|
||||
|
|
|
|||
|
|
@ -70,6 +70,28 @@ pub async fn test(test: &WebDavTest) {
|
|||
);
|
||||
client.validate_values(&hierarchy).await;
|
||||
|
||||
// Delete cache an resync
|
||||
test.clear_cache();
|
||||
let response = client
|
||||
.sync_collection(
|
||||
&user_base_path,
|
||||
prev_sync_token,
|
||||
Depth::Infinity,
|
||||
None,
|
||||
["D:getetag"],
|
||||
)
|
||||
.await;
|
||||
let sync_token = response.sync_token();
|
||||
let changed_hrefs = response.hrefs();
|
||||
assert_ne!(sync_token, prev_sync_token);
|
||||
assert_eq!(
|
||||
changed_hrefs,
|
||||
hierarchy.iter().map(|x| x.0.as_str()).collect::<Vec<_>>(),
|
||||
"lengths {} & {}",
|
||||
changed_hrefs.len(),
|
||||
hierarchy.len()
|
||||
);
|
||||
|
||||
// Copying and moving to the same or root containers is invalid
|
||||
for method in ["COPY", "MOVE"] {
|
||||
for destination in [
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ use common::{
|
|||
manager::boot::build_ipc,
|
||||
};
|
||||
use dav_proto::schema::property::{DavProperty, WebDavProperty};
|
||||
use directory::Permission;
|
||||
use groupware::{DavResourceName, cache::GroupwareCache};
|
||||
use http::HttpSessionManager;
|
||||
use hyper::{HeaderMap, Method, StatusCode, header::AUTHORIZATION};
|
||||
|
|
@ -201,6 +202,12 @@ async fn init_webdav_tests(store_id: &str, delete_if_exists: bool) -> WebDavTest
|
|||
*account,
|
||||
DummyWebDavClient::new(account_id, account, secret, email),
|
||||
);
|
||||
store
|
||||
.add_permissions(
|
||||
account,
|
||||
[Permission::DavPrincipalList, Permission::DavPrincipalSearch],
|
||||
)
|
||||
.await;
|
||||
if *account == "mike" {
|
||||
store.set_test_quota(account, 1024).await;
|
||||
}
|
||||
|
|
@ -232,8 +239,7 @@ impl WebDavTest {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn assert_is_empty(&self) {
|
||||
assert_is_empty(self.server.clone()).await;
|
||||
pub fn clear_cache(&self) {
|
||||
for cache in [
|
||||
&self.server.inner.cache.events,
|
||||
&self.server.inner.cache.contacts,
|
||||
|
|
@ -242,6 +248,11 @@ impl WebDavTest {
|
|||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn assert_is_empty(&self) {
|
||||
assert_is_empty(self.server.clone()).await;
|
||||
self.clear_cache();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue