From 6a7e839f352df7f9b979d53a4f50fad55eb94a3a Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Mon, 6 Jan 2025 17:28:40 +0100 Subject: [PATCH] Support read-only calendar store as preparation for birthday calendars --- crates/caldav/src/calendar/resource.rs | 80 ++++++++++++++-------- crates/caldav/src/principal/mod.rs | 5 +- crates/dav/src/privileges.rs | 10 +++ crates/store/src/calendar_store.rs | 2 + crates/store/src/contact_birthday_store.rs | 4 ++ crates/store_sqlite/src/calendar_store.rs | 4 ++ 6 files changed, 76 insertions(+), 29 deletions(-) diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs index 112d7ee..7c679a1 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -89,7 +89,16 @@ pub enum CalendarProp { } #[derive(Clone, Debug, From, Into)] -pub struct CalendarResource(Calendar); +pub struct CalendarResource { + pub cal: Calendar, + pub read_only: bool, +} + +impl From for Calendar { + fn from(value: CalendarResource) -> Self { + value.cal + } +} impl Resource for CalendarResource { type PropName = CalendarPropName; @@ -98,7 +107,7 @@ impl Resource for CalendarResource { type PrincipalResource = PrincipalResource; fn get_resourcetype(&self) -> Resourcetype { - if self.0.subscription_url.is_none() { + if self.cal.subscription_url.is_none() { Resourcetype(&[ ResourcetypeInner(rustical_dav::namespace::NS_DAV, "collection"), ResourcetypeInner(rustical_dav::namespace::NS_CALDAV, "calendar"), @@ -118,18 +127,20 @@ impl Resource for CalendarResource { prop: &Self::PropName, ) -> Result { Ok(match prop { - CalendarPropName::Displayname => CalendarProp::Displayname(self.0.displayname.clone()), - CalendarPropName::CalendarColor => CalendarProp::CalendarColor(self.0.color.clone()), + CalendarPropName::Displayname => { + CalendarProp::Displayname(self.cal.displayname.clone()) + } + CalendarPropName::CalendarColor => CalendarProp::CalendarColor(self.cal.color.clone()), CalendarPropName::CalendarDescription => { - CalendarProp::CalendarDescription(self.0.description.clone()) + CalendarProp::CalendarDescription(self.cal.description.clone()) } CalendarPropName::CalendarTimezone => { - CalendarProp::CalendarTimezone(self.0.timezone.clone()) + CalendarProp::CalendarTimezone(self.cal.timezone.clone()) } CalendarPropName::CalendarTimezoneId => { - CalendarProp::CalendarTimezoneId(self.0.timezone_id.clone()) + CalendarProp::CalendarTimezoneId(self.cal.timezone_id.clone()) } - CalendarPropName::CalendarOrder => CalendarProp::CalendarOrder(Some(self.0.order)), + CalendarPropName::CalendarOrder => CalendarProp::CalendarOrder(Some(self.cal.order)), CalendarPropName::SupportedCalendarComponentSet => { CalendarProp::SupportedCalendarComponentSet(SupportedCalendarComponentSet::default()) } @@ -142,7 +153,8 @@ impl Resource for CalendarResource { CalendarPropName::Transports => CalendarProp::Transports(Default::default()), CalendarPropName::Topic => { // TODO: Add salt since this could be public - let url = CalendarResource::get_url(rmap, [&self.0.principal, &self.0.id]).unwrap(); + let url = + CalendarResource::get_url(rmap, [&self.cal.principal, &self.cal.id]).unwrap(); let mut hasher = Sha256::new(); hasher.update(url); let topic = format!("{:x}", hasher.finalize()); @@ -152,39 +164,42 @@ impl Resource for CalendarResource { CalendarPropName::SupportedReportSet => { CalendarProp::SupportedReportSet(SupportedReportSet::default()) } - CalendarPropName::SyncToken => CalendarProp::SyncToken(self.0.format_synctoken()), - CalendarPropName::Getctag => CalendarProp::Getctag(self.0.format_synctoken()), + CalendarPropName::SyncToken => CalendarProp::SyncToken(self.cal.format_synctoken()), + CalendarPropName::Getctag => CalendarProp::Getctag(self.cal.format_synctoken()), CalendarPropName::Source => { - CalendarProp::Source(self.0.subscription_url.to_owned().map(HrefElement::from)) + CalendarProp::Source(self.cal.subscription_url.to_owned().map(HrefElement::from)) } }) } fn set_prop(&mut self, prop: Self::Prop) -> Result<(), rustical_dav::Error> { + if self.read_only { + return Err(rustical_dav::Error::PropReadOnly); + } match prop { CalendarProp::Displayname(displayname) => { - self.0.displayname = displayname; + self.cal.displayname = displayname; Ok(()) } CalendarProp::CalendarColor(color) => { - self.0.color = color; + self.cal.color = color; Ok(()) } CalendarProp::CalendarDescription(description) => { - self.0.description = description; + self.cal.description = description; Ok(()) } CalendarProp::CalendarTimezone(timezone) => { - self.0.timezone = timezone; + self.cal.timezone = timezone; Ok(()) } CalendarProp::CalendarTimezoneId(timezone_id) => { // TODO: Set or remove timezone accordingly - self.0.timezone_id = timezone_id; + self.cal.timezone_id = timezone_id; Ok(()) } CalendarProp::CalendarOrder(order) => { - self.0.order = order.unwrap_or_default(); + self.cal.order = order.unwrap_or_default(); Ok(()) } CalendarProp::SupportedCalendarComponentSet(_) => { @@ -204,29 +219,32 @@ impl Resource for CalendarResource { } fn remove_prop(&mut self, prop: &Self::PropName) -> Result<(), rustical_dav::Error> { + if self.read_only { + return Err(rustical_dav::Error::PropReadOnly); + } match prop { CalendarPropName::Displayname => { - self.0.displayname = None; + self.cal.displayname = None; Ok(()) } CalendarPropName::CalendarColor => { - self.0.color = None; + self.cal.color = None; Ok(()) } CalendarPropName::CalendarDescription => { - self.0.description = None; + self.cal.description = None; Ok(()) } CalendarPropName::CalendarTimezone => { - self.0.timezone = None; + self.cal.timezone = None; Ok(()) } CalendarPropName::CalendarTimezoneId => { - self.0.timezone_id = None; + self.cal.timezone_id = None; Ok(()) } CalendarPropName::CalendarOrder => { - self.0.order = 0; + self.cal.order = 0; Ok(()) } CalendarPropName::SupportedCalendarComponentSet => { @@ -251,12 +269,15 @@ impl Resource for CalendarResource { } fn get_owner(&self) -> Option<&str> { - Some(&self.0.principal) + Some(&self.cal.principal) } fn get_user_privileges(&self, user: &User) -> Result { - // TODO: read-only for subscription - Ok(UserPrivilegeSet::owner_only(self.0.principal == user.id)) + if self.cal.subscription_url.is_some() { + return Ok(UserPrivilegeSet::read_only()); + } + + Ok(UserPrivilegeSet::owner_only(self.cal.principal == user.id)) } } @@ -276,7 +297,10 @@ impl ResourceService for CalendarResourceService { .get_calendar(principal, cal_id) .await .map_err(|_e| Error::NotFound)?; - Ok(calendar.into()) + Ok(CalendarResource { + cal: calendar, + read_only: self.cal_store.is_read_only(), + }) } async fn get_members( diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index 4c23fd8..1e68ced 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -122,7 +122,10 @@ impl ResourceService for PrincipalResourceService .map(|cal| { ( CalendarResource::get_url(rmap, vec![principal, &cal.id]).unwrap(), - cal.into(), + CalendarResource { + cal, + read_only: self.cal_store.is_read_only(), + }, ) }) .collect()) diff --git a/crates/dav/src/privileges.rs b/crates/dav/src/privileges.rs index a309245..773f8f5 100644 --- a/crates/dav/src/privileges.rs +++ b/crates/dav/src/privileges.rs @@ -63,6 +63,16 @@ impl UserPrivilegeSet { Self::default() } } + + pub fn read_only() -> Self { + Self { + privileges: HashSet::from([ + UserPrivilege::Read, + UserPrivilege::ReadAcl, + UserPrivilege::ReadCurrentUserPrivilegeSet, + ]), + } + } } impl From<[UserPrivilege; N]> for UserPrivilegeSet { diff --git a/crates/store/src/calendar_store.rs b/crates/store/src/calendar_store.rs index 8664748..8028df6 100644 --- a/crates/store/src/calendar_store.rs +++ b/crates/store/src/calendar_store.rs @@ -61,4 +61,6 @@ pub trait CalendarStore: Send + Sync + 'static { cal_id: &str, object_id: &str, ) -> Result<(), Error>; + + fn is_read_only(&self) -> bool; } diff --git a/crates/store/src/contact_birthday_store.rs b/crates/store/src/contact_birthday_store.rs index 5dccccb..0399d0a 100644 --- a/crates/store/src/contact_birthday_store.rs +++ b/crates/store/src/contact_birthday_store.rs @@ -140,4 +140,8 @@ impl CalendarStore for ContactBirthdayStore { ) -> Result<(), Error> { Err(Error::ReadOnly) } + + fn is_read_only(&self) -> bool { + true + } } diff --git a/crates/store_sqlite/src/calendar_store.rs b/crates/store_sqlite/src/calendar_store.rs index 5d54678..067309d 100644 --- a/crates/store_sqlite/src/calendar_store.rs +++ b/crates/store_sqlite/src/calendar_store.rs @@ -379,4 +379,8 @@ impl CalendarStore for SqliteStore { Ok((objects, deleted_objects, new_synctoken)) } + + fn is_read_only(&self) -> bool { + false + } }