From b9af6c10a38a6aaea209b6f63b9f678f33869416 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Sun, 12 Jan 2025 16:04:38 +0100 Subject: [PATCH] birthday calendar, lots of refactoring --- .../methods/report/calendar_multiget.rs | 12 +- .../calendar/methods/report/calendar_query.rs | 9 +- .../methods/report/sync_collection.rs | 12 +- crates/caldav/src/calendar/resource.rs | 42 +++---- crates/caldav/src/calendar_object/resource.rs | 5 - crates/caldav/src/calendar_set/mod.rs | 114 ++++++++++++++++++ crates/caldav/src/lib.rs | 54 ++++++--- crates/caldav/src/principal/mod.rs | 70 +++++------ crates/carddav/src/address_object/resource.rs | 5 - .../methods/report/addressbook_multiget.rs | 21 +--- .../methods/report/sync_collection.rs | 12 +- crates/carddav/src/addressbook/resource.rs | 12 +- crates/carddav/src/lib.rs | 8 +- crates/carddav/src/principal/mod.rs | 21 ++-- crates/dav/src/resource/methods/propfind.rs | 12 +- crates/dav/src/resource/mod.rs | 23 +--- crates/dav/src/resource/resource_service.rs | 42 +++++-- crates/dav/src/resources/root.rs | 13 +- src/app.rs | 7 +- 19 files changed, 284 insertions(+), 210 deletions(-) create mode 100644 crates/caldav/src/calendar_set/mod.rs diff --git a/crates/caldav/src/calendar/methods/report/calendar_multiget.rs b/crates/caldav/src/calendar/methods/report/calendar_multiget.rs index 7f3dbb5..aaf0778 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_multiget.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_multiget.rs @@ -1,6 +1,5 @@ use crate::{ calendar_object::resource::{CalendarObjectProp, CalendarObjectResource}, - principal::PrincipalResource, Error, }; use actix_web::{ @@ -27,13 +26,12 @@ pub(crate) struct CalendarMultigetRequest { pub async fn get_objects_calendar_multiget( cal_query: &CalendarMultigetRequest, - principal_url: &str, + path: &str, principal: &str, cal_id: &str, store: &C, ) -> Result<(Vec, Vec), Error> { - let resource_def = - ResourceDef::prefix(principal_url).join(&ResourceDef::new("/{cal_id}/{object_id}")); + let resource_def = ResourceDef::prefix(path).join(&ResourceDef::new("/{object_id}")); let mut result = vec![]; let mut not_found = vec![]; @@ -43,9 +41,6 @@ pub async fn get_objects_calendar_multiget( if !resource_def.capture_match_info(&mut path) { not_found.push(href.to_owned()); }; - if path.get("cal_id").unwrap() != cal_id { - not_found.push(href.to_owned()); - } let object_id = path.get("object_id").unwrap(); match store.get_object(principal, cal_id, object_id).await { Ok(object) => result.push(object), @@ -66,9 +61,8 @@ pub async fn handle_calendar_multiget( cal_store: &C, ) -> Result, String>, Error> { - let principal_url = PrincipalResource::get_url(req.resource_map(), vec![principal]).unwrap(); let (objects, not_found) = - get_objects_calendar_multiget(&cal_multiget, &principal_url, principal, cal_id, cal_store) + get_objects_calendar_multiget(&cal_multiget, req.path(), principal, cal_id, cal_store) .await?; let props = match cal_multiget.prop { diff --git a/crates/caldav/src/calendar/methods/report/calendar_query.rs b/crates/caldav/src/calendar/methods/report/calendar_query.rs index c833a7a..b1764b7 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_query.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_query.rs @@ -110,8 +110,7 @@ impl CompFilterElement { if self .comp_filter .iter() - .map(|filter| filter.matches(cal_object)) - .all(|x| x) + .all(|filter| filter.matches(cal_object)) { return true; } @@ -215,11 +214,7 @@ pub async fn handle_calendar_query( let mut responses = Vec::new(); for object in objects { - let path = CalendarObjectResource::get_url( - req.resource_map(), - vec![principal, cal_id, object.get_id()], - ) - .unwrap(); + let path = format!("{}/{}", req.path().trim_end_matches('/'), object.get_id()); responses.push( CalendarObjectResource { object, diff --git a/crates/caldav/src/calendar/methods/report/sync_collection.rs b/crates/caldav/src/calendar/methods/report/sync_collection.rs index 016f1b2..08c3fbd 100644 --- a/crates/caldav/src/calendar/methods/report/sync_collection.rs +++ b/crates/caldav/src/calendar/methods/report/sync_collection.rs @@ -86,11 +86,7 @@ pub async fn handle_sync_collection( let mut responses = Vec::new(); for object in new_objects { - let path = CalendarObjectResource::get_url( - req.resource_map(), - vec![principal, cal_id, &object.get_id()], - ) - .unwrap(); + let path = format!("{}/{}", req.path().trim_end_matches('/'), object.get_id()); responses.push( CalendarObjectResource { object, @@ -101,11 +97,7 @@ pub async fn handle_sync_collection( } for object_id in deleted_objects { - let path = CalendarObjectResource::get_url( - req.resource_map(), - vec![principal, cal_id, &object_id], - ) - .unwrap(); + let path = format!("{}/{}", req.path().trim_end_matches('/'), object_id); responses.push(ResponseElement { href: path, status: Some(StatusCode::NOT_FOUND), diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs index 7c679a1..cf7733d 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -22,16 +22,6 @@ use std::str::FromStr; use std::sync::Arc; use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; -pub struct CalendarResourceService { - cal_store: Arc, -} - -impl CalendarResourceService { - pub fn new(cal_store: Arc) -> Self { - Self { cal_store } - } -} - #[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] #[strum_discriminants( name(CalendarPropName), @@ -122,7 +112,7 @@ impl Resource for CalendarResource { fn get_prop( &self, - rmap: &ResourceMap, + _rmap: &ResourceMap, _user: &User, prop: &Self::PropName, ) -> Result { @@ -153,8 +143,9 @@ 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.cal.principal, &self.cal.id]).unwrap(); + // let url = + // CalendarResource::get_url(rmap, [&self.cal.principal, &self.cal.id]).unwrap(); + let url = "TODO!".to_owned(); let mut hasher = Sha256::new(); hasher.update(url); let topic = format!("{:x}", hasher.finalize()); @@ -263,11 +254,6 @@ impl Resource for CalendarResource { } } - #[inline] - fn resource_name() -> &'static str { - "caldav_calendar" - } - fn get_owner(&self) -> Option<&str> { Some(&self.cal.principal) } @@ -281,6 +267,16 @@ impl Resource for CalendarResource { } } +pub struct CalendarResourceService { + cal_store: Arc, +} + +impl CalendarResourceService { + pub fn new(cal_store: Arc) -> Self { + Self { cal_store } + } +} + #[async_trait(?Send)] impl ResourceService for CalendarResourceService { type MemberType = CalendarObjectResource; @@ -292,11 +288,7 @@ impl ResourceService for CalendarResourceService { &self, (principal, cal_id): &Self::PathComponents, ) -> Result { - let calendar = self - .cal_store - .get_calendar(principal, cal_id) - .await - .map_err(|_e| Error::NotFound)?; + let calendar = self.cal_store.get_calendar(principal, cal_id).await?; Ok(CalendarResource { cal: calendar, read_only: self.cal_store.is_read_only(), @@ -306,7 +298,6 @@ impl ResourceService for CalendarResourceService { async fn get_members( &self, (principal, cal_id): &Self::PathComponents, - rmap: &ResourceMap, ) -> Result, Self::Error> { Ok(self .cal_store @@ -315,8 +306,7 @@ impl ResourceService for CalendarResourceService { .into_iter() .map(|object| { ( - CalendarObjectResource::get_url(rmap, vec![principal, cal_id, object.get_id()]) - .unwrap(), + object.get_id().to_string(), CalendarObjectResource { object, principal: principal.to_owned(), diff --git a/crates/caldav/src/calendar_object/resource.rs b/crates/caldav/src/calendar_object/resource.rs index 8f273ca..bbf0442 100644 --- a/crates/caldav/src/calendar_object/resource.rs +++ b/crates/caldav/src/calendar_object/resource.rs @@ -75,11 +75,6 @@ impl Resource for CalendarObjectResource { }) } - #[inline] - fn resource_name() -> &'static str { - "caldav_calendar_object" - } - fn get_owner(&self) -> Option<&str> { Some(&self.principal) } diff --git a/crates/caldav/src/calendar_set/mod.rs b/crates/caldav/src/calendar_set/mod.rs new file mode 100644 index 0000000..2f7acee --- /dev/null +++ b/crates/caldav/src/calendar_set/mod.rs @@ -0,0 +1,114 @@ +use crate::calendar::resource::CalendarResource; +use crate::principal::PrincipalResource; +use crate::Error; +use actix_web::dev::ResourceMap; +use async_trait::async_trait; +use rustical_dav::privileges::UserPrivilegeSet; +use rustical_dav::resource::{NamedRoute, Resource, ResourceService}; +use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; +use rustical_store::auth::User; +use rustical_store::CalendarStore; +use rustical_xml::{XmlDeserialize, XmlSerialize}; +use std::sync::Arc; +use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; + +#[derive(Clone)] +pub struct CalendarSetResource { + pub(crate) principal: String, +} + +#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] +#[strum_discriminants( + name(PrincipalPropName), + derive(EnumString, VariantNames, IntoStaticStr), + strum(serialize_all = "kebab-case") +)] +pub enum PrincipalProp { + // WebDAV Access Control (RFC 3744) + #[strum_discriminants(strum(serialize = "principal-URL"))] + #[xml(ns = "rustical_dav::namespace::NS_DAV")] + PrincipalUrl(HrefElement), +} + +impl Resource for CalendarSetResource { + type PropName = PrincipalPropName; + type Prop = PrincipalProp; + type Error = Error; + type PrincipalResource = PrincipalResource; + + fn get_resourcetype(&self) -> Resourcetype { + Resourcetype(&[ResourcetypeInner( + rustical_dav::namespace::NS_DAV, + "collection", + )]) + } + + fn get_prop( + &self, + rmap: &ResourceMap, + _user: &User, + prop: &Self::PropName, + ) -> Result { + let principal_href = HrefElement::new( + Self::PrincipalResource::get_url(rmap, vec![&self.principal]).unwrap(), + ); + + Ok(match prop { + PrincipalPropName::PrincipalUrl => PrincipalProp::PrincipalUrl(principal_href), + }) + } + + fn get_owner(&self) -> Option<&str> { + Some(&self.principal) + } + + fn get_user_privileges(&self, user: &User) -> Result { + Ok(UserPrivilegeSet::owner_only(self.principal == user.id)) + } +} + +pub struct CalendarSetResourceService { + cal_store: Arc, +} + +impl CalendarSetResourceService { + pub fn new(cal_store: Arc) -> Self { + Self { cal_store } + } +} + +#[async_trait(?Send)] +impl ResourceService for CalendarSetResourceService { + type PathComponents = (String,); + type MemberType = CalendarResource; + type Resource = CalendarSetResource; + type Error = Error; + + async fn get_resource( + &self, + (principal,): &Self::PathComponents, + ) -> Result { + Ok(CalendarSetResource { + principal: principal.to_owned(), + }) + } + + async fn get_members( + &self, + (principal,): &Self::PathComponents, + ) -> Result, Self::Error> { + let calendars = self.cal_store.get_calendars(principal).await?; + Ok(calendars + .into_iter() + .map(|cal| { + ( + cal.id.to_owned(), + CalendarResource { + cal, + read_only: self.cal_store.is_read_only(), + }, + ) + }) + .collect()) + } +} diff --git a/crates/caldav/src/lib.rs b/crates/caldav/src/lib.rs index 9747471..d59374c 100644 --- a/crates/caldav/src/lib.rs +++ b/crates/caldav/src/lib.rs @@ -6,15 +6,17 @@ use actix_web::web::{self, Data}; use actix_web::HttpResponse; use calendar::resource::CalendarResourceService; use calendar_object::resource::CalendarObjectResourceService; +use calendar_set::CalendarSetResourceService; use principal::{PrincipalResource, PrincipalResourceService}; -use rustical_dav::resource::ResourceService; +use rustical_dav::resource::{NamedRoute, ResourceService, ResourceServiceRoute}; use rustical_dav::resources::RootResourceService; use rustical_store::auth::{AuthenticationMiddleware, AuthenticationProvider}; -use rustical_store::CalendarStore; +use rustical_store::{AddressbookStore, CalendarStore, ContactBirthdayStore}; use std::sync::Arc; pub mod calendar; pub mod calendar_object; +pub mod calendar_set; pub mod error; pub mod principal; @@ -24,11 +26,17 @@ pub fn configure_well_known(cfg: &mut web::ServiceConfig, caldav_root: String) { cfg.service(web::redirect("/caldav", caldav_root).permanent()); } -pub fn configure_dav( +pub fn configure_dav< + AP: AuthenticationProvider, + AS: AddressbookStore + ?Sized, + C: CalendarStore + ?Sized, +>( cfg: &mut web::ServiceConfig, auth_provider: Arc, store: Arc, + addr_store: Arc, ) { + let birthday_store = Arc::new(ContactBirthdayStore::new(addr_store)); cfg.service( web::scope("") .wrap(AuthenticationMiddleware::new(auth_provider)) @@ -53,24 +61,36 @@ pub fn configure_dav( }), ) .app_data(Data::from(store.clone())) + .app_data(Data::from(birthday_store.clone())) .service(RootResourceService::::default().actix_resource()) .service( web::scope("/user").service( web::scope("/{principal}") - .service(PrincipalResourceService::::new(store.clone()).actix_resource()) - .service( - web::scope("/{calendar}") - .service( - CalendarResourceService::::new(store.clone()) - .actix_resource(), - ) - .service( - web::scope("/{object}").service( - CalendarObjectResourceService::::new(store.clone()) - .actix_resource(), - ), - ), - ), + .service(PrincipalResourceService(&[ + "calendar", "birthdays" + ]).actix_resource().name(PrincipalResource::route_name())) + .service(web::scope("/calendar") + .service(CalendarSetResourceService::new(store.clone()).actix_resource()) + .service( + web::scope("/{calendar}") + .service( + ResourceServiceRoute(CalendarResourceService::new(store.clone())) + ) + .service(web::scope("/{object}").service(CalendarObjectResourceService::new(store.clone()).actix_resource() + )) + ) + ) + .service(web::scope("/birthdays") + .service(CalendarSetResourceService::new(birthday_store.clone()).actix_resource()) + .service( + web::scope("/{calendar}") + .service( + ResourceServiceRoute(CalendarResourceService::new(birthday_store.clone())) + ) + .service(web::scope("/{object}").service(CalendarObjectResourceService::new(birthday_store.clone()).actix_resource() + )) + ) + ) ), ), ); diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index 1e68ced..25f8406 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -1,31 +1,23 @@ -use crate::calendar::resource::CalendarResource; +use crate::calendar_set::CalendarSetResource; use crate::Error; use actix_web::dev::ResourceMap; use async_trait::async_trait; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{Resource, ResourceService}; +use rustical_dav::resource::{NamedRoute, Resource, ResourceService}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_store::auth::User; -use rustical_store::CalendarStore; use rustical_xml::{XmlDeserialize, XmlSerialize}; -use std::sync::Arc; use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; -pub struct PrincipalResourceService { - cal_store: Arc, -} - -impl PrincipalResourceService { - pub fn new(cal_store: Arc) -> Self { - Self { cal_store } - } -} - #[derive(Clone)] pub struct PrincipalResource { principal: String, + home_set: &'static [&'static str], } +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone)] +pub struct CalendarHomeSet(#[xml(ty = "untagged", flatten)] Vec); + #[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] #[strum_discriminants( name(PrincipalPropName), @@ -40,7 +32,7 @@ pub enum PrincipalProp { // CalDAV (RFC 4791) #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] - CalendarHomeSet(HrefElement), + CalendarHomeSet(CalendarHomeSet), #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] CalendarUserAddressSet(HrefElement), } @@ -51,6 +43,12 @@ impl PrincipalResource { } } +impl NamedRoute for PrincipalResource { + fn route_name() -> &'static str { + "caldav_principal" + } +} + impl Resource for PrincipalResource { type PropName = PrincipalPropName; type Prop = PrincipalProp; @@ -70,22 +68,23 @@ impl Resource for PrincipalResource { _user: &User, prop: &Self::PropName, ) -> Result { - let principal_href = HrefElement::new(Self::get_url(rmap, vec![&self.principal]).unwrap()); + let principal_url = Self::get_url(rmap, vec![&self.principal]).unwrap(); + let home_set = CalendarHomeSet( + self.home_set + .iter() + .map(|&home_name| format!("{}/{}", principal_url, home_name).into()) + .collect(), + ); Ok(match prop { - PrincipalPropName::PrincipalUrl => PrincipalProp::PrincipalUrl(principal_href), - PrincipalPropName::CalendarHomeSet => PrincipalProp::CalendarHomeSet(principal_href), + PrincipalPropName::PrincipalUrl => PrincipalProp::PrincipalUrl(principal_url.into()), + PrincipalPropName::CalendarHomeSet => PrincipalProp::CalendarHomeSet(home_set), PrincipalPropName::CalendarUserAddressSet => { - PrincipalProp::CalendarUserAddressSet(principal_href) + PrincipalProp::CalendarUserAddressSet(principal_url.into()) } }) } - #[inline] - fn resource_name() -> &'static str { - "caldav_principal" - } - fn get_owner(&self) -> Option<&str> { Some(&self.principal) } @@ -95,10 +94,12 @@ impl Resource for PrincipalResource { } } +pub struct PrincipalResourceService(pub &'static [&'static str]); + #[async_trait(?Send)] -impl ResourceService for PrincipalResourceService { +impl ResourceService for PrincipalResourceService { type PathComponents = (String,); - type MemberType = CalendarResource; + type MemberType = CalendarSetResource; type Resource = PrincipalResource; type Error = Error; @@ -108,23 +109,22 @@ impl ResourceService for PrincipalResourceService ) -> Result { Ok(PrincipalResource { principal: principal.to_owned(), + home_set: self.0, }) } async fn get_members( &self, (principal,): &Self::PathComponents, - rmap: &ResourceMap, ) -> Result, Self::Error> { - let calendars = self.cal_store.get_calendars(principal).await?; - Ok(calendars - .into_iter() - .map(|cal| { + Ok(self + .0 + .iter() + .map(|&set_name| { ( - CalendarResource::get_url(rmap, vec![principal, &cal.id]).unwrap(), - CalendarResource { - cal, - read_only: self.cal_store.is_read_only(), + set_name.to_string(), + CalendarSetResource { + principal: principal.to_owned(), }, ) }) diff --git a/crates/carddav/src/address_object/resource.rs b/crates/carddav/src/address_object/resource.rs index f23b4d8..71af2a8 100644 --- a/crates/carddav/src/address_object/resource.rs +++ b/crates/carddav/src/address_object/resource.rs @@ -76,11 +76,6 @@ impl Resource for AddressObjectResource { }) } - #[inline] - fn resource_name() -> &'static str { - "carddav_address_object" - } - fn get_owner(&self) -> Option<&str> { Some(&self.principal) } diff --git a/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs b/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs index 44233c2..29b8e01 100644 --- a/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs +++ b/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs @@ -1,6 +1,5 @@ use crate::{ address_object::resource::{AddressObjectProp, AddressObjectResource}, - principal::PrincipalResource, Error, }; use actix_web::{ @@ -10,8 +9,7 @@ use actix_web::{ }; use rustical_dav::{ resource::{CommonPropertiesProp, EitherProp, Resource}, - xml::{multistatus::ResponseElement, MultistatusElement}, - xml::{PropElement, PropfindType}, + xml::{multistatus::ResponseElement, MultistatusElement, PropElement, PropfindType}, }; use rustical_store::{auth::User, AddressObject, AddressbookStore}; use rustical_xml::XmlDeserialize; @@ -27,13 +25,12 @@ pub struct AddressbookMultigetRequest { pub async fn get_objects_addressbook_multiget( addressbook_multiget: &AddressbookMultigetRequest, - principal_url: &str, + path: &str, principal: &str, addressbook_id: &str, store: &AS, ) -> Result<(Vec, Vec), Error> { - let resource_def = - ResourceDef::prefix(principal_url).join(&ResourceDef::new("/{addressbook_id}/{object_id}")); + let resource_def = ResourceDef::prefix(path).join(&ResourceDef::new("/{object_id}")); let mut result = vec![]; let mut not_found = vec![]; @@ -67,15 +64,9 @@ pub async fn handle_addressbook_multiget( addr_store: &AS, ) -> Result, String>, Error> { - let principal_url = PrincipalResource::get_url(req.resource_map(), vec![principal]).unwrap(); - let (objects, not_found) = get_objects_addressbook_multiget( - &addr_multiget, - &principal_url, - principal, - cal_id, - addr_store, - ) - .await?; + let (objects, not_found) = + get_objects_addressbook_multiget(&addr_multiget, req.path(), principal, cal_id, addr_store) + .await?; let props = match addr_multiget.prop { PropfindType::Allprop => { diff --git a/crates/carddav/src/addressbook/methods/report/sync_collection.rs b/crates/carddav/src/addressbook/methods/report/sync_collection.rs index f22bea8..2bec1a7 100644 --- a/crates/carddav/src/addressbook/methods/report/sync_collection.rs +++ b/crates/carddav/src/addressbook/methods/report/sync_collection.rs @@ -83,11 +83,7 @@ pub async fn handle_sync_collection( let mut responses = Vec::new(); for object in new_objects { - let path = AddressObjectResource::get_url( - req.resource_map(), - vec![principal, addressbook_id, &object.get_id()], - ) - .unwrap(); + let path = format!("{}/{}", req.path().trim_end_matches('/'), object.get_id()); responses.push( AddressObjectResource { object, @@ -98,11 +94,7 @@ pub async fn handle_sync_collection( } for object_id in deleted_objects { - let path = AddressObjectResource::get_url( - req.resource_map(), - vec![principal, addressbook_id, &object_id], - ) - .unwrap(); + let path = format!("{}/{}", req.path().trim_end_matches('/'), object_id); responses.push(ResponseElement { href: path, status: Some(StatusCode::NOT_FOUND), diff --git a/crates/carddav/src/addressbook/resource.rs b/crates/carddav/src/addressbook/resource.rs index cf79803..5845da7 100644 --- a/crates/carddav/src/addressbook/resource.rs +++ b/crates/carddav/src/addressbook/resource.rs @@ -143,11 +143,6 @@ impl Resource for AddressbookResource { } } - #[inline] - fn resource_name() -> &'static str { - "carddav_addressbook" - } - fn get_owner(&self) -> Option<&str> { Some(&self.0.principal) } @@ -179,7 +174,6 @@ impl ResourceService for AddressbookResourceServi async fn get_members( &self, (principal, addressbook_id): &Self::PathComponents, - rmap: &ResourceMap, ) -> Result, Self::Error> { Ok(self .addr_store @@ -188,11 +182,7 @@ impl ResourceService for AddressbookResourceServi .into_iter() .map(|object| { ( - AddressObjectResource::get_url( - rmap, - vec![principal, addressbook_id, object.get_id()], - ) - .unwrap(), + object.get_id().to_string(), AddressObjectResource { object, principal: principal.to_owned(), diff --git a/crates/carddav/src/lib.rs b/crates/carddav/src/lib.rs index 9e73707..1d69f17 100644 --- a/crates/carddav/src/lib.rs +++ b/crates/carddav/src/lib.rs @@ -12,7 +12,7 @@ use address_object::resource::AddressObjectResourceService; use addressbook::resource::AddressbookResourceService; pub use error::Error; use principal::{PrincipalResource, PrincipalResourceService}; -use rustical_dav::resource::ResourceService; +use rustical_dav::resource::{NamedRoute, ResourceService}; use rustical_dav::resources::RootResourceService; use rustical_store::{ auth::{AuthenticationMiddleware, AuthenticationProvider}, @@ -62,7 +62,11 @@ pub fn configure_dav( .service( web::scope("/user").service( web::scope("/{principal}") - .service(PrincipalResourceService::::new(store.clone()).actix_resource()) + .service( + PrincipalResourceService::new(store.clone()) + .actix_resource() + .name(PrincipalResource::route_name()), + ) .service( web::scope("/{addressbook}") .service( diff --git a/crates/carddav/src/principal/mod.rs b/crates/carddav/src/principal/mod.rs index 0689fc0..9b51f5e 100644 --- a/crates/carddav/src/principal/mod.rs +++ b/crates/carddav/src/principal/mod.rs @@ -3,7 +3,7 @@ use crate::Error; use actix_web::dev::ResourceMap; use async_trait::async_trait; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{Resource, ResourceService}; +use rustical_dav::resource::{NamedRoute, Resource, ResourceService}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_store::auth::User; use rustical_store::AddressbookStore; @@ -52,6 +52,12 @@ impl PrincipalResource { } } +impl NamedRoute for PrincipalResource { + fn route_name() -> &'static str { + "carddav_principal" + } +} + impl Resource for PrincipalResource { type PropName = PrincipalPropName; type Prop = PrincipalProp; @@ -82,11 +88,6 @@ impl Resource for PrincipalResource { }) } - #[inline] - fn resource_name() -> &'static str { - "carddav_principal" - } - fn get_owner(&self) -> Option<&str> { Some(&self.principal) } @@ -115,17 +116,11 @@ impl ResourceService for PrincipalResourceService< async fn get_members( &self, (principal,): &Self::PathComponents, - rmap: &ResourceMap, ) -> Result, Self::Error> { let addressbooks = self.addr_store.get_addressbooks(principal).await?; Ok(addressbooks .into_iter() - .map(|addressbook| { - ( - AddressbookResource::get_url(rmap, vec![principal, &addressbook.id]).unwrap(), - addressbook.into(), - ) - }) + .map(|addressbook| (addressbook.id.to_owned(), addressbook.into())) .collect()) } } diff --git a/crates/dav/src/resource/methods/propfind.rs b/crates/dav/src/resource/methods/propfind.rs index 959dd6f..47fd520 100644 --- a/crates/dav/src/resource/methods/propfind.rs +++ b/crates/dav/src/resource/methods/propfind.rs @@ -61,11 +61,13 @@ pub(crate) async fn route_propfind( let mut member_responses = Vec::new(); if depth != Depth::Zero { - for (path, member) in resource_service - .get_members(&path, req.resource_map()) - .await? - { - member_responses.push(member.propfind(&path, &props, &user, req.resource_map())?); + for (subpath, member) in resource_service.get_members(&path).await? { + member_responses.push(member.propfind( + &format!("{}/{}", req.path().trim_end_matches('/'), subpath), + &props, + &user, + req.resource_map(), + )?); } } diff --git a/crates/dav/src/resource/mod.rs b/crates/dav/src/resource/mod.rs index a26ff69..bf9de61 100644 --- a/crates/dav/src/resource/mod.rs +++ b/crates/dav/src/resource/mod.rs @@ -4,8 +4,6 @@ use crate::xml::{multistatus::ResponseElement, TagList}; use crate::xml::{HrefElement, Resourcetype}; use crate::Error; use actix_web::dev::ResourceMap; -use actix_web::error::UrlGenerationError; -use actix_web::test::TestRequest; use actix_web::{http::StatusCode, ResponseError}; use itertools::Itertools; pub use resource_service::ResourceService; @@ -17,6 +15,8 @@ use strum::{EnumString, VariantNames}; mod methods; mod resource_service; +pub use resource_service::*; + pub trait ResourceProp: XmlSerialize + XmlDeserialize {} impl ResourceProp for T {} @@ -62,7 +62,7 @@ pub trait Resource: Clone + 'static { type PropName: ResourcePropName + From + Into<&'static str>; type Prop: ResourceProp + PartialEq + Clone; type Error: ResponseError + From; - type PrincipalResource: Resource; + type PrincipalResource: Resource + NamedRoute; fn get_resourcetype(&self) -> Resourcetype; @@ -115,27 +115,10 @@ pub trait Resource: Clone + 'static { Err(crate::Error::PropReadOnly) } - fn resource_name() -> &'static str; - fn get_owner(&self) -> Option<&str> { None } - fn get_url(rmap: &ResourceMap, elements: U) -> Result - where - U: IntoIterator, - I: AsRef, - { - Ok(rmap - .url_for( - &TestRequest::default().to_http_request(), - Self::resource_name(), - elements, - )? - .path() - .to_owned()) - } - fn get_user_privileges(&self, user: &User) -> Result; fn propfind( diff --git a/crates/dav/src/resource/resource_service.rs b/crates/dav/src/resource/resource_service.rs index 2cf98f2..8cd854f 100644 --- a/crates/dav/src/resource/resource_service.rs +++ b/crates/dav/src/resource/resource_service.rs @@ -1,9 +1,11 @@ -use std::str::FromStr; - +use actix_web::dev::{AppService, HttpServiceFactory}; +use actix_web::error::UrlGenerationError; +use actix_web::test::TestRequest; use actix_web::web::Data; use actix_web::{dev::ResourceMap, http::Method, web, ResponseError}; use async_trait::async_trait; use serde::Deserialize; +use std::str::FromStr; use super::methods::{route_delete, route_propfind, route_proppatch}; use super::Resource; @@ -17,8 +19,7 @@ pub trait ResourceService: Sized + 'static { async fn get_members( &self, - _path: &Self::PathComponents, - _rmap: &ResourceMap, + _path_components: &Self::PathComponents, ) -> Result, Self::Error> { Ok(vec![]) } @@ -42,17 +43,11 @@ pub trait ResourceService: Sized + 'static { Err(crate::Error::Unauthorized.into()) } - #[inline] - fn resource_name() -> &'static str { - Self::Resource::resource_name() - } - #[inline] fn actix_resource(self) -> actix_web::Resource { Self::actix_additional_routes( web::resource("") .app_data(Data::new(self)) - .name(Self::resource_name()) .route( web::method(Method::from_str("PROPFIND").unwrap()).to(route_propfind::), ) @@ -69,3 +64,30 @@ pub trait ResourceService: Sized + 'static { res } } + +pub trait NamedRoute { + fn route_name() -> &'static str; + + fn get_url(rmap: &ResourceMap, elements: U) -> Result + where + U: IntoIterator, + I: AsRef, + { + Ok(rmap + .url_for( + &TestRequest::default().to_http_request(), + Self::route_name(), + elements, + )? + .path() + .to_owned()) + } +} + +pub struct ResourceServiceRoute(pub RS); + +impl HttpServiceFactory for ResourceServiceRoute { + fn register(self, config: &mut AppService) { + self.0.actix_resource().register(config); + } +} diff --git a/crates/dav/src/resources/root.rs b/crates/dav/src/resources/root.rs index 5e083de..24da4d6 100644 --- a/crates/dav/src/resources/root.rs +++ b/crates/dav/src/resources/root.rs @@ -1,12 +1,11 @@ use crate::privileges::UserPrivilegeSet; -use crate::resource::{Resource, ResourceService}; +use crate::resource::{NamedRoute, Resource, ResourceService}; use crate::xml::{Resourcetype, ResourcetypeInner}; use actix_web::dev::ResourceMap; use async_trait::async_trait; use rustical_store::auth::User; use rustical_xml::{XmlDeserialize, XmlSerialize}; use serde::Serialize; -use std::any::type_name; use std::marker::PhantomData; use strum::{EnumString, IntoStaticStr, VariantNames}; @@ -32,7 +31,7 @@ impl From for RootResourcePropName { } } -impl Resource for RootResource { +impl Resource for RootResource { type PropName = RootResourcePropName; type Prop = RootResourceProp; type Error = PR::Error; @@ -51,16 +50,12 @@ impl Resource for RootResource { unreachable!("we shouldn't end up here") } - #[inline] - fn resource_name() -> &'static str { - type_name::() - } - fn get_user_privileges(&self, _user: &User) -> Result { Ok(UserPrivilegeSet::all()) } } +#[derive(Clone)] pub struct RootResourceService(PhantomData); impl Default for RootResourceService { @@ -70,7 +65,7 @@ impl Default for RootResourceService { } #[async_trait(?Send)] -impl ResourceService for RootResourceService { +impl ResourceService for RootResourceService { type PathComponents = (); type MemberType = PR; type Resource = RootResource; diff --git a/src/app.rs b/src/app.rs index ce14db2..058febf 100644 --- a/src/app.rs +++ b/src/app.rs @@ -27,7 +27,12 @@ pub fn make_app( .wrap(TracingLogger::default()) .wrap(NormalizePath::trim()) .service(web::scope("/caldav").configure(|cfg| { - rustical_caldav::configure_dav(cfg, auth_provider.clone(), cal_store.clone()) + rustical_caldav::configure_dav( + cfg, + auth_provider.clone(), + cal_store.clone(), + addr_store.clone(), + ) })) .service(web::scope("/carddav").configure(|cfg| { rustical_carddav::configure_dav(cfg, auth_provider.clone(), addr_store.clone())