diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs index 58b5343..6a13ac6 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -292,7 +292,12 @@ impl Resource for CalendarResource { } fn get_user_privileges(&self, user: &Principal) -> Result { - if self.cal.subscription_url.is_some() || self.read_only { + if self.cal.subscription_url.is_some() { + return Ok(UserPrivilegeSet::owner_write_properties( + user.is_principal(&self.cal.principal), + )); + } + if self.read_only { return Ok(UserPrivilegeSet::owner_read( user.is_principal(&self.cal.principal), )); diff --git a/crates/dav/src/privileges.rs b/crates/dav/src/privileges.rs index 87c8eea..13a3249 100644 --- a/crates/dav/src/privileges.rs +++ b/crates/dav/src/privileges.rs @@ -2,6 +2,7 @@ use quick_xml::name::Namespace; use rustical_xml::{XmlDeserialize, XmlSerialize}; use std::collections::{HashMap, HashSet}; +// https://datatracker.ietf.org/doc/html/rfc3744 #[derive(Debug, Clone, XmlSerialize, XmlDeserialize, Eq, Hash, PartialEq)] pub enum UserPrivilege { Read, @@ -47,6 +48,12 @@ pub struct UserPrivilegeSet { impl UserPrivilegeSet { pub fn has(&self, privilege: &UserPrivilege) -> bool { + if (privilege == &UserPrivilege::WriteProperties + || privilege == &UserPrivilege::WriteContent) + && self.privileges.contains(&UserPrivilege::Write) + { + return true; + } self.privileges.contains(privilege) || self.privileges.contains(&UserPrivilege::All) } @@ -72,6 +79,15 @@ impl UserPrivilegeSet { } } + pub fn owner_write_properties(is_owner: bool) -> Self { + // Content is read-only but we can write properties + if is_owner { + Self::write_properties() + } else { + Self::default() + } + } + pub fn read_only() -> Self { Self { privileges: HashSet::from([ @@ -81,6 +97,17 @@ impl UserPrivilegeSet { ]), } } + + pub fn write_properties() -> Self { + Self { + privileges: HashSet::from([ + UserPrivilege::Read, + UserPrivilege::WriteProperties, + UserPrivilege::ReadAcl, + UserPrivilege::ReadCurrentUserPrivilegeSet, + ]), + } + } } impl From<[UserPrivilege; N]> for UserPrivilegeSet { diff --git a/crates/dav/src/resource/methods/delete.rs b/crates/dav/src/resource/methods/delete.rs index a091aa1..6e21464 100644 --- a/crates/dav/src/resource/methods/delete.rs +++ b/crates/dav/src/resource/methods/delete.rs @@ -47,8 +47,9 @@ pub async fn route_delete( ) -> Result<(), R::Error> { let resource = resource_service.get_resource(path_components).await?; + // Kind of a bodge since we don't get unbind from the parent let privileges = resource.get_user_privileges(principal)?; - if !privileges.has(&UserPrivilege::Write) { + if !privileges.has(&UserPrivilege::WriteProperties) { return Err(Error::Unauthorized.into()); }