From a47d056df08268d34a8239aaaff56b7ae84e105a Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Thu, 3 Oct 2024 23:02:13 +0200 Subject: [PATCH] Massive refactoring how DAV resources and routes work --- Cargo.lock | 1 + crates/caldav/Cargo.toml | 1 + .../methods/report/calendar_multiget.rs | 4 +- .../calendar/methods/report/calendar_query.rs | 11 +- .../caldav/src/calendar/methods/report/mod.rs | 12 +- .../methods/report/sync_collection.rs | 15 +- crates/caldav/src/calendar/resource.rs | 54 ++++--- crates/caldav/src/calendar_object/resource.rs | 13 +- crates/caldav/src/lib.rs | 69 +++------ crates/caldav/src/principal/mod.rs | 29 +++- crates/caldav/src/root/mod.rs | 15 +- crates/dav/src/methods/propfind.rs | 17 ++- crates/dav/src/resource.rs | 135 +++++++++++------- crates/store/src/auth/middleware.rs | 1 - src/app.rs | 3 +- 15 files changed, 218 insertions(+), 162 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 662b739..5610ef0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1965,6 +1965,7 @@ dependencies = [ "strum", "thiserror", "tokio", + "url", ] [[package]] diff --git a/crates/caldav/Cargo.toml b/crates/caldav/Cargo.toml index 5eaef4a..a03b29d 100644 --- a/crates/caldav/Cargo.toml +++ b/crates/caldav/Cargo.toml @@ -24,3 +24,4 @@ async-trait = "0.1" thiserror = "1.0" strum = { version = "0.26", features = ["strum_macros", "derive"] } derive_more = { version = "1.0", features = ["from", "into"] } +url = "2.5.2" diff --git a/crates/caldav/src/calendar/methods/report/calendar_multiget.rs b/crates/caldav/src/calendar/methods/report/calendar_multiget.rs index 57d6c3a..3bc727f 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_multiget.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_multiget.rs @@ -8,7 +8,7 @@ use actix_web::{ }; use rustical_dav::{ methods::propfind::{PropElement, PropfindType}, - resource::HandlePropfind, + resource::Resource, xml::{multistatus::PropstatWrapper, MultistatusElement}, }; use rustical_store::{model::object::CalendarObject, CalendarStore}; @@ -88,7 +88,7 @@ pub async fn handle_calendar_multiget( let path = format!("{}/{}", req.path(), object.get_uid()); responses.push( CalendarObjectResource::from(object) - .propfind(prefix, &path, props.clone()) + .propfind(&path, props.clone(), req.resource_map()) .await?, ); } diff --git a/crates/caldav/src/calendar/methods/report/calendar_query.rs b/crates/caldav/src/calendar/methods/report/calendar_query.rs index 617b3ec..398eb0f 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_query.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_query.rs @@ -1,7 +1,7 @@ use actix_web::HttpRequest; use rustical_dav::{ methods::propfind::{PropElement, PropfindType}, - resource::HandlePropfind, + resource::Resource, xml::{multistatus::PropstatWrapper, MultistatusElement}, }; use rustical_store::{model::object::CalendarObject, CalendarStore}; @@ -176,7 +176,6 @@ pub async fn get_objects_calendar_query( pub async fn handle_calendar_query( cal_query: CalendarQueryRequest, req: HttpRequest, - prefix: &str, principal: &str, cid: &str, cal_store: &RwLock, @@ -197,10 +196,14 @@ pub async fn handle_calendar_query( let mut responses = Vec::new(); for object in objects { - let path = format!("{}/{}", req.path(), object.get_uid()); + let path = CalendarObjectResource::get_url( + req.resource_map(), + vec![principal, cid, object.get_uid()], + ) + .unwrap(); responses.push( CalendarObjectResource::from(object) - .propfind(prefix, &path, props.clone()) + .propfind(&path, props.clone(), req.resource_map()) .await?, ); } diff --git a/crates/caldav/src/calendar/methods/report/mod.rs b/crates/caldav/src/calendar/methods/report/mod.rs index 2f21c3c..53fb0f4 100644 --- a/crates/caldav/src/calendar/methods/report/mod.rs +++ b/crates/caldav/src/calendar/methods/report/mod.rs @@ -51,22 +51,14 @@ pub async fn route_report_calendar( Ok(match request.clone() { ReportRequest::CalendarQuery(cal_query) => { - handle_calendar_query(cal_query, req, &prefix, &principal, &cid, &cal_store).await? + handle_calendar_query(cal_query, req, &principal, &cid, &cal_store).await? } ReportRequest::CalendarMultiget(cal_multiget) => { handle_calendar_multiget(cal_multiget, req, &prefix, &principal, &cid, &cal_store) .await? } ReportRequest::SyncCollection(sync_collection) => { - handle_sync_collection( - sync_collection, - req, - &prefix.0, - &principal, - &cid, - &cal_store, - ) - .await? + handle_sync_collection(sync_collection, req, &principal, &cid, &cal_store).await? } }) } diff --git a/crates/caldav/src/calendar/methods/report/sync_collection.rs b/crates/caldav/src/calendar/methods/report/sync_collection.rs index 0295a5b..e69fb4b 100644 --- a/crates/caldav/src/calendar/methods/report/sync_collection.rs +++ b/crates/caldav/src/calendar/methods/report/sync_collection.rs @@ -1,7 +1,7 @@ use actix_web::{http::StatusCode, HttpRequest}; use rustical_dav::{ methods::propfind::{PropElement, PropfindType}, - resource::HandlePropfind, + resource::Resource, xml::{ multistatus::{PropstatWrapper, ResponseElement}, MultistatusElement, @@ -45,7 +45,6 @@ pub struct SyncCollectionRequest { pub async fn handle_sync_collection( sync_collection: SyncCollectionRequest, req: HttpRequest, - prefix: &str, principal: &str, cid: &str, cal_store: &RwLock, @@ -71,16 +70,22 @@ pub async fn handle_sync_collection( let mut responses = Vec::new(); for object in new_objects { - let path = format!("{}/{}", req.path(), object.get_uid()); + let path = CalendarObjectResource::get_url( + req.resource_map(), + vec![principal, cid, &object.get_uid()], + ) + .unwrap(); responses.push( CalendarObjectResource::from(object) - .propfind(prefix, &path, props.clone()) + .propfind(&path, props.clone(), req.resource_map()) .await?, ); } for object_uid in deleted_objects { - let path = format!("{}/{}", req.path(), object_uid); + let path = + CalendarObjectResource::get_url(req.resource_map(), vec![principal, cid, &object_uid]) + .unwrap(); responses.push(ResponseElement { href: path, status: Some(format!("HTTP/1.1 {}", StatusCode::NOT_FOUND)), diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs index cac5ef2..9be4347 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -1,5 +1,11 @@ +use super::prop::{ + Resourcetype, SupportedCalendarComponent, SupportedCalendarComponentSet, SupportedCalendarData, + SupportedReportSet, UserPrivilegeSet, +}; use crate::calendar_object::resource::CalendarObjectResource; +use crate::principal::PrincipalResource; use crate::Error; +use actix_web::dev::ResourceMap; use actix_web::{web::Data, HttpRequest}; use async_trait::async_trait; use derive_more::derive::{From, Into}; @@ -12,11 +18,6 @@ use std::sync::Arc; use strum::{EnumString, VariantNames}; use tokio::sync::RwLock; -use super::prop::{ - Resourcetype, SupportedCalendarComponent, SupportedCalendarComponentSet, SupportedCalendarData, - SupportedReportSet, UserPrivilegeSet, -}; - pub struct CalendarResourceService { pub cal_store: Arc>, pub path: String, @@ -94,16 +95,21 @@ impl Resource for CalendarResource { type Prop = CalendarProp; type Error = Error; - fn get_prop(&self, prefix: &str, prop: Self::PropName) -> Result { + fn get_prop( + &self, + rmap: &ResourceMap, + prop: Self::PropName, + ) -> Result { Ok(match prop { CalendarPropName::Resourcetype => CalendarProp::Resourcetype(Resourcetype::default()), - CalendarPropName::CurrentUserPrincipal => CalendarProp::CurrentUserPrincipal( - HrefElement::new(format!("{}/user/{}/", prefix, self.0.principal)), - ), - CalendarPropName::Owner => CalendarProp::Owner(HrefElement::new(format!( - "{}/user/{}/", - prefix, self.0.principal - ))), + CalendarPropName::CurrentUserPrincipal => { + CalendarProp::CurrentUserPrincipal(HrefElement::new( + PrincipalResource::get_url(rmap, vec![&self.0.principal]).unwrap(), + )) + } + CalendarPropName::Owner => CalendarProp::Owner(HrefElement::new( + PrincipalResource::get_url(rmap, vec![&self.0.principal]).unwrap(), + )), CalendarPropName::Displayname => CalendarProp::Displayname(self.0.displayname.clone()), CalendarPropName::CalendarColor => CalendarProp::CalendarColor(self.0.color.clone()), CalendarPropName::CalendarDescription => { @@ -219,6 +225,11 @@ impl Resource for CalendarResource { CalendarPropName::Getctag => Err(rustical_dav::Error::PropReadOnly), } } + + #[inline] + fn resource_name() -> &'static str { + "caldav_calendar" + } } #[async_trait(?Send)] @@ -242,8 +253,10 @@ impl ResourceService for CalendarResourceService { Ok(calendar.into()) } - async fn get_members(&self) -> Result, Self::Error> { - // As of now the calendar resource has no members since events are shown with REPORT + async fn get_members( + &self, + rmap: &ResourceMap, + ) -> Result, Self::Error> { Ok(self .cal_store .read() @@ -251,7 +264,16 @@ impl ResourceService for CalendarResourceService { .get_objects(&self.principal, &self.calendar_id) .await? .into_iter() - .map(|event| (format!("{}/{}", self.path, &event.get_uid()), event.into())) + .map(|object| { + ( + CalendarObjectResource::get_url( + rmap, + vec![&self.principal, &self.calendar_id, object.get_uid()], + ) + .unwrap(), + object.into(), + ) + }) .collect()) } diff --git a/crates/caldav/src/calendar_object/resource.rs b/crates/caldav/src/calendar_object/resource.rs index 98191ac..1707fe2 100644 --- a/crates/caldav/src/calendar_object/resource.rs +++ b/crates/caldav/src/calendar_object/resource.rs @@ -1,5 +1,5 @@ use crate::Error; -use actix_web::{web::Data, HttpRequest}; +use actix_web::{dev::ResourceMap, web::Data, HttpRequest}; use async_trait::async_trait; use derive_more::derive::{From, Into}; use rustical_dav::resource::{InvalidProperty, Resource, ResourceService}; @@ -51,7 +51,11 @@ impl Resource for CalendarObjectResource { type Prop = CalendarObjectProp; type Error = Error; - fn get_prop(&self, _prefix: &str, prop: Self::PropName) -> Result { + fn get_prop( + &self, + _rmap: &ResourceMap, + prop: Self::PropName, + ) -> Result { Ok(match prop { CalendarObjectPropName::Getetag => CalendarObjectProp::Getetag(self.0.get_etag()), CalendarObjectPropName::CalendarData => { @@ -62,6 +66,11 @@ impl Resource for CalendarObjectResource { } }) } + + #[inline] + fn resource_name() -> &'static str { + "caldav_calendar_object" + } } #[async_trait(?Send)] diff --git a/crates/caldav/src/lib.rs b/crates/caldav/src/lib.rs index a3ae9dd..a8574cd 100644 --- a/crates/caldav/src/lib.rs +++ b/crates/caldav/src/lib.rs @@ -5,9 +5,8 @@ use calendar::resource::CalendarResourceService; use calendar_object::resource::CalendarObjectResourceService; use principal::PrincipalResourceService; use root::RootResourceService; -use rustical_dav::methods::{ - propfind::ServicePrefix, route_delete, route_propfind, route_proppatch, -}; +use rustical_dav::methods::{propfind::ServicePrefix, route_delete}; +use rustical_dav::resource::ResourceService; use rustical_store::auth::{AuthenticationMiddleware, AuthenticationProvider}; use rustical_store::CalendarStore; use std::str::FromStr; @@ -36,8 +35,6 @@ pub fn configure_dav( auth_provider: Arc, store: Arc>, ) { - let propfind_method = || web::method(Method::from_str("PROPFIND").unwrap()); - let proppatch_method = || web::method(Method::from_str("PROPPATCH").unwrap()); let report_method = || web::method(Method::from_str("REPORT").unwrap()); let mkcalendar_method = || web::method(Method::from_str("MKCALENDAR").unwrap()); @@ -55,40 +52,18 @@ pub fn configure_dav( .guard(guard::Method(Method::OPTIONS)) .to(options_handler), ) - .service( - web::resource("") - .route(propfind_method().to(route_propfind::)) - .route(proppatch_method().to(route_proppatch::)), - ) + .service(RootResourceService::actix_resource()) .service( web::scope("/user").service( web::scope("/{principal}") - .service( - web::resource("") - .route( - propfind_method() - .to(route_propfind::>), - ) - .route( - proppatch_method() - .to(route_proppatch::>), - ), - ) + .service(PrincipalResourceService::::actix_resource()) .service( web::scope("/{calendar}") .service( - web::resource("") + CalendarResourceService::::actix_resource() .route(report_method().to( calendar::methods::report::route_report_calendar::, )) - .route( - propfind_method() - .to(route_propfind::>), - ) - .route( - proppatch_method() - .to(route_proppatch::>), - ) .route( web::method(Method::DELETE) .to(route_delete::>), @@ -98,28 +73,20 @@ pub fn configure_dav( )), ) .service( - web::resource("/{event}") - .route( - propfind_method().to(route_propfind::< + web::scope("/{object}").service( + CalendarObjectResourceService::::actix_resource() + .route(web::method(Method::DELETE).to(route_delete::< CalendarObjectResourceService, - >), - ) - .route(proppatch_method().to(route_proppatch::< - CalendarObjectResourceService, - >)) - .route( - web::method(Method::DELETE).to(route_delete::< - CalendarObjectResourceService, - >), - ) - .route( - web::method(Method::GET) - .to(calendar_object::methods::get_event::), - ) - .route( - web::method(Method::PUT) - .to(calendar_object::methods::put_event::), - ), + >)) + .route( + web::method(Method::GET) + .to(calendar_object::methods::get_event::), + ) + .route( + web::method(Method::PUT) + .to(calendar_object::methods::put_event::), + ), + ), ), ), ), diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index 133283a..0e8650f 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -1,4 +1,5 @@ use crate::Error; +use actix_web::dev::ResourceMap; use actix_web::web::Data; use actix_web::HttpRequest; use async_trait::async_trait; @@ -14,7 +15,6 @@ use crate::calendar::resource::CalendarResource; pub struct PrincipalResourceService { principal: String, - path: String, cal_store: Arc>, } @@ -67,8 +67,13 @@ impl Resource for PrincipalResource { type Prop = PrincipalProp; type Error = Error; - fn get_prop(&self, prefix: &str, prop: Self::PropName) -> Result { - let principal_href = HrefElement::new(format!("{}/user/{}/", prefix, self.principal)); + fn get_prop( + &self, + rmap: &ResourceMap, + prop: Self::PropName, + ) -> Result { + let principal_href = HrefElement::new(Self::get_url(rmap, vec![&self.principal]).unwrap()); + Ok(match prop { PrincipalPropName::Resourcetype => PrincipalProp::Resourcetype(Resourcetype::default()), PrincipalPropName::CurrentUserPrincipal => { @@ -81,6 +86,11 @@ impl Resource for PrincipalResource { } }) } + + #[inline] + fn resource_name() -> &'static str { + "caldav_principal" + } } #[async_trait(?Send)] @@ -102,7 +112,6 @@ impl ResourceService for PrincipalResourceService Ok(Self { cal_store, - path: req.path().to_owned(), principal, }) } @@ -116,7 +125,10 @@ impl ResourceService for PrincipalResourceService }) } - async fn get_members(&self) -> Result, Self::Error> { + async fn get_members( + &self, + rmap: &ResourceMap, + ) -> Result, Self::Error> { let calendars = self .cal_store .read() @@ -125,7 +137,12 @@ impl ResourceService for PrincipalResourceService .await?; Ok(calendars .into_iter() - .map(|cal| (format!("{}/{}", &self.path, &cal.id), cal.into())) + .map(|cal| { + ( + CalendarResource::get_url(rmap, vec![&self.principal, &cal.id]).unwrap(), + cal.into(), + ) + }) .collect()) } diff --git a/crates/caldav/src/root/mod.rs b/crates/caldav/src/root/mod.rs index d8a4ccc..29fba4c 100644 --- a/crates/caldav/src/root/mod.rs +++ b/crates/caldav/src/root/mod.rs @@ -1,4 +1,6 @@ +use crate::principal::PrincipalResource; use crate::Error; +use actix_web::dev::ResourceMap; use actix_web::HttpRequest; use async_trait::async_trait; use rustical_dav::resource::{InvalidProperty, Resource, ResourceService}; @@ -47,14 +49,23 @@ impl Resource for RootResource { type Prop = RootProp; type Error = Error; - fn get_prop(&self, prefix: &str, prop: Self::PropName) -> Result { + fn get_prop( + &self, + rmap: &ResourceMap, + prop: Self::PropName, + ) -> Result { Ok(match prop { RootPropName::Resourcetype => RootProp::Resourcetype(Resourcetype::default()), RootPropName::CurrentUserPrincipal => RootProp::CurrentUserPrincipal(HrefElement::new( - format!("{}/user/{}/", prefix, self.principal), + PrincipalResource::get_url(rmap, vec![&self.principal]).unwrap(), )), }) } + + #[inline] + fn resource_name() -> &'static str { + "caldav_root" + } } #[async_trait(?Send)] diff --git a/crates/dav/src/methods/propfind.rs b/crates/dav/src/methods/propfind.rs index 5a9c8d7..46afb5f 100644 --- a/crates/dav/src/methods/propfind.rs +++ b/crates/dav/src/methods/propfind.rs @@ -1,12 +1,11 @@ use crate::depth_extractor::Depth; -use crate::resource::HandlePropfind; use crate::resource::Resource; use crate::resource::ResourceService; use crate::xml::multistatus::PropstatWrapper; use crate::xml::MultistatusElement; use crate::xml::TagList; use crate::Error; -use actix_web::web::{Data, Path}; +use actix_web::web::Path; use actix_web::HttpRequest; use derive_more::derive::Deref; use log::debug; @@ -43,7 +42,6 @@ pub async fn route_propfind( path_components: Path, body: String, req: HttpRequest, - prefix: Data, user: User, depth: Depth, ) -> Result< @@ -54,7 +52,6 @@ pub async fn route_propfind( R::Error, > { debug!("{body}"); - let prefix = prefix.into_inner(); let resource_service = R::new(&req, path_components.into_inner()).await?; @@ -81,13 +78,19 @@ pub async fn route_propfind( let mut member_responses = Vec::new(); if depth != Depth::Zero { - for (path, member) in resource_service.get_members().await? { - member_responses.push(member.propfind(&prefix, &path, props.clone()).await?); + for (path, member) in resource_service.get_members(req.resource_map()).await? { + member_responses.push( + member + .propfind(&path, props.clone(), req.resource_map()) + .await?, + ); } } let resource = resource_service.get_resource(user.id).await?; - let response = resource.propfind(&prefix, req.path(), props).await?; + let response = resource + .propfind(req.path(), props, req.resource_map()) + .await?; Ok(MultistatusElement { responses: vec![response], diff --git a/crates/dav/src/resource.rs b/crates/dav/src/resource.rs index 4068d23..3fc3b01 100644 --- a/crates/dav/src/resource.rs +++ b/crates/dav/src/resource.rs @@ -1,6 +1,12 @@ +use crate::methods::{route_propfind, route_proppatch}; use crate::xml::multistatus::{PropTagWrapper, PropstatElement, PropstatWrapper}; use crate::xml::{multistatus::ResponseElement, TagList}; use crate::Error; +use actix_web::dev::ResourceMap; +use actix_web::error::UrlGenerationError; +use actix_web::http::Method; +use actix_web::test::TestRequest; +use actix_web::web; use actix_web::{http::StatusCode, HttpRequest, ResponseError}; use async_trait::async_trait; use core::fmt; @@ -18,7 +24,8 @@ pub trait Resource: Clone { Self::PropName::VARIANTS } - fn get_prop(&self, prefix: &str, prop: Self::PropName) -> Result; + fn get_prop(&self, rmap: &ResourceMap, prop: Self::PropName) + -> Result; fn set_prop(&mut self, _prop: Self::Prop) -> Result<(), crate::Error> { Err(crate::Error::PropReadOnly) @@ -27,61 +34,31 @@ pub trait Resource: Clone { fn remove_prop(&mut self, _prop: Self::PropName) -> Result<(), crate::Error> { Err(crate::Error::PropReadOnly) } -} -pub trait InvalidProperty { - fn invalid_property(&self) -> bool; -} + fn resource_name() -> &'static str; -// A resource is identified by a URI and has properties -// A resource can also be a collection -// A resource cannot be none, only Methods like PROPFIND, GET, REPORT, etc. can be exposed -// A resource exists -#[async_trait(?Send)] -pub trait ResourceService: Sized { - type MemberType: Resource; - type PathComponents: Sized + Clone; // defines how the resource URI maps to parameters, i.e. /{principal}/{calendar} -> (String, String) - type Resource: Resource; - type Error: ResponseError + From + From; - - async fn new( - req: &HttpRequest, - path_components: Self::PathComponents, - ) -> Result; - - async fn get_members(&self) -> Result, Self::Error> { - Ok(vec![]) + 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()) } - async fn get_resource(&self, principal: String) -> Result; - async fn save_resource(&self, file: Self::Resource) -> Result<(), Self::Error>; - async fn delete_resource(&self, _use_trashbin: bool) -> Result<(), Self::Error> { - Err(crate::Error::Unauthorized.into()) - } -} - -#[async_trait(?Send)] -pub trait HandlePropfind { - type Error: ResponseError + From + From; - + #[allow(async_fn_in_trait)] async fn propfind( &self, - prefix: &str, - path: &str, - props: Vec<&str>, - ) -> Result; -} - -#[async_trait(?Send)] -impl HandlePropfind for R { - type Error = R::Error; - - async fn propfind( - &self, - prefix: &str, path: &str, mut props: Vec<&str>, - ) -> Result>, R::Error> { + rmap: &ResourceMap, + ) -> Result>, Self::Error> { if props.contains(&"propname") { if props.len() != 1 { // propname MUST be the only queried prop per spec @@ -89,7 +66,7 @@ impl HandlePropfind for R { Error::BadRequest("propname MUST be the only queried prop".to_owned()).into(), ); } - let props: Vec = R::list_props() + let props: Vec = Self::list_props() .iter() .map(|&prop| prop.to_string()) .collect(); @@ -109,26 +86,26 @@ impl HandlePropfind for R { Error::BadRequest("allprop MUST be the only queried prop".to_owned()).into(), ); } - props = R::list_props().into(); + props = Self::list_props().into(); } - let (valid_props, invalid_props): (Vec>, Vec>) = props + let (valid_props, invalid_props): (Vec>, Vec>) = props .into_iter() .map(|prop| { - if let Ok(valid_prop) = R::PropName::from_str(prop) { + if let Ok(valid_prop) = Self::PropName::from_str(prop) { (Some(valid_prop), None) } else { (None, Some(prop)) } }) .unzip(); - let valid_props: Vec = valid_props.into_iter().flatten().collect(); + let valid_props: Vec = valid_props.into_iter().flatten().collect(); let invalid_props: Vec<&str> = invalid_props.into_iter().flatten().collect(); let prop_responses = valid_props .into_iter() - .map(|prop| self.get_prop(prefix, prop)) - .collect::, R::Error>>()?; + .map(|prop| self.get_prop(rmap, prop)) + .collect::, Self::Error>>()?; let mut propstats = vec![PropstatWrapper::Normal(PropstatElement { status: format!("HTTP/1.1 {}", StatusCode::OK), @@ -153,3 +130,51 @@ impl HandlePropfind for R { }) } } + +pub trait InvalidProperty { + fn invalid_property(&self) -> bool; +} + +// A resource is identified by a URI and has properties +// A resource can also be a collection +// A resource cannot be none, only Methods like PROPFIND, GET, REPORT, etc. can be exposed +// A resource exists +#[async_trait(?Send)] +pub trait ResourceService: Sized + 'static { + type MemberType: Resource; + type PathComponents: for<'de> Deserialize<'de> + Sized + Clone + 'static; // defines how the resource URI maps to parameters, i.e. /{principal}/{calendar} -> (String, String) + type Resource: Resource; + type Error: ResponseError + From + From; + + async fn new( + req: &HttpRequest, + path_components: Self::PathComponents, + ) -> Result; + + async fn get_members( + &self, + _rmap: &ResourceMap, + ) -> Result, Self::Error> { + Ok(vec![]) + } + + async fn get_resource(&self, principal: String) -> Result; + async fn save_resource(&self, file: Self::Resource) -> Result<(), Self::Error>; + async fn delete_resource(&self, _use_trashbin: bool) -> Result<(), Self::Error> { + Err(crate::Error::Unauthorized.into()) + } + + fn resource_name() -> &'static str { + Self::Resource::resource_name() + } + + fn actix_resource() -> actix_web::Resource { + let propfind_method = || web::method(Method::from_str("PROPFIND").unwrap()); + let proppatch_method = || web::method(Method::from_str("PROPPATCH").unwrap()); + + web::resource("") + .name(Self::resource_name()) + .route(propfind_method().to(route_propfind::)) + .route(proppatch_method().to(route_proppatch::)) + } +} diff --git a/crates/store/src/auth/middleware.rs b/crates/store/src/auth/middleware.rs index ef5d87e..ce7c513 100644 --- a/crates/store/src/auth/middleware.rs +++ b/crates/store/src/auth/middleware.rs @@ -1,7 +1,6 @@ use super::AuthenticationProvider; use actix_web::{ dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, - error::ErrorUnauthorized, http::header::Header, HttpMessage, }; diff --git a/src/app.rs b/src/app.rs index 60eeafc..33373c1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -42,7 +42,8 @@ pub fn make_app( // }), ) .service( - web::scope("/frontend").configure(|cfg| configure_frontend(cfg, cal_store.clone())), + web::scope("/frontend") + .configure(|cfg| configure_frontend(cfg, auth_provider.clone(), cal_store.clone())), ) .service(web::redirect("/", "/frontend").permanent()) }