From d5ef6669a6b16ef9b087c91ab28e223179eda1c9 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:22:48 +0100 Subject: [PATCH] Remove all that extension business and replace with internal properties --- .../methods/report/calendar_multiget.rs | 10 +- .../calendar/methods/report/calendar_query.rs | 10 +- .../methods/report/sync_collection.rs | 10 +- crates/caldav/src/calendar/resource.rs | 8 +- crates/caldav/src/calendar_object/resource.rs | 7 +- crates/caldav/src/error.rs | 2 + crates/caldav/src/principal/mod.rs | 8 +- crates/carddav/src/address_object/resource.rs | 7 +- .../methods/report/addressbook_multiget.rs | 10 +- .../methods/report/sync_collection.rs | 10 +- crates/carddav/src/addressbook/resource.rs | 8 +- crates/carddav/src/error.rs | 2 + crates/carddav/src/principal/mod.rs | 8 +- crates/dav/src/error.rs | 4 +- crates/dav/src/extension.rs | 55 ------ crates/dav/src/extensions/mod.rs | 91 ---------- crates/dav/src/lib.rs | 2 - crates/dav/src/methods/propfind.rs | 6 +- crates/dav/src/methods/proppatch.rs | 4 +- crates/dav/src/resource/mod.rs | 167 ++++++++++++------ crates/dav/src/resources/root.rs | 11 +- 21 files changed, 176 insertions(+), 264 deletions(-) delete mode 100644 crates/dav/src/extension.rs delete mode 100644 crates/dav/src/extensions/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 8fdc59e..47ffd71 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_multiget.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_multiget.rs @@ -10,7 +10,7 @@ use actix_web::{ }; use rustical_dav::{ methods::propfind::{PropElement, PropfindType}, - resource::Resource, + resource::{CommonPropertiesProp, EitherProp, Resource}, xml::{ multistatus::{PropstatWrapper, ResponseElement}, MultistatusElement, @@ -69,7 +69,13 @@ pub async fn handle_calendar_multiget( principal: &str, cal_id: &str, cal_store: &C, -) -> Result, String>, Error> { +) -> Result< + MultistatusElement< + PropstatWrapper>, + 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) diff --git a/crates/caldav/src/calendar/methods/report/calendar_query.rs b/crates/caldav/src/calendar/methods/report/calendar_query.rs index a2c7f74..906a288 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_query.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_query.rs @@ -2,7 +2,7 @@ use actix_web::HttpRequest; use chrono::{DateTime, Utc}; use rustical_dav::{ methods::propfind::{PropElement, PropfindType}, - resource::Resource, + resource::{CommonPropertiesProp, EitherProp, Resource}, xml::{multistatus::PropstatWrapper, MultistatusElement}, }; use rustical_store::{auth::User, CalendarObject, CalendarStore}; @@ -210,7 +210,13 @@ pub async fn handle_calendar_query( principal: &str, cal_id: &str, cal_store: &C, -) -> Result, String>, Error> { +) -> Result< + MultistatusElement< + PropstatWrapper>, + String, + >, + Error, +> { let objects = get_objects_calendar_query(&cal_query, principal, cal_id, cal_store).await?; let props = match cal_query.prop { diff --git a/crates/caldav/src/calendar/methods/report/sync_collection.rs b/crates/caldav/src/calendar/methods/report/sync_collection.rs index 8a1812f..48e3ff5 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::Resource, + resource::{CommonPropertiesProp, EitherProp, Resource}, xml::{ multistatus::{PropstatWrapper, ResponseElement}, MultistatusElement, @@ -49,7 +49,13 @@ pub async fn handle_sync_collection( principal: &str, cal_id: &str, cal_store: &C, -) -> Result, String>, Error> { +) -> Result< + MultistatusElement< + PropstatWrapper>, + String, + >, + Error, +> { let props = match sync_collection.prop { PropfindType::Allprop => { vec!["allprop".to_owned()] diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs index 2a7eb53..a2e999a 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -13,7 +13,6 @@ use actix_web::web; use actix_web::{web::Data, HttpRequest}; use async_trait::async_trait; use derive_more::derive::{From, Into}; -use rustical_dav::extensions::CommonPropertiesProp; use rustical_dav::privileges::UserPrivilegeSet; use rustical_dav::resource::{Resource, ResourceService}; use rustical_store::auth::User; @@ -46,7 +45,7 @@ pub enum CalendarPropName { Getctag, } -#[derive(Default, Deserialize, Serialize, From, PartialEq)] +#[derive(Default, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum CalendarProp { // WebDAV (RFC 2518) @@ -83,10 +82,6 @@ pub enum CalendarProp { // Didn't find the spec Getctag(String), - #[serde(skip_deserializing, rename = "$value")] - #[from] - ExtCommonProperties(CommonPropertiesProp), - #[serde(other)] #[default] Invalid, @@ -183,7 +178,6 @@ impl Resource for CalendarResource { CalendarProp::SyncToken(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::Getctag(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::Invalid => Err(rustical_dav::Error::PropReadOnly), - _ => panic!("we shouldn't end up here"), } } diff --git a/crates/caldav/src/calendar_object/resource.rs b/crates/caldav/src/calendar_object/resource.rs index cbcda2d..a76c93c 100644 --- a/crates/caldav/src/calendar_object/resource.rs +++ b/crates/caldav/src/calendar_object/resource.rs @@ -4,7 +4,6 @@ use actix_web::{dev::ResourceMap, web::Data, HttpRequest}; use async_trait::async_trait; use derive_more::derive::{From, Into}; use rustical_dav::{ - extensions::CommonPropertiesProp, privileges::UserPrivilegeSet, resource::{Resource, ResourceService}, }; @@ -28,7 +27,7 @@ pub enum CalendarObjectPropName { Getcontenttype, } -#[derive(Default, Deserialize, Serialize, From, PartialEq)] +#[derive(Default, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum CalendarObjectProp { // WebDAV (RFC 2518) @@ -39,10 +38,6 @@ pub enum CalendarObjectProp { #[serde(rename = "C:calendar-data")] CalendarData(String), - #[serde(skip_deserializing, rename = "$value")] - #[from] - ExtCommonProperties(CommonPropertiesProp), - #[serde(other)] #[default] Invalid, diff --git a/crates/caldav/src/error.rs b/crates/caldav/src/error.rs index 270684f..05fb51b 100644 --- a/crates/caldav/src/error.rs +++ b/crates/caldav/src/error.rs @@ -1,4 +1,5 @@ use actix_web::{http::StatusCode, HttpResponse}; +use tracing::error; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -41,6 +42,7 @@ impl actix_web::ResponseError for Error { } } fn error_response(&self) -> actix_web::HttpResponse { + error!("Error: {self}"); match self { Error::DavError(err) => err.error_response(), _ => HttpResponse::build(self.status_code()).body(self.to_string()), diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index 3c726e1..93d146b 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -4,8 +4,6 @@ use actix_web::dev::ResourceMap; use actix_web::web::Data; use actix_web::HttpRequest; use async_trait::async_trait; -use derive_more::derive::From; -use rustical_dav::extensions::CommonPropertiesProp; use rustical_dav::privileges::UserPrivilegeSet; use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::xml::HrefElement; @@ -25,7 +23,7 @@ pub struct PrincipalResource { principal: String, } -#[derive(Default, Deserialize, Serialize, From, PartialEq)] +#[derive(Default, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum PrincipalProp { // WebDAV Access Control (RFC 3744) @@ -38,10 +36,6 @@ pub enum PrincipalProp { #[serde(rename = "C:calendar-user-address-set")] CalendarUserAddressSet(HrefElement), - #[serde(skip_deserializing, rename = "$value")] - #[from] - ExtCommonProperties(CommonPropertiesProp), - #[serde(other)] #[default] Invalid, diff --git a/crates/carddav/src/address_object/resource.rs b/crates/carddav/src/address_object/resource.rs index d0f11e9..a1c0238 100644 --- a/crates/carddav/src/address_object/resource.rs +++ b/crates/carddav/src/address_object/resource.rs @@ -3,7 +3,6 @@ use actix_web::{dev::ResourceMap, web::Data, HttpRequest}; use async_trait::async_trait; use derive_more::derive::{From, Into}; use rustical_dav::{ - extensions::CommonPropertiesProp, privileges::UserPrivilegeSet, resource::{Resource, ResourceService}, }; @@ -29,7 +28,7 @@ pub enum AddressObjectPropName { Getcontenttype, } -#[derive(Default, Deserialize, Serialize, From, PartialEq)] +#[derive(Default, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum AddressObjectProp { // WebDAV (RFC 2518) @@ -40,10 +39,6 @@ pub enum AddressObjectProp { #[serde(rename = "CARD:address-data")] AddressData(String), - #[serde(skip_deserializing, rename = "$value")] - #[from] - ExtCommonProperties(CommonPropertiesProp), - #[serde(other)] #[default] Invalid, diff --git a/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs b/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs index 1aee58c..570e368 100644 --- a/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs +++ b/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs @@ -10,7 +10,7 @@ use actix_web::{ }; use rustical_dav::{ methods::propfind::{PropElement, PropfindType}, - resource::Resource, + resource::{CommonPropertiesProp, EitherProp, Resource}, xml::{ multistatus::{PropstatWrapper, ResponseElement}, MultistatusElement, @@ -68,7 +68,13 @@ pub async fn handle_addressbook_multiget( principal: &str, cal_id: &str, addr_store: &AS, -) -> Result, String>, Error> { +) -> Result< + MultistatusElement< + PropstatWrapper>, + String, + >, + Error, +> { let principal_url = PrincipalResource::get_url(req.resource_map(), vec![principal]).unwrap(); let (objects, not_found) = get_objects_addressbook_multiget( &addr_multiget, diff --git a/crates/carddav/src/addressbook/methods/report/sync_collection.rs b/crates/carddav/src/addressbook/methods/report/sync_collection.rs index 7bbab05..923e561 100644 --- a/crates/carddav/src/addressbook/methods/report/sync_collection.rs +++ b/crates/carddav/src/addressbook/methods/report/sync_collection.rs @@ -5,7 +5,7 @@ use crate::{ use actix_web::{http::StatusCode, HttpRequest}; use rustical_dav::{ methods::propfind::{PropElement, PropfindType}, - resource::Resource, + resource::{CommonPropertiesProp, EitherProp, Resource}, xml::{ multistatus::{PropstatWrapper, ResponseElement}, MultistatusElement, @@ -47,7 +47,13 @@ pub async fn handle_sync_collection( principal: &str, addressbook_id: &str, addr_store: &AS, -) -> Result, String>, Error> { +) -> Result< + MultistatusElement< + PropstatWrapper>, + String, + >, + Error, +> { let props = match sync_collection.prop { PropfindType::Allprop => { vec!["allprop".to_owned()] diff --git a/crates/carddav/src/addressbook/resource.rs b/crates/carddav/src/addressbook/resource.rs index cc929b7..adf15b8 100644 --- a/crates/carddav/src/addressbook/resource.rs +++ b/crates/carddav/src/addressbook/resource.rs @@ -10,7 +10,6 @@ use actix_web::web; use actix_web::{web::Data, HttpRequest}; use async_trait::async_trait; use derive_more::derive::{From, Into}; -use rustical_dav::extensions::CommonPropertiesProp; use rustical_dav::privileges::UserPrivilegeSet; use rustical_dav::resource::{Resource, ResourceService}; use rustical_store::auth::User; @@ -39,7 +38,7 @@ pub enum AddressbookPropName { Getctag, } -#[derive(Default, Deserialize, Serialize, From, PartialEq)] +#[derive(Default, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum AddressbookProp { // WebDAV (RFC 2518) @@ -68,10 +67,6 @@ pub enum AddressbookProp { // Didn't find the spec Getctag(String), - #[serde(skip_deserializing, rename = "$value")] - #[from] - ExtCommonProperties(CommonPropertiesProp), - #[serde(other)] #[default] Invalid, @@ -135,7 +130,6 @@ impl Resource for AddressbookResource { AddressbookProp::SyncToken(_) => Err(rustical_dav::Error::PropReadOnly), AddressbookProp::Getctag(_) => Err(rustical_dav::Error::PropReadOnly), AddressbookProp::Invalid => Err(rustical_dav::Error::PropReadOnly), - _ => panic!("we shouldn't end up here"), } } diff --git a/crates/carddav/src/error.rs b/crates/carddav/src/error.rs index 270684f..05fb51b 100644 --- a/crates/carddav/src/error.rs +++ b/crates/carddav/src/error.rs @@ -1,4 +1,5 @@ use actix_web::{http::StatusCode, HttpResponse}; +use tracing::error; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -41,6 +42,7 @@ impl actix_web::ResponseError for Error { } } fn error_response(&self) -> actix_web::HttpResponse { + error!("Error: {self}"); match self { Error::DavError(err) => err.error_response(), _ => HttpResponse::build(self.status_code()).body(self.to_string()), diff --git a/crates/carddav/src/principal/mod.rs b/crates/carddav/src/principal/mod.rs index 741b0a2..536cc8b 100644 --- a/crates/carddav/src/principal/mod.rs +++ b/crates/carddav/src/principal/mod.rs @@ -4,8 +4,6 @@ use actix_web::dev::ResourceMap; use actix_web::web::Data; use actix_web::HttpRequest; use async_trait::async_trait; -use derive_more::derive::From; -use rustical_dav::extensions::CommonPropertiesProp; use rustical_dav::privileges::UserPrivilegeSet; use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::xml::HrefElement; @@ -25,7 +23,7 @@ pub struct PrincipalResource { principal: String, } -#[derive(Default, Deserialize, Serialize, From, PartialEq)] +#[derive(Default, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum PrincipalProp { // WebDAV Access Control (RFC 3744) @@ -38,10 +36,6 @@ pub enum PrincipalProp { #[serde(rename = "CARD:principal-address")] PrincipalAddress(Option), - #[serde(skip_deserializing, rename = "$value")] - #[from] - ExtCommonProperties(CommonPropertiesProp), - #[serde(other)] #[default] Invalid, diff --git a/crates/dav/src/error.rs b/crates/dav/src/error.rs index 98ef614..cda9005 100644 --- a/crates/dav/src/error.rs +++ b/crates/dav/src/error.rs @@ -1,7 +1,6 @@ use actix_web::{http::StatusCode, HttpResponse}; use thiserror::Error; - -// use crate::routes::propfind; +use tracing::error; #[derive(Debug, Error)] pub enum Error { @@ -41,6 +40,7 @@ impl actix_web::error::ResponseError for Error { } fn error_response(&self) -> HttpResponse { + error!("Error: {self}"); match self { Error::Unauthorized => HttpResponse::build(self.status_code()) .append_header(("WWW-Authenticate", "Basic")) diff --git a/crates/dav/src/extension.rs b/crates/dav/src/extension.rs deleted file mode 100644 index 4b92546..0000000 --- a/crates/dav/src/extension.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::resource::{Resource, ResourceProp, ResourcePropName}; -use actix_web::dev::ResourceMap; -use rustical_store::auth::User; -use std::str::FromStr; -use strum::VariantNames; - -pub trait ResourceExtension: Clone { - type PropName: ResourcePropName; - type Prop: ResourceProp + Into; - type Error: Into + From; - - fn list_props(&self) -> &'static [&'static str] { - Self::PropName::VARIANTS - } - - fn get_prop( - &self, - resource: &R, - rmap: &ResourceMap, - user: &User, - prop: Self::PropName, - ) -> Result; - - fn propfind<'a>( - &self, - resource: &R, - props: Vec<&'a str>, - user: &User, - rmap: &ResourceMap, - ) -> Result<(Vec<&'a str>, Vec), R::Error> { - let (valid_props, invalid_props): (Vec>, Vec>) = props - .into_iter() - .map(|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 invalid_props: Vec<&str> = invalid_props.into_iter().flatten().collect(); - - let prop_responses = valid_props - .into_iter() - .map(|prop| >::get_prop(self, resource, rmap, user, prop)) - .collect::, Self::Error>>() - .map_err(Self::Error::into)? - .into_iter() - .map(|prop| prop.into()) - .collect::>(); - - Ok((invalid_props, prop_responses)) - } -} diff --git a/crates/dav/src/extensions/mod.rs b/crates/dav/src/extensions/mod.rs deleted file mode 100644 index 40a0232..0000000 --- a/crates/dav/src/extensions/mod.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::{ - extension::ResourceExtension, - privileges::UserPrivilegeSet, - resource::{InvalidProperty, Resource}, - xml::{HrefElement, Resourcetype}, -}; -use actix_web::dev::ResourceMap; -use rustical_store::auth::User; -use serde::{Deserialize, Serialize}; -use std::marker::PhantomData; -use strum::{EnumString, VariantNames}; - -#[derive(Clone)] -pub struct CommonPropertiesExtension(PhantomData); - -impl Default for CommonPropertiesExtension { - fn default() -> Self { - Self(PhantomData) - } -} - -#[derive(Deserialize, Serialize, PartialEq)] -#[serde(rename_all = "kebab-case")] -pub enum CommonPropertiesProp { - // WebDAV (RFC 2518) - #[serde(skip_deserializing)] - Resourcetype(Resourcetype), - - // WebDAV Current Principal Extension (RFC 5397) - CurrentUserPrincipal(HrefElement), - - // WebDAV Access Control Protocol (RFC 3477) - CurrentUserPrivilegeSet(UserPrivilegeSet), - Owner(Option), - - #[serde(other)] - Invalid, -} - -impl InvalidProperty for CommonPropertiesProp { - fn invalid_property(&self) -> bool { - matches!(self, Self::Invalid) - } -} - -#[derive(EnumString, VariantNames, Clone)] -#[strum(serialize_all = "kebab-case")] -pub enum CommonPropertiesPropName { - Resourcetype, - CurrentUserPrincipal, - CurrentUserPrivilegeSet, - Owner, -} - -impl ResourceExtension for CommonPropertiesExtension -where - R::Prop: From, -{ - type Prop = CommonPropertiesProp; - type PropName = CommonPropertiesPropName; - type Error = R::Error; - - fn get_prop( - &self, - resource: &R, - rmap: &ResourceMap, - user: &User, - prop: Self::PropName, - ) -> Result { - Ok(match prop { - CommonPropertiesPropName::Resourcetype => { - CommonPropertiesProp::Resourcetype(Resourcetype(R::get_resourcetype())) - } - CommonPropertiesPropName::CurrentUserPrincipal => { - CommonPropertiesProp::CurrentUserPrincipal( - R::PrincipalResource::get_url(rmap, [&user.id]) - .unwrap() - .into(), - ) - } - CommonPropertiesPropName::CurrentUserPrivilegeSet => { - CommonPropertiesProp::CurrentUserPrivilegeSet(resource.get_user_privileges(user)?) - } - CommonPropertiesPropName::Owner => CommonPropertiesProp::Owner( - resource - .get_owner() - .map(|owner| R::PrincipalResource::get_url(rmap, [owner]).unwrap().into()), - ), - }) - } -} diff --git a/crates/dav/src/lib.rs b/crates/dav/src/lib.rs index 38ee9c0..4884ea4 100644 --- a/crates/dav/src/lib.rs +++ b/crates/dav/src/lib.rs @@ -1,7 +1,5 @@ pub mod depth_header; pub mod error; -pub mod extension; -pub mod extensions; pub mod methods; pub mod namespace; pub mod privileges; diff --git a/crates/dav/src/methods/propfind.rs b/crates/dav/src/methods/propfind.rs index 17334a3..cee2c98 100644 --- a/crates/dav/src/methods/propfind.rs +++ b/crates/dav/src/methods/propfind.rs @@ -1,5 +1,7 @@ use crate::depth_header::Depth; use crate::privileges::UserPrivilege; +use crate::resource::CommonPropertiesProp; +use crate::resource::EitherProp; use crate::resource::Resource; use crate::resource::ResourceService; use crate::xml::multistatus::PropstatWrapper; @@ -46,8 +48,8 @@ pub async fn route_propfind( root_span: RootSpan, ) -> Result< MultistatusElement< - PropstatWrapper<::Prop>, - PropstatWrapper<::Prop>, + PropstatWrapper::Prop, CommonPropertiesProp>>, + PropstatWrapper::Prop, CommonPropertiesProp>>, >, R::Error, > { diff --git a/crates/dav/src/methods/proppatch.rs b/crates/dav/src/methods/proppatch.rs index 93e615b..a041eb0 100644 --- a/crates/dav/src/methods/proppatch.rs +++ b/crates/dav/src/methods/proppatch.rs @@ -93,10 +93,10 @@ pub async fn route_proppatch( prop: PropertyElement { prop }, }) => { if prop.invalid_property() { - if ::list_all_props().contains(&propname.as_str()) { + if ::list_props().contains(&propname.as_str()) { // This happens in following cases: // - read-only properties with #[serde(skip_deserializing)] - // - for read-only properties from extensions + // - internal properties props_conflict.push(propname) } else { props_not_found.push(propname); diff --git a/crates/dav/src/resource/mod.rs b/crates/dav/src/resource/mod.rs index ea6fbe9..d1e725c 100644 --- a/crates/dav/src/resource/mod.rs +++ b/crates/dav/src/resource/mod.rs @@ -1,9 +1,8 @@ -use crate::extension::ResourceExtension; -use crate::extensions::{CommonPropertiesExtension, CommonPropertiesProp}; use crate::methods::{route_delete, route_propfind, route_proppatch}; use crate::privileges::UserPrivilegeSet; use crate::xml::multistatus::{PropTagWrapper, PropstatElement, PropstatWrapper}; use crate::xml::{multistatus::ResponseElement, TagList}; +use crate::xml::{HrefElement, Resourcetype}; use crate::Error; use actix_web::dev::ResourceMap; use actix_web::error::UrlGenerationError; @@ -16,7 +15,7 @@ use itertools::Itertools; use rustical_store::auth::User; use serde::{Deserialize, Serialize}; use std::str::FromStr; -use strum::VariantNames; +use strum::{EnumString, VariantNames}; pub trait ResourceProp: InvalidProperty + Serialize + for<'de> Deserialize<'de> {} impl Deserialize<'de>> ResourceProp for T {} @@ -27,31 +26,91 @@ impl ResourcePropName for T {} pub trait ResourceType: Serialize + for<'de> Deserialize<'de> {} impl Deserialize<'de>> ResourceType for T {} +#[derive(Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "kebab-case")] +pub enum CommonPropertiesProp { + // WebDAV (RFC 2518) + #[serde(skip_deserializing)] + Resourcetype(Resourcetype), + + // WebDAV Current Principal Extension (RFC 5397) + CurrentUserPrincipal(HrefElement), + + // WebDAV Access Control Protocol (RFC 3477) + CurrentUserPrivilegeSet(UserPrivilegeSet), + Owner(Option), + + #[serde(other)] + Invalid, +} + +#[derive(Serialize)] +pub enum EitherProp { + #[serde(untagged)] + Left(Left), + #[serde(untagged)] + Right(Right), +} + +impl InvalidProperty for CommonPropertiesProp { + fn invalid_property(&self) -> bool { + matches!(self, Self::Invalid) + } +} + +#[derive(EnumString, VariantNames, Clone)] +#[strum(serialize_all = "kebab-case")] +pub enum CommonPropertiesPropName { + Resourcetype, + CurrentUserPrincipal, + CurrentUserPrivilegeSet, + Owner, +} + pub trait Resource: Clone + 'static { type PropName: ResourcePropName; - type Prop: ResourceProp + From + PartialEq; + type Prop: ResourceProp + PartialEq; type Error: ResponseError + From; type PrincipalResource: Resource; fn get_resourcetype() -> &'static [&'static str]; - fn list_extensions() -> Vec> { - vec![CommonPropertiesExtension::default()] - } - - fn list_props() -> &'static [&'static str] { - Self::PropName::VARIANTS + fn list_props() -> Vec<&'static str> { + [ + &Self::PropName::VARIANTS[..], + &CommonPropertiesPropName::VARIANTS[..], + ] + .concat() } - fn list_all_props() -> Vec<&'static str> { - let mut props = Self::list_props().to_vec(); - props.extend( - Self::list_extensions() - .into_iter() - .map(|ext| ext.list_props().to_vec()) - .concat(), - ); - props + fn get_internal_prop( + &self, + rmap: &ResourceMap, + user: &User, + prop: &CommonPropertiesPropName, + ) -> Result { + Ok(match prop { + CommonPropertiesPropName::Resourcetype => { + CommonPropertiesProp::Resourcetype(Resourcetype(Self::get_resourcetype())) + } + CommonPropertiesPropName::CurrentUserPrincipal => { + CommonPropertiesProp::CurrentUserPrincipal( + Self::PrincipalResource::get_url(rmap, [&user.id]) + .unwrap() + .into(), + ) + } + CommonPropertiesPropName::CurrentUserPrivilegeSet => { + CommonPropertiesProp::CurrentUserPrivilegeSet(self.get_user_privileges(user)?) + } + CommonPropertiesPropName::Owner => { + CommonPropertiesProp::Owner(self.get_owner().map(|owner| { + Self::PrincipalResource::get_url(rmap, [owner]) + .unwrap() + .into() + })) + } + }) } fn get_prop( @@ -98,7 +157,10 @@ pub trait Resource: Clone + 'static { mut props: Vec<&str>, user: &User, rmap: &ResourceMap, - ) -> Result>, Self::Error> { + ) -> Result< + ResponseElement>>, + Self::Error, + > { if props.contains(&"propname") { if props.len() != 1 { // propname MUST be the only queried prop per spec @@ -106,18 +168,11 @@ pub trait Resource: Clone + 'static { Error::BadRequest("propname MUST be the only queried prop".to_owned()).into(), ); } - let mut props: Vec = Self::list_props() + let props: Vec = Self::list_props() .iter() .map(|&prop| prop.to_string()) .collect(); - for extension in Self::list_extensions() { - let ext_props: Vec = extension - .list_props() - .iter() - .map(|&prop| prop.to_string()) - .collect(); - props.extend(ext_props); - } + return Ok(ResponseElement { href: path.to_owned(), propstat: vec![PropstatWrapper::TagList(PropstatElement { @@ -134,40 +189,36 @@ pub trait Resource: Clone + 'static { Error::BadRequest("allprop MUST be the only queried prop".to_owned()).into(), ); } - props = Self::list_props().to_vec(); - for extension in Self::list_extensions() { - let ext_props: Vec<&str> = extension.list_props().into(); - props.extend(ext_props); - } + props = Self::list_props(); } - let mut prop_responses = Vec::new(); - - for extension in Self::list_extensions() { - let (ext_invalid_props, ext_responses) = extension.propfind(self, props, user, rmap)?; - props = ext_invalid_props; - prop_responses.extend(ext_responses); + let mut valid_props = vec![]; + let mut internal_props = vec![]; + let mut invalid_props = vec![]; + for prop in props { + if let Ok(valid_prop) = Self::PropName::from_str(prop) { + valid_props.push(valid_prop); + } else if let Ok(internal_prop) = CommonPropertiesPropName::from_str(prop) { + internal_props.push(internal_prop); + } else { + invalid_props.push(prop) + } } - let (valid_props, invalid_props): (Vec>, Vec>) = props + let internal_prop_responses: Vec<_> = internal_props + .into_iter() + .map(|prop| self.get_internal_prop(rmap, user, &prop)) + .collect::, Self::Error>>()? + .into_iter() + .map(EitherProp::Right) + .collect(); + + let mut prop_responses = valid_props .into_iter() - .map(|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 invalid_props: Vec<&str> = invalid_props.into_iter().flatten().collect(); - - prop_responses.extend( - valid_props - .into_iter() - .map(|prop| self.get_prop(rmap, user, &prop)) - .collect::, Self::Error>>()?, - ); + .map(|prop| self.get_prop(rmap, user, &prop)) + .map_ok(EitherProp::Left) + .collect::, Self::Error>>()?; + prop_responses.extend(internal_prop_responses); let mut propstats = vec![PropstatWrapper::Normal(PropstatElement { status: format!("HTTP/1.1 {}", StatusCode::OK), diff --git a/crates/dav/src/resources/root.rs b/crates/dav/src/resources/root.rs index 5bd234c..be19e47 100644 --- a/crates/dav/src/resources/root.rs +++ b/crates/dav/src/resources/root.rs @@ -1,10 +1,10 @@ -use crate::extensions::CommonPropertiesProp; use crate::privileges::UserPrivilegeSet; use crate::resource::{Resource, ResourceService}; use actix_web::dev::ResourceMap; use actix_web::HttpRequest; use async_trait::async_trait; use rustical_store::auth::User; +use serde::{Deserialize, Serialize}; use std::any::type_name; use std::marker::PhantomData; use strum::{EnumString, VariantNames}; @@ -22,9 +22,16 @@ impl Default for RootResource { #[strum(serialize_all = "kebab-case")] pub enum RootResourcePropName {} +#[derive(Deserialize, Serialize, Default, Clone, PartialEq)] +pub enum RootResourceProp { + #[serde(other)] + #[default] + Invalid, +} + impl Resource for RootResource { type PropName = RootResourcePropName; - type Prop = CommonPropertiesProp; + type Prop = RootResourceProp; type Error = PR::Error; type PrincipalResource = PR;