diff --git a/crates/caldav/src/calendar/methods/report/calendar_multiget.rs b/crates/caldav/src/calendar/methods/report/calendar_multiget.rs index dada01a..3540db9 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_multiget.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_multiget.rs @@ -4,12 +4,11 @@ use crate::{ calendar_object::resource::{CalendarObjectPropWrapper, CalendarObjectResource}, }; use actix_web::{ - HttpRequest, dev::{Path, ResourceDef}, http::StatusCode, }; use rustical_dav::{ - resource::Resource, + resource::{PrincipalUri, Resource}, xml::{MultistatusElement, PropfindType, multistatus::ResponseElement}, }; use rustical_store::{CalendarObject, CalendarStore, auth::User}; @@ -58,25 +57,25 @@ pub async fn get_objects_calendar_multiget( pub async fn handle_calendar_multiget( cal_multiget: &CalendarMultigetRequest, props: &[&str], - req: HttpRequest, + path: &str, + puri: &impl PrincipalUri, user: &User, principal: &str, cal_id: &str, cal_store: &C, ) -> Result, Error> { let (objects, not_found) = - get_objects_calendar_multiget(cal_multiget, req.path(), principal, cal_id, cal_store) - .await?; + get_objects_calendar_multiget(cal_multiget, path, principal, cal_id, cal_store).await?; let mut responses = Vec::new(); for object in objects { - let path = format!("{}/{}.ics", req.path(), object.get_id()); + let path = format!("{}/{}.ics", path, object.get_id()); responses.push( CalendarObjectResource { object, principal: principal.to_owned(), } - .propfind(&path, props, user, req.resource_map())?, + .propfind(&path, props, puri, user)?, ); } diff --git a/crates/caldav/src/calendar/methods/report/calendar_query.rs b/crates/caldav/src/calendar/methods/report/calendar_query.rs index 916a14e..28c6a3e 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_query.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_query.rs @@ -1,6 +1,5 @@ -use actix_web::HttpRequest; use rustical_dav::{ - resource::Resource, + resource::{PrincipalUri, Resource}, xml::{MultistatusElement, PropfindType}, }; use rustical_ical::UtcDateTime; @@ -217,7 +216,8 @@ pub async fn get_objects_calendar_query( pub async fn handle_calendar_query( cal_query: &CalendarQueryRequest, props: &[&str], - req: HttpRequest, + path: &str, + puri: &impl PrincipalUri, user: &User, principal: &str, cal_id: &str, @@ -227,17 +227,13 @@ pub async fn handle_calendar_query( let mut responses = Vec::new(); for object in objects { - let path = format!( - "{}/{}.ics", - req.path().trim_end_matches('/'), - object.get_id() - ); + let path = format!("{}/{}.ics", path, object.get_id()); responses.push( CalendarObjectResource { object, principal: principal.to_owned(), } - .propfind(&path, props, user, req.resource_map())?, + .propfind(&path, props, puri, user)?, ); } diff --git a/crates/caldav/src/calendar/methods/report/mod.rs b/crates/caldav/src/calendar/methods/report/mod.rs index e7713da..5006da6 100644 --- a/crates/caldav/src/calendar/methods/report/mod.rs +++ b/crates/caldav/src/calendar/methods/report/mod.rs @@ -1,4 +1,4 @@ -use crate::Error; +use crate::{CalDavPrincipalUri, Error}; use actix_web::{ HttpRequest, Responder, web::{Data, Path}, @@ -87,6 +87,7 @@ pub async fn route_report_calendar( body: String, user: User, req: HttpRequest, + puri: Data, cal_store: Data, ) -> Result { let (principal, cal_id) = path.into_inner(); @@ -102,7 +103,8 @@ pub async fn route_report_calendar( handle_calendar_query( cal_query, &props, - req, + req.path(), + puri.as_ref(), &user, &principal, &cal_id, @@ -114,7 +116,8 @@ pub async fn route_report_calendar( handle_calendar_multiget( cal_multiget, &props, - req, + req.path(), + puri.as_ref(), &user, &principal, &cal_id, @@ -126,7 +129,8 @@ pub async fn route_report_calendar( handle_sync_collection( sync_collection, &props, - req, + req.path(), + puri.as_ref(), &user, &principal, &cal_id, diff --git a/crates/caldav/src/calendar/methods/report/sync_collection.rs b/crates/caldav/src/calendar/methods/report/sync_collection.rs index e7cef4d..7cecd54 100644 --- a/crates/caldav/src/calendar/methods/report/sync_collection.rs +++ b/crates/caldav/src/calendar/methods/report/sync_collection.rs @@ -3,9 +3,9 @@ use crate::{ Error, calendar_object::resource::{CalendarObjectPropWrapper, CalendarObjectResource}, }; -use actix_web::{HttpRequest, http::StatusCode}; +use actix_web::http::StatusCode; use rustical_dav::{ - resource::Resource, + resource::{PrincipalUri, Resource}, xml::{ MultistatusElement, multistatus::ResponseElement, sync_collection::SyncCollectionRequest, }, @@ -19,7 +19,8 @@ use rustical_store::{ pub async fn handle_sync_collection( sync_collection: &SyncCollectionRequest, props: &[&str], - req: HttpRequest, + path: &str, + puri: &impl PrincipalUri, user: &User, principal: &str, cal_id: &str, @@ -32,22 +33,18 @@ pub async fn handle_sync_collection( let mut responses = Vec::new(); for object in new_objects { - let path = format!( - "{}/{}.ics", - req.path().trim_end_matches('/'), - object.get_id() - ); + let path = format!("{}/{}.ics", path, object.get_id()); responses.push( CalendarObjectResource { object, principal: principal.to_owned(), } - .propfind(&path, props, user, req.resource_map())?, + .propfind(&path, props, puri, user)?, ); } for object_id in deleted_objects { - let path = format!("{}/{}.ics", req.path().trim_end_matches('/'), object_id); + let path = format!("{path}/{object_id}.ics"); 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 473ab39..6ecb958 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -2,10 +2,8 @@ use super::methods::mkcalendar::route_mkcalendar; use super::methods::post::route_post; use super::methods::report::route_report_calendar; use super::prop::{SupportedCalendarComponentSet, SupportedCalendarData, SupportedReportSet}; -use crate::Error; use crate::calendar_object::resource::CalendarObjectResource; -use crate::principal::PrincipalResource; -use actix_web::dev::ResourceMap; +use crate::{CalDavPrincipalUri, Error}; use actix_web::http::Method; use actix_web::web; use async_trait::async_trait; @@ -15,7 +13,7 @@ use rustical_dav::extensions::{ CommonPropertiesExtension, CommonPropertiesProp, SyncTokenExtension, SyncTokenExtensionProp, }; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{Resource, ResourceService}; +use rustical_dav::resource::{PrincipalUri, Resource, ResourceService}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_dav_push::{DavPushExtension, DavPushExtensionProp}; use rustical_ical::CalDateTime; @@ -100,10 +98,6 @@ impl DavPushExtension for CalendarResource { } } -impl CommonPropertiesExtension for CalendarResource { - type PrincipalResource = PrincipalResource; -} - impl Resource for CalendarResource { type Prop = CalendarPropWrapper; type Error = Error; @@ -128,7 +122,7 @@ impl Resource for CalendarResource { fn get_prop( &self, - rmap: &ResourceMap, + puri: &impl PrincipalUri, user: &User, prop: &CalendarPropWrapperName, ) -> Result { @@ -183,7 +177,7 @@ impl Resource for CalendarResource { CalendarPropWrapper::DavPush(DavPushExtension::get_prop(self, prop)?) } CalendarPropWrapperName::Common(prop) => CalendarPropWrapper::Common( - CommonPropertiesExtension::get_prop(self, rmap, user, prop)?, + CommonPropertiesExtension::get_prop(self, puri, user, prop)?, ), }) } @@ -336,6 +330,7 @@ impl ResourceService for CalendarResourc type Resource = CalendarResource; type Error = Error; type Principal = User; + type PrincipalUri = CalDavPrincipalUri; async fn get_resource( &self, diff --git a/crates/caldav/src/calendar_object/resource.rs b/crates/caldav/src/calendar_object/resource.rs index bdee6a8..2bc0974 100644 --- a/crates/caldav/src/calendar_object/resource.rs +++ b/crates/caldav/src/calendar_object/resource.rs @@ -1,12 +1,11 @@ use super::methods::{get_event, put_event}; -use crate::{Error, principal::PrincipalResource}; -use actix_web::dev::ResourceMap; +use crate::{CalDavPrincipalUri, Error}; use async_trait::async_trait; use derive_more::derive::{From, Into}; use rustical_dav::{ extensions::{CommonPropertiesExtension, CommonPropertiesProp}, privileges::UserPrivilegeSet, - resource::{Resource, ResourceService}, + resource::{PrincipalUri, Resource, ResourceService}, xml::Resourcetype, }; use rustical_store::{CalendarObject, CalendarStore, auth::User}; @@ -51,10 +50,6 @@ pub struct CalendarObjectResource { pub principal: String, } -impl CommonPropertiesExtension for CalendarObjectResource { - type PrincipalResource = PrincipalResource; -} - impl Resource for CalendarObjectResource { type Prop = CalendarObjectPropWrapper; type Error = Error; @@ -66,7 +61,7 @@ impl Resource for CalendarObjectResource { fn get_prop( &self, - rmap: &ResourceMap, + puri: &impl PrincipalUri, user: &User, prop: &CalendarObjectPropWrapperName, ) -> Result { @@ -85,7 +80,7 @@ impl Resource for CalendarObjectResource { }) } CalendarObjectPropWrapperName::Common(prop) => CalendarObjectPropWrapper::Common( - CommonPropertiesExtension::get_prop(self, rmap, user, prop)?, + CommonPropertiesExtension::get_prop(self, puri, user, prop)?, ), }) } @@ -119,6 +114,7 @@ impl ResourceService for CalendarObjectResourceService { type MemberType = CalendarObjectResource; type Error = Error; type Principal = User; + type PrincipalUri = CalDavPrincipalUri; async fn get_resource( &self, diff --git a/crates/caldav/src/calendar_set/mod.rs b/crates/caldav/src/calendar_set/mod.rs index c17b392..5156a6f 100644 --- a/crates/caldav/src/calendar_set/mod.rs +++ b/crates/caldav/src/calendar_set/mod.rs @@ -1,11 +1,9 @@ -use crate::Error; use crate::calendar::resource::CalendarResource; -use crate::principal::PrincipalResource; -use actix_web::dev::ResourceMap; +use crate::{CalDavPrincipalUri, Error}; use async_trait::async_trait; use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp}; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{Resource, ResourceService}; +use rustical_dav::resource::{PrincipalUri, Resource, ResourceService}; use rustical_dav::xml::{Resourcetype, ResourcetypeInner}; use rustical_store::CalendarStore; use rustical_store::auth::User; @@ -24,10 +22,6 @@ pub enum PrincipalPropWrapper { Common(CommonPropertiesProp), } -impl CommonPropertiesExtension for CalendarSetResource { - type PrincipalResource = PrincipalResource; -} - impl Resource for CalendarSetResource { type Prop = PrincipalPropWrapper; type Error = Error; @@ -42,13 +36,13 @@ impl Resource for CalendarSetResource { fn get_prop( &self, - rmap: &ResourceMap, + puri: &impl PrincipalUri, user: &User, prop: &PrincipalPropWrapperName, ) -> Result { Ok(match prop { PrincipalPropWrapperName::Common(prop) => PrincipalPropWrapper::Common( - ::get_prop(self, rmap, user, prop)?, + ::get_prop(self, puri, user, prop)?, ), }) } @@ -83,6 +77,7 @@ impl ResourceService for CalendarSetResourceService { type Resource = CalendarSetResource; type Error = Error; type Principal = User; + type PrincipalUri = CalDavPrincipalUri; async fn get_resource( &self, diff --git a/crates/caldav/src/lib.rs b/crates/caldav/src/lib.rs index 00a76fe..ab5deec 100644 --- a/crates/caldav/src/lib.rs +++ b/crates/caldav/src/lib.rs @@ -8,8 +8,9 @@ use actix_web::web::{self, Data}; use calendar::resource::CalendarResourceService; use calendar_object::resource::CalendarObjectResourceService; use calendar_set::CalendarSetResourceService; +use derive_more::Constructor; use principal::{PrincipalResource, PrincipalResourceService}; -use rustical_dav::resource::{NamedRoute, ResourceService, ResourceServiceRoute}; +use rustical_dav::resource::{PrincipalUri, ResourceService, ResourceServiceRoute}; use rustical_dav::resources::RootResourceService; use rustical_store::auth::{AuthenticationMiddleware, AuthenticationProvider, User}; use rustical_store::{AddressbookStore, CalendarStore, ContactBirthdayStore, SubscriptionStore}; @@ -25,6 +26,15 @@ mod subscription; pub use error::Error; +#[derive(Debug, Clone, Constructor)] +pub struct CalDavPrincipalUri(&'static str); + +impl PrincipalUri for CalDavPrincipalUri { + fn principal_uri(&self, principal: &str) -> String { + format!("{}/{}", self.0, principal) + } +} + /// Quite a janky implementation but the default METHOD_NOT_ALLOWED response gives us the allowed /// methods of a resource fn options_handler() -> ErrorHandlers { @@ -57,6 +67,7 @@ pub fn caldav_service< C: CalendarStore, S: SubscriptionStore, >( + prefix: &'static str, auth_provider: Arc, store: Arc, addr_store: Arc, @@ -70,7 +81,13 @@ pub fn caldav_service< .app_data(Data::from(store.clone())) .app_data(Data::from(birthday_store.clone())) .app_data(Data::from(subscription_store)) - .service(RootResourceService::::default().actix_resource()) + .app_data(Data::new(CalDavPrincipalUri::new( + format!("{prefix}/principal").leak(), + ))) + .service( + RootResourceService::::default() + .actix_resource(), + ) .service( web::scope("/principal").service( web::scope("/{principal}") @@ -79,8 +96,7 @@ pub fn caldav_service< auth_provider, home_set: &[("calendar", false), ("birthdays", true)], } - .actix_resource() - .name(PrincipalResource::route_name()), + .actix_resource(), ) .service( web::scope("/calendar") diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index f51b374..f8725ac 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -1,16 +1,14 @@ -use std::sync::Arc; - -use crate::Error; use crate::calendar_set::CalendarSetResource; -use actix_web::dev::ResourceMap; +use crate::{CalDavPrincipalUri, Error}; use async_trait::async_trait; use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp}; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{NamedRoute, Resource, ResourceService}; +use rustical_dav::resource::{PrincipalUri, Resource, ResourceService}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_store::auth::user::PrincipalType; use rustical_store::auth::{AuthenticationProvider, User}; use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; +use std::sync::Arc; #[derive(Clone)] pub struct PrincipalResource { @@ -49,22 +47,6 @@ pub enum PrincipalPropWrapper { Common(CommonPropertiesProp), } -impl PrincipalResource { - pub fn get_principal_url(rmap: &ResourceMap, principal: &str) -> String { - Self::get_url(rmap, vec![principal]).unwrap() - } -} - -impl NamedRoute for PrincipalResource { - fn route_name() -> &'static str { - "caldav_principal" - } -} - -impl CommonPropertiesExtension for PrincipalResource { - type PrincipalResource = Self; -} - impl Resource for PrincipalResource { type Prop = PrincipalPropWrapper; type Error = Error; @@ -79,16 +61,16 @@ impl Resource for PrincipalResource { fn get_prop( &self, - rmap: &ResourceMap, + puri: &impl PrincipalUri, user: &User, prop: &PrincipalPropWrapperName, ) -> Result { - let principal_url = Self::get_url(rmap, vec![&self.principal.id]).unwrap(); + let principal_url = puri.principal_uri(&self.principal.id); let home_set = CalendarHomeSet( user.memberships() .into_iter() - .map(|principal| Self::get_url(rmap, vec![principal]).unwrap()) + .map(|principal| puri.principal_uri(principal)) .flat_map(|principal_url| { self.home_set.iter().map(move |&(home_name, _read_only)| { HrefElement::new(format!("{}/{}", &principal_url, home_name)) @@ -119,7 +101,7 @@ impl Resource for PrincipalResource { }) } PrincipalPropWrapperName::Common(prop) => PrincipalPropWrapper::Common( - ::get_prop(self, rmap, user, prop)?, + ::get_prop(self, puri, user, prop)?, ), }) } @@ -147,6 +129,7 @@ impl ResourceService for PrincipalResourceService Result { @@ -81,7 +76,7 @@ impl Resource for AddressObjectResource { }) } AddressObjectPropWrapperName::Common(prop) => AddressObjectPropWrapper::Common( - CommonPropertiesExtension::get_prop(self, rmap, user, prop)?, + CommonPropertiesExtension::get_prop(self, puri, user, prop)?, ), }) } @@ -115,6 +110,7 @@ impl ResourceService for AddressObjectResourceService type MemberType = AddressObjectResource; type Error = Error; type Principal = User; + type PrincipalUri = CardDavPrincipalUri; async fn get_resource( &self, diff --git a/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs b/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs index 3724cd0..c01f2eb 100644 --- a/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs +++ b/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs @@ -3,12 +3,11 @@ use crate::{ address_object::resource::{AddressObjectPropWrapper, AddressObjectResource}, }; use actix_web::{ - HttpRequest, dev::{Path, ResourceDef}, http::StatusCode, }; use rustical_dav::{ - resource::Resource, + resource::{PrincipalUri, Resource}, xml::{MultistatusElement, PropfindType, multistatus::ResponseElement}, }; use rustical_store::{AddressObject, AddressbookStore, auth::User}; @@ -60,25 +59,26 @@ pub async fn get_objects_addressbook_multiget( pub async fn handle_addressbook_multiget( addr_multiget: &AddressbookMultigetRequest, props: &[&str], - req: HttpRequest, + path: &str, + puri: &impl PrincipalUri, user: &User, principal: &str, cal_id: &str, addr_store: &AS, ) -> Result, Error> { let (objects, not_found) = - get_objects_addressbook_multiget(addr_multiget, req.path(), principal, cal_id, addr_store) + get_objects_addressbook_multiget(addr_multiget, path, principal, cal_id, addr_store) .await?; let mut responses = Vec::new(); for object in objects { - let path = format!("{}/{}.vcf", req.path(), object.get_id()); + let path = format!("{}/{}.vcf", path, object.get_id()); responses.push( AddressObjectResource { object, principal: principal.to_owned(), } - .propfind(&path, props, user, req.resource_map())?, + .propfind(&path, props, puri, user)?, ); } diff --git a/crates/carddav/src/addressbook/methods/report/mod.rs b/crates/carddav/src/addressbook/methods/report/mod.rs index c9a888a..0afbb09 100644 --- a/crates/carddav/src/addressbook/methods/report/mod.rs +++ b/crates/carddav/src/addressbook/methods/report/mod.rs @@ -1,4 +1,4 @@ -use crate::Error; +use crate::{CardDavPrincipalUri, Error}; use actix_web::{ HttpRequest, Responder, web::{Data, Path}, @@ -49,6 +49,7 @@ pub async fn route_report_addressbook( body: String, user: User, req: HttpRequest, + puri: Data, addr_store: Data, ) -> Result { let (principal, addressbook_id) = path.into_inner(); @@ -64,7 +65,8 @@ pub async fn route_report_addressbook( handle_addressbook_multiget( addr_multiget, &props, - req, + req.path(), + puri.as_ref(), &user, &principal, &addressbook_id, @@ -76,7 +78,8 @@ pub async fn route_report_addressbook( handle_sync_collection( sync_collection, &props, - req, + req.path(), + puri.as_ref(), &user, &principal, &addressbook_id, diff --git a/crates/carddav/src/addressbook/methods/report/sync_collection.rs b/crates/carddav/src/addressbook/methods/report/sync_collection.rs index 2244b77..6f51b4a 100644 --- a/crates/carddav/src/addressbook/methods/report/sync_collection.rs +++ b/crates/carddav/src/addressbook/methods/report/sync_collection.rs @@ -2,9 +2,9 @@ use crate::{ Error, address_object::resource::{AddressObjectPropWrapper, AddressObjectResource}, }; -use actix_web::{HttpRequest, http::StatusCode}; +use actix_web::http::StatusCode; use rustical_dav::{ - resource::Resource, + resource::{PrincipalUri, Resource}, xml::{ MultistatusElement, multistatus::ResponseElement, sync_collection::SyncCollectionRequest, }, @@ -18,7 +18,8 @@ use rustical_store::{ pub async fn handle_sync_collection( sync_collection: &SyncCollectionRequest, props: &[&str], - req: HttpRequest, + path: &str, + puri: &impl PrincipalUri, user: &User, principal: &str, addressbook_id: &str, @@ -31,22 +32,18 @@ pub async fn handle_sync_collection( let mut responses = Vec::new(); for object in new_objects { - let path = format!( - "{}/{}.vcf", - req.path().trim_end_matches('/'), - object.get_id() - ); + let path = format!("{}/{}.vcf", path.trim_end_matches('/'), object.get_id()); responses.push( AddressObjectResource { object, principal: principal.to_owned(), } - .propfind(&path, props, user, req.resource_map())?, + .propfind(&path, props, puri, user)?, ); } for object_id in deleted_objects { - let path = format!("{}/{}.vcf", req.path().trim_end_matches('/'), object_id); + let path = format!("{}/{}.vcf", 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 f805ff5..2cf7ece 100644 --- a/crates/carddav/src/addressbook/resource.rs +++ b/crates/carddav/src/addressbook/resource.rs @@ -2,10 +2,8 @@ use super::methods::mkcol::route_mkcol; use super::methods::post::route_post; use super::methods::report::route_report_addressbook; use super::prop::{SupportedAddressData, SupportedReportSet}; -use crate::Error; use crate::address_object::resource::AddressObjectResource; -use crate::principal::PrincipalResource; -use actix_web::dev::ResourceMap; +use crate::{CardDavPrincipalUri, Error}; use actix_web::http::Method; use actix_web::web; use async_trait::async_trait; @@ -14,7 +12,7 @@ use rustical_dav::extensions::{ CommonPropertiesExtension, CommonPropertiesProp, SyncTokenExtension, SyncTokenExtensionProp, }; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{Resource, ResourceService}; +use rustical_dav::resource::{PrincipalUri, Resource, ResourceService}; use rustical_dav::xml::{Resourcetype, ResourcetypeInner}; use rustical_dav_push::{DavPushExtension, DavPushExtensionProp}; use rustical_store::auth::User; @@ -80,10 +78,6 @@ impl DavPushExtension for AddressbookResource { } } -impl CommonPropertiesExtension for AddressbookResource { - type PrincipalResource = PrincipalResource; -} - impl Resource for AddressbookResource { type Prop = AddressbookPropWrapper; type Error = Error; @@ -98,7 +92,7 @@ impl Resource for AddressbookResource { fn get_prop( &self, - rmap: &ResourceMap, + puri: &impl PrincipalUri, user: &User, prop: &AddressbookPropWrapperName, ) -> Result { @@ -130,7 +124,7 @@ impl Resource for AddressbookResource { AddressbookPropWrapper::DavPush(::get_prop(self, prop)?) } AddressbookPropWrapperName::Common(prop) => AddressbookPropWrapper::Common( - CommonPropertiesExtension::get_prop(self, rmap, user, prop)?, + CommonPropertiesExtension::get_prop(self, puri, user, prop)?, ), }) } @@ -204,6 +198,7 @@ impl ResourceService type Resource = AddressbookResource; type Error = Error; type Principal = User; + type PrincipalUri = CardDavPrincipalUri; async fn get_resource( &self, diff --git a/crates/carddav/src/lib.rs b/crates/carddav/src/lib.rs index 879d5bd..2ad1839 100644 --- a/crates/carddav/src/lib.rs +++ b/crates/carddav/src/lib.rs @@ -11,9 +11,10 @@ use actix_web::{ }; use address_object::resource::AddressObjectResourceService; use addressbook::resource::AddressbookResourceService; +use derive_more::Constructor; pub use error::Error; use principal::{PrincipalResource, PrincipalResourceService}; -use rustical_dav::resource::{NamedRoute, ResourceService}; +use rustical_dav::resource::{PrincipalUri, ResourceService}; use rustical_dav::resources::RootResourceService; use rustical_store::{ AddressbookStore, SubscriptionStore, @@ -26,6 +27,15 @@ pub mod addressbook; pub mod error; pub mod principal; +#[derive(Debug, Clone, Constructor)] +pub struct CardDavPrincipalUri(&'static str); + +impl PrincipalUri for CardDavPrincipalUri { + fn principal_uri(&self, principal: &str) -> String { + format!("{}/{}", self.0, principal) + } +} + /// Quite a janky implementation but the default METHOD_NOT_ALLOWED response gives us the allowed /// methods of a resource fn options_handler() -> ErrorHandlers { @@ -53,6 +63,7 @@ fn options_handler() -> ErrorHandlers { } pub fn carddav_service( + prefix: &'static str, auth_provider: Arc, store: Arc, subscription_store: Arc, @@ -62,14 +73,19 @@ pub fn carddav_service::default().actix_resource()) + .app_data(Data::new(CardDavPrincipalUri::new( + format!("{prefix}/principal").leak(), + ))) + .service( + RootResourceService::::default() + .actix_resource(), + ) .service( web::scope("/principal").service( web::scope("/{principal}") .service( PrincipalResourceService::new(store.clone(), auth_provider) - .actix_resource() - .name(PrincipalResource::route_name()), + .actix_resource(), ) .service( web::scope("/{addressbook_id}") diff --git a/crates/carddav/src/principal/mod.rs b/crates/carddav/src/principal/mod.rs index 92f2630..83757cf 100644 --- a/crates/carddav/src/principal/mod.rs +++ b/crates/carddav/src/principal/mod.rs @@ -1,10 +1,9 @@ -use crate::Error; use crate::addressbook::resource::AddressbookResource; -use actix_web::dev::ResourceMap; +use crate::{CardDavPrincipalUri, Error}; use async_trait::async_trait; use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp}; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{NamedRoute, Resource, ResourceService}; +use rustical_dav::resource::{PrincipalUri, Resource, ResourceService}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_store::AddressbookStore; use rustical_store::auth::{AuthenticationProvider, User}; @@ -58,22 +57,6 @@ pub enum PrincipalPropWrapper { Common(CommonPropertiesProp), } -impl PrincipalResource { - pub fn get_principal_url(rmap: &ResourceMap, principal: &str) -> String { - Self::get_url(rmap, vec![principal]).unwrap() - } -} - -impl NamedRoute for PrincipalResource { - fn route_name() -> &'static str { - "carddav_principal" - } -} - -impl CommonPropertiesExtension for PrincipalResource { - type PrincipalResource = Self; -} - impl Resource for PrincipalResource { type Prop = PrincipalPropWrapper; type Error = Error; @@ -88,16 +71,16 @@ impl Resource for PrincipalResource { fn get_prop( &self, - rmap: &ResourceMap, + puri: &impl PrincipalUri, user: &User, prop: &PrincipalPropWrapperName, ) -> Result { - let principal_href = HrefElement::new(Self::get_principal_url(rmap, &self.principal.id)); + let principal_href = HrefElement::new(puri.principal_uri(&user.id)); let home_set = AddressbookHomeSet( user.memberships() .into_iter() - .map(|principal| Self::get_url(rmap, vec![principal]).unwrap()) + .map(|principal| puri.principal_uri(principal)) .map(HrefElement::new) .collect(), ); @@ -120,7 +103,7 @@ impl Resource for PrincipalResource { } PrincipalPropWrapperName::Common(prop) => PrincipalPropWrapper::Common( - CommonPropertiesExtension::get_prop(self, rmap, user, prop)?, + CommonPropertiesExtension::get_prop(self, puri, user, prop)?, ), }) } @@ -145,6 +128,7 @@ impl ResourceService type Resource = PrincipalResource; type Error = Error; type Principal = User; + type PrincipalUri = CardDavPrincipalUri; async fn get_resource( &self, diff --git a/crates/dav/src/extensions/common.rs b/crates/dav/src/extensions/common.rs index 0e9d176..399b599 100644 --- a/crates/dav/src/extensions/common.rs +++ b/crates/dav/src/extensions/common.rs @@ -1,10 +1,9 @@ use crate::{ Principal, privileges::UserPrivilegeSet, - resource::{NamedRoute, Resource}, + resource::{PrincipalUri, Resource}, xml::{HrefElement, Resourcetype}, }; -use actix_web::dev::ResourceMap; use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; #[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumUnitVariants, EnumVariants)] @@ -28,11 +27,9 @@ pub enum CommonPropertiesProp { } pub trait CommonPropertiesExtension: Resource { - type PrincipalResource: NamedRoute; - fn get_prop( &self, - rmap: &ResourceMap, + principal_uri: &impl PrincipalUri, principal: &Self::Principal, prop: &CommonPropertiesPropName, ) -> Result::Error> { @@ -42,21 +39,16 @@ pub trait CommonPropertiesExtension: Resource { } CommonPropertiesPropName::CurrentUserPrincipal => { CommonPropertiesProp::CurrentUserPrincipal( - Self::PrincipalResource::get_url(rmap, [&principal.get_id()]) - .unwrap() - .into(), + principal_uri.principal_uri(principal.get_id()).into(), ) } CommonPropertiesPropName::CurrentUserPrivilegeSet => { CommonPropertiesProp::CurrentUserPrivilegeSet(self.get_user_privileges(principal)?) } - CommonPropertiesPropName::Owner => { - CommonPropertiesProp::Owner(self.get_owner().map(|owner| { - Self::PrincipalResource::get_url(rmap, [owner]) - .unwrap() - .into() - })) - } + CommonPropertiesPropName::Owner => CommonPropertiesProp::Owner( + self.get_owner() + .map(|owner| principal_uri.principal_uri(owner).into()), + ), }) } @@ -68,3 +60,5 @@ pub trait CommonPropertiesExtension: Resource { Err(crate::Error::PropReadOnly) } } + +impl CommonPropertiesExtension for R {} diff --git a/crates/dav/src/resource/methods/propfind.rs b/crates/dav/src/resource/methods/propfind.rs index 9ede9bb..743f068 100644 --- a/crates/dav/src/resource/methods/propfind.rs +++ b/crates/dav/src/resource/methods/propfind.rs @@ -14,7 +14,7 @@ use rustical_xml::XmlDocument; use tracing::instrument; use tracing_actix_web::RootSpan; -#[instrument(parent = root_span.id(), skip(path, req, root_span, resource_service))] +#[instrument(parent = root_span.id(), skip(path, req, root_span, resource_service, puri))] #[allow(clippy::type_complexity)] pub(crate) async fn route_propfind( path: Path, @@ -24,6 +24,7 @@ pub(crate) async fn route_propfind( depth: Depth, root_span: RootSpan, resource_service: Data, + puri: Data, ) -> Result< MultistatusElement<::Prop, ::Prop>, R::Error, @@ -61,13 +62,13 @@ pub(crate) async fn route_propfind( member_responses.push(member.propfind( &format!("{}/{}", req.path().trim_end_matches('/'), subpath), &props, + puri.as_ref(), &user, - req.resource_map(), )?); } } - let response = resource.propfind(req.path(), &props, &user, req.resource_map())?; + let response = resource.propfind(req.path(), &props, puri.as_ref(), &user)?; Ok(MultistatusElement { responses: vec![response], diff --git a/crates/dav/src/resource/mod.rs b/crates/dav/src/resource/mod.rs index 2e15228..c31aefe 100644 --- a/crates/dav/src/resource/mod.rs +++ b/crates/dav/src/resource/mod.rs @@ -3,7 +3,6 @@ use crate::xml::Resourcetype; use crate::xml::multistatus::{PropTagWrapper, PropstatElement, PropstatWrapper}; use crate::xml::{TagList, multistatus::ResponseElement}; use crate::{Error, Principal}; -use actix_web::dev::ResourceMap; use actix_web::http::header::{EntityTag, IfMatch, IfNoneMatch}; use actix_web::{ResponseError, http::StatusCode}; use itertools::Itertools; @@ -13,8 +12,10 @@ use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize} use std::str::FromStr; mod methods; +mod principal_uri; mod resource_service; +pub use principal_uri::PrincipalUri; pub use resource_service::*; pub trait ResourceProp: XmlSerialize + XmlDeserialize {} @@ -36,7 +37,7 @@ pub trait Resource: Clone + 'static { fn get_prop( &self, - rmap: &ResourceMap, + principal_uri: &impl PrincipalUri, principal: &Self::Principal, prop: &::UnitVariants, ) -> Result; @@ -101,8 +102,8 @@ pub trait Resource: Clone + 'static { &self, path: &str, props: &[&str], + principal_uri: &impl PrincipalUri, principal: &Self::Principal, - rmap: &ResourceMap, ) -> Result, Self::Error> { let mut props = props.to_vec(); @@ -154,7 +155,7 @@ pub trait Resource: Clone + 'static { let prop_responses = valid_props .into_iter() - .map(|prop| self.get_prop(rmap, principal, &prop)) + .map(|prop| self.get_prop(principal_uri, principal, &prop)) .collect::, Self::Error>>()?; let mut propstats = vec![PropstatWrapper::Normal(PropstatElement { diff --git a/crates/dav/src/resource/principal_uri.rs b/crates/dav/src/resource/principal_uri.rs new file mode 100644 index 0000000..2cb5a74 --- /dev/null +++ b/crates/dav/src/resource/principal_uri.rs @@ -0,0 +1,3 @@ +pub trait PrincipalUri: 'static { + fn principal_uri(&self, principal: &str) -> String; +} diff --git a/crates/dav/src/resource/resource_service.rs b/crates/dav/src/resource/resource_service.rs index 3a774d7..0a796b5 100644 --- a/crates/dav/src/resource/resource_service.rs +++ b/crates/dav/src/resource/resource_service.rs @@ -1,17 +1,13 @@ +use super::methods::{route_delete, route_propfind, route_proppatch}; +use super::{PrincipalUri, Resource}; +use crate::Principal; use actix_web::dev::{AppService, HttpServiceFactory}; -use actix_web::error::UrlGenerationError; -use actix_web::test::TestRequest; use actix_web::web::Data; -use actix_web::{ResponseError, dev::ResourceMap, http::Method, web}; +use actix_web::{ResponseError, http::Method, web}; use async_trait::async_trait; use serde::Deserialize; use std::str::FromStr; -use crate::Principal; - -use super::Resource; -use super::methods::{route_delete, route_propfind, route_proppatch}; - #[async_trait(?Send)] pub trait ResourceService: Sized + 'static { type MemberType: Resource; @@ -19,6 +15,7 @@ pub trait ResourceService: Sized + 'static { type Resource: Resource; type Error: ResponseError + From; type Principal: Principal; + type PrincipalUri: PrincipalUri; async fn get_members( &self, @@ -31,6 +28,7 @@ pub trait ResourceService: Sized + 'static { &self, _path: &Self::PathComponents, ) -> Result; + async fn save_resource( &self, _path: &Self::PathComponents, @@ -38,6 +36,7 @@ pub trait ResourceService: Sized + 'static { ) -> Result<(), Self::Error> { Err(crate::Error::Unauthorized.into()) } + async fn delete_resource( &self, _path: &Self::PathComponents, @@ -68,25 +67,6 @@ pub trait ResourceService: Sized + 'static { } } -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 { diff --git a/crates/dav/src/resources/root.rs b/crates/dav/src/resources/root.rs index 845b7a4..81664f1 100644 --- a/crates/dav/src/resources/root.rs +++ b/crates/dav/src/resources/root.rs @@ -3,9 +3,8 @@ use crate::extensions::{ CommonPropertiesExtension, CommonPropertiesProp, CommonPropertiesPropName, }; use crate::privileges::UserPrivilegeSet; -use crate::resource::{NamedRoute, Resource, ResourceService}; +use crate::resource::{PrincipalUri, Resource, ResourceService}; use crate::xml::{Resourcetype, ResourcetypeInner}; -use actix_web::dev::ResourceMap; use async_trait::async_trait; use std::marker::PhantomData; @@ -18,11 +17,7 @@ impl Default for RootResource { } } -impl CommonPropertiesExtension for RootResource { - type PrincipalResource = PR; -} - -impl Resource for RootResource { +impl Resource for RootResource { type Prop = CommonPropertiesProp; type Error = PR::Error; type Principal = P; @@ -36,11 +31,11 @@ impl Resource for RootResource { fn get_prop( &self, - rmap: &ResourceMap, + principal_uri: &impl PrincipalUri, user: &P, prop: &CommonPropertiesPropName, ) -> Result { - CommonPropertiesExtension::get_prop(self, rmap, user, prop) + CommonPropertiesExtension::get_prop(self, principal_uri, user, prop) } fn get_user_privileges(&self, _user: &P) -> Result { @@ -49,23 +44,28 @@ impl Resource for RootResource { } #[derive(Clone)] -pub struct RootResourceService(PhantomData, PhantomData

); +pub struct RootResourceService( + PhantomData, + PhantomData

, + PhantomData, +); -impl Default for RootResourceService { +impl Default for RootResourceService { fn default() -> Self { - Self(PhantomData, PhantomData) + Self(PhantomData, PhantomData, PhantomData) } } #[async_trait(?Send)] -impl + NamedRoute, P: Principal> ResourceService - for RootResourceService +impl, P: Principal, PURI: PrincipalUri> ResourceService + for RootResourceService { type PathComponents = (); type MemberType = PR; type Resource = RootResource; type Error = PR::Error; type Principal = P; + type PrincipalUri = PURI; async fn get_resource(&self, _: &()) -> Result { Ok(RootResource::::default()) diff --git a/src/app.rs b/src/app.rs index ef229ec..25e0034 100644 --- a/src/app.rs +++ b/src/app.rs @@ -38,12 +38,14 @@ pub fn make_app( .wrap(TracingLogger::default()) .wrap(NormalizePath::trim()) .service(web::scope("/caldav").service(caldav_service( + "/caldav", auth_provider.clone(), cal_store.clone(), addr_store.clone(), subscription_store.clone(), ))) .service(web::scope("/carddav").service(carddav_service( + "/carddav", auth_provider.clone(), addr_store.clone(), subscription_store,