diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs index 5675ca2..15c0842 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -15,7 +15,7 @@ use rustical_dav::extensions::{ CommonPropertiesExtension, CommonPropertiesProp, SyncTokenExtension, SyncTokenExtensionProp, }; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceService}; +use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceName, ResourceService}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_dav_push::DavPushExtension; use rustical_ical::CalDateTime; @@ -83,6 +83,12 @@ pub struct CalendarResource { pub read_only: bool, } +impl ResourceName for CalendarResource { + fn get_name(&self) -> String { + self.cal.id.to_owned() + } +} + impl From for Calendar { fn from(value: CalendarResource) -> Self { value.cal @@ -360,20 +366,15 @@ impl ResourceService for CalendarResourc async fn get_members( &self, (principal, cal_id): &Self::PathComponents, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { Ok(self .cal_store .get_objects(principal, cal_id) .await? .into_iter() - .map(|object| { - ( - format!("{}.ics", object.get_id()), - CalendarObjectResource { - object, - principal: principal.to_owned(), - }, - ) + .map(|object| CalendarObjectResource { + object, + principal: principal.to_owned(), }) .collect()) } diff --git a/crates/caldav/src/calendar_object/resource.rs b/crates/caldav/src/calendar_object/resource.rs index 50f5ee4..67cd361 100644 --- a/crates/caldav/src/calendar_object/resource.rs +++ b/crates/caldav/src/calendar_object/resource.rs @@ -10,7 +10,7 @@ use futures_util::future::BoxFuture; use rustical_dav::{ extensions::{CommonPropertiesExtension, CommonPropertiesProp}, privileges::UserPrivilegeSet, - resource::{AxumMethods, PrincipalUri, Resource, ResourceService}, + resource::{AxumMethods, PrincipalUri, Resource, ResourceName, ResourceService}, xml::Resourcetype, }; use rustical_ical::{CalendarObject, UtcDateTime}; @@ -20,24 +20,6 @@ use serde::{Deserialize, Deserializer}; use std::{convert::Infallible, sync::Arc}; use tower::Service; -pub struct CalendarObjectResourceService { - pub(crate) cal_store: Arc, -} - -impl Clone for CalendarObjectResourceService { - fn clone(&self) -> Self { - Self { - cal_store: self.cal_store.clone(), - } - } -} - -impl CalendarObjectResourceService { - pub fn new(cal_store: Arc) -> Self { - Self { cal_store } - } -} - #[derive(XmlDeserialize, Clone, Debug, PartialEq, Eq, Hash)] pub(crate) struct ExpandElement { #[xml(ty = "attr")] @@ -86,6 +68,12 @@ pub struct CalendarObjectResource { pub principal: String, } +impl ResourceName for CalendarObjectResource { + fn get_name(&self) -> String { + format!("{}.ics", self.object.get_id()) + } +} + impl Resource for CalendarObjectResource { type Prop = CalendarObjectPropWrapper; type Error = Error; @@ -163,6 +151,24 @@ pub struct CalendarObjectPathComponents { pub object_id: String, } +pub struct CalendarObjectResourceService { + pub(crate) cal_store: Arc, +} + +impl Clone for CalendarObjectResourceService { + fn clone(&self) -> Self { + Self { + cal_store: self.cal_store.clone(), + } + } +} + +impl CalendarObjectResourceService { + pub fn new(cal_store: Arc) -> Self { + Self { cal_store } + } +} + #[async_trait] impl ResourceService for CalendarObjectResourceService { type PathComponents = CalendarObjectPathComponents; diff --git a/crates/caldav/src/calendar_set/mod.rs b/crates/caldav/src/calendar_set/mod.rs index 2029cd1..22a6d9a 100644 --- a/crates/caldav/src/calendar_set/mod.rs +++ b/crates/caldav/src/calendar_set/mod.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use axum::Router; use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp}; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceService}; +use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceName, ResourceService}; use rustical_dav::xml::{Resourcetype, ResourcetypeInner}; use rustical_store::auth::User; use rustical_store::{CalendarStore, SubscriptionStore}; @@ -15,6 +15,13 @@ use std::sync::Arc; pub struct CalendarSetResource { pub(crate) principal: String, pub(crate) read_only: bool, + pub(crate) name: &'static str, +} + +impl ResourceName for CalendarSetResource { + fn get_name(&self) -> String { + self.name.to_owned() + } } #[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] @@ -105,24 +112,20 @@ impl ResourceService for CalendarSetReso Ok(CalendarSetResource { principal: principal.to_owned(), read_only: self.cal_store.is_read_only(), + name: self.name, }) } async fn get_members( &self, (principal,): &Self::PathComponents, - ) -> Result, Self::Error> { + ) -> 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(), - }, - ) + .map(|cal| CalendarResource { + cal, + read_only: self.cal_store.is_read_only(), }) .collect()) } diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index 6095391..22f8e26 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use axum::Router; use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp}; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceService}; +use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceName, ResourceService}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_store::auth::user::PrincipalType; use rustical_store::auth::{AuthenticationProvider, User}; @@ -18,6 +18,12 @@ pub struct PrincipalResource { home_set: &'static [&'static str], } +impl ResourceName for PrincipalResource { + fn get_name(&self) -> String { + self.principal.id.to_owned() + } +} + #[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone)] pub struct CalendarHomeSet(#[xml(ty = "untagged", flatten)] Vec); @@ -176,22 +182,18 @@ impl Result, Self::Error> { + ) -> Result, Self::Error> { Ok(vec![ - ( - "calendar".to_owned(), - CalendarSetResource { - principal: principal.to_owned(), - read_only: false, - }, - ), - ( - "birthdays".to_owned(), - CalendarSetResource { - principal: principal.to_owned(), - read_only: true, - }, - ), + CalendarSetResource { + name: "calendar", + principal: principal.to_owned(), + read_only: false, + }, + CalendarSetResource { + name: "birthdays", + principal: principal.to_owned(), + read_only: true, + }, ]) } diff --git a/crates/carddav/src/address_object/resource.rs b/crates/carddav/src/address_object/resource.rs index 1d49a9b..91284d9 100644 --- a/crates/carddav/src/address_object/resource.rs +++ b/crates/carddav/src/address_object/resource.rs @@ -6,7 +6,7 @@ use futures_util::future::BoxFuture; use rustical_dav::{ extensions::{CommonPropertiesExtension, CommonPropertiesProp}, privileges::UserPrivilegeSet, - resource::{AxumMethods, PrincipalUri, Resource, ResourceService}, + resource::{AxumMethods, PrincipalUri, Resource, ResourceName, ResourceService}, xml::Resourcetype, }; use rustical_ical::AddressObject; @@ -58,6 +58,12 @@ pub struct AddressObjectResource { pub principal: String, } +impl ResourceName for AddressObjectResource { + fn get_name(&self) -> String { + format!("{}.vcf", self.object.get_id()) + } +} + impl Resource for AddressObjectResource { type Prop = AddressObjectPropWrapper; type Error = Error; diff --git a/crates/carddav/src/addressbook/resource.rs b/crates/carddav/src/addressbook/resource.rs index 17eed53..7d5b71f 100644 --- a/crates/carddav/src/addressbook/resource.rs +++ b/crates/carddav/src/addressbook/resource.rs @@ -14,7 +14,7 @@ use rustical_dav::extensions::{ CommonPropertiesExtension, CommonPropertiesProp, SyncTokenExtension, SyncTokenExtensionProp, }; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceService}; +use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceName, ResourceService}; use rustical_dav::xml::{Resourcetype, ResourcetypeInner}; use rustical_dav_push::{DavPushExtension, DavPushExtensionProp}; use rustical_store::auth::User; @@ -77,6 +77,12 @@ pub enum AddressbookPropWrapper { #[derive(Clone, Debug, From, Into)] pub struct AddressbookResource(pub(crate) Addressbook); +impl ResourceName for AddressbookResource { + fn get_name(&self) -> String { + self.0.id.to_owned() + } +} + impl SyncTokenExtension for AddressbookResource { fn get_synctoken(&self) -> String { self.0.format_synctoken() @@ -228,20 +234,15 @@ impl ResourceService async fn get_members( &self, (principal, addressbook_id): &Self::PathComponents, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { Ok(self .addr_store .get_objects(principal, addressbook_id) .await? .into_iter() - .map(|object| { - ( - format!("{}.vcf", object.get_id()), - AddressObjectResource { - object, - principal: principal.to_owned(), - }, - ) + .map(|object| AddressObjectResource { + object, + principal: principal.to_owned(), }) .collect()) } diff --git a/crates/carddav/src/principal/mod.rs b/crates/carddav/src/principal/mod.rs index baf3b5d..b028f97 100644 --- a/crates/carddav/src/principal/mod.rs +++ b/crates/carddav/src/principal/mod.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use axum::Router; use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp}; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceService}; +use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceName, ResourceService}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_store::auth::{AuthenticationProvider, User}; use rustical_store::{AddressbookStore, SubscriptionStore}; @@ -50,6 +50,12 @@ pub struct PrincipalResource { principal: User, } +impl ResourceName for PrincipalResource { + fn get_name(&self) -> String { + self.principal.id.to_owned() + } +} + #[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone)] pub struct AddressbookHomeSet(#[xml(ty = "untagged", flatten)] Vec); @@ -168,11 +174,11 @@ impl Reso async fn get_members( &self, (principal,): &Self::PathComponents, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { let addressbooks = self.addr_store.get_addressbooks(principal).await?; Ok(addressbooks .into_iter() - .map(|addressbook| (addressbook.id.to_owned(), addressbook.into())) + .map(AddressbookResource::from) .collect()) } diff --git a/crates/dav/src/resource/methods/propfind.rs b/crates/dav/src/resource/methods/propfind.rs index a2b8147..56fab1e 100644 --- a/crates/dav/src/resource/methods/propfind.rs +++ b/crates/dav/src/resource/methods/propfind.rs @@ -3,6 +3,7 @@ use crate::header::Depth; use crate::privileges::UserPrivilege; use crate::resource::PrincipalUri; use crate::resource::Resource; +use crate::resource::ResourceName; use crate::resource::ResourceService; use crate::xml::MultistatusElement; use crate::xml::PropfindElement; @@ -12,6 +13,11 @@ use rustical_xml::PropName; use rustical_xml::XmlDocument; use tracing::instrument; +type RSMultistatus = MultistatusElement< + <::Resource as Resource>::Prop, + <::MemberType as Resource>::Prop, +>; + #[instrument(skip(path, resource_service, puri))] pub(crate) async fn axum_route_propfind( Path(path): Path, @@ -21,10 +27,7 @@ pub(crate) async fn axum_route_propfind( uri: OriginalUri, Extension(puri): Extension, body: String, -) -> Result< - MultistatusElement<::Prop, ::Prop>, - R::Error, -> { +) -> Result, R::Error> { route_propfind::( &path, uri.path(), @@ -45,10 +48,7 @@ pub(crate) async fn route_propfind( depth: &Depth, resource_service: &R, puri: &impl PrincipalUri, -) -> Result< - MultistatusElement<::Prop, ::Prop>, - R::Error, -> { +) -> Result, R::Error> { let resource = resource_service.get_resource(path_components).await?; let privileges = resource.get_user_privileges(principal)?; if !privileges.has(&UserPrivilege::Read) { @@ -75,9 +75,9 @@ pub(crate) async fn route_propfind( let mut member_responses = Vec::new(); if depth != &Depth::Zero { - for (subpath, member) in resource_service.get_members(path_components).await? { + for member in resource_service.get_members(path_components).await? { member_responses.push(member.propfind_typed( - &format!("{}/{}", path.trim_end_matches('/'), subpath), + &format!("{}/{}", path.trim_end_matches('/'), member.get_name()), &propfind_member.prop, puri, principal, diff --git a/crates/dav/src/resource/mod.rs b/crates/dav/src/resource/mod.rs index 676939f..f73e1a9 100644 --- a/crates/dav/src/resource/mod.rs +++ b/crates/dav/src/resource/mod.rs @@ -28,6 +28,10 @@ impl ResourceProp for T {} pub trait ResourcePropName: FromStr {} impl ResourcePropName for T {} +pub trait ResourceName { + fn get_name(&self) -> String; +} + pub trait Resource: Clone + Send + 'static { type Prop: ResourceProp + PartialEq + Clone + EnumVariants + PropName + Send; type Error: From; diff --git a/crates/dav/src/resource/resource_service.rs b/crates/dav/src/resource/resource_service.rs index b1d9b46..83c8ccb 100644 --- a/crates/dav/src/resource/resource_service.rs +++ b/crates/dav/src/resource/resource_service.rs @@ -10,7 +10,8 @@ use serde::Deserialize; #[async_trait] pub trait ResourceService: Clone + Sized + Send + Sync + AxumMethods + 'static { type PathComponents: for<'de> Deserialize<'de> + Sized + Send + Sync + Clone + 'static; // defines how the resource URI maps to parameters, i.e. /{principal}/{calendar} -> (String, String) - type MemberType: Resource; + type MemberType: Resource + + super::ResourceName; type Resource: Resource; type Error: From + Send + Sync + IntoResponse + 'static; type Principal: Principal + FromRequestParts; @@ -21,7 +22,7 @@ pub trait ResourceService: Clone + Sized + Send + Sync + AxumMethods + 'static { async fn get_members( &self, _path: &Self::PathComponents, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { Ok(vec![]) } diff --git a/crates/dav/src/resources/root.rs b/crates/dav/src/resources/root.rs index 75ae02b..bb0341b 100644 --- a/crates/dav/src/resources/root.rs +++ b/crates/dav/src/resources/root.rs @@ -3,7 +3,7 @@ use crate::extensions::{ CommonPropertiesExtension, CommonPropertiesProp, CommonPropertiesPropName, }; use crate::privileges::UserPrivilegeSet; -use crate::resource::{AxumMethods, PrincipalUri, Resource, ResourceService}; +use crate::resource::{AxumMethods, PrincipalUri, Resource, ResourceName, ResourceService}; use crate::xml::{Resourcetype, ResourcetypeInner}; use async_trait::async_trait; use axum::Router; @@ -66,6 +66,8 @@ impl< P: Principal + FromRequestParts, PURI: PrincipalUri, > ResourceService for RootResourceService +where + PRS::Resource: ResourceName, { type PathComponents = (); type MemberType = PRS::Resource; diff --git a/crates/frontend/src/nextcloud_login/mod.rs b/crates/frontend/src/nextcloud_login/mod.rs index 06115da..9e8e6cb 100644 --- a/crates/frontend/src/nextcloud_login/mod.rs +++ b/crates/frontend/src/nextcloud_login/mod.rs @@ -46,10 +46,9 @@ pub struct NextcloudFlows { flows: RwLock>, } -pub fn nextcloud_login_router( - nextcloud_flows_state: Arc, - auth_provider: Arc, -) -> Router { +pub fn nextcloud_login_router(auth_provider: Arc) -> Router { + let nextcloud_flows = Arc::new(NextcloudFlows::default()); + Router::new() .route("/poll/{flow}", post(post_nextcloud_poll::)) .route( @@ -57,7 +56,7 @@ pub fn nextcloud_login_router( get(get_nextcloud_flow).post(post_nextcloud_flow), ) .route("/", post(post_nextcloud_login)) - .layer(Extension(nextcloud_flows_state)) + .layer(Extension(nextcloud_flows)) .layer(Extension(auth_provider.clone())) .layer(AuthenticationLayer::new(auth_provider.clone())) .layer(middleware::from_fn(unauthorized_handler)) diff --git a/src/app.rs b/src/app.rs index 85c7d99..ce6b042 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,3 +1,4 @@ +use crate::config::NextcloudLoginConfig; use axum::Router; use axum::extract::Request; use axum::response::Response; @@ -5,7 +6,7 @@ use headers::{HeaderMapExt, UserAgent}; use http::StatusCode; use rustical_caldav::caldav_router; use rustical_carddav::carddav_router; -use rustical_frontend::nextcloud_login::{NextcloudFlows, nextcloud_login_router}; +use rustical_frontend::nextcloud_login::nextcloud_login_router; use rustical_frontend::{FrontendConfig, frontend_router}; use rustical_oidc::OidcConfig; use rustical_store::auth::AuthenticationProvider; @@ -20,8 +21,6 @@ use tower_sessions::{Expiry, MemoryStore, SessionManagerLayer}; use tracing::Span; use tracing::field::display; -use crate::config::NextcloudLoginConfig; - #[allow(clippy::too_many_arguments)] pub fn make_app( addr_store: Arc, @@ -31,7 +30,6 @@ pub fn make_app( frontend_config: FrontendConfig, oidc_config: Option, nextcloud_login_config: NextcloudLoginConfig, - nextcloud_flows_state: Arc, ) -> Router<()> { let mut router = Router::new() .merge(caldav_router( @@ -63,7 +61,7 @@ pub fn make_app( if nextcloud_login_config.enabled { router = router.nest( "/index.php/login/v2", - nextcloud_login_router(nextcloud_flows_state, auth_provider.clone()), + nextcloud_login_router(auth_provider.clone()), ); } router diff --git a/src/main.rs b/src/main.rs index 1d20350..cde30ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,6 @@ use config::{DataStoreConfig, SqliteDataStoreConfig}; use figment::Figment; use figment::providers::{Env, Format, Toml}; use rustical_dav_push::DavPushController; -use rustical_frontend::nextcloud_login::NextcloudFlows; use rustical_store::auth::AuthenticationProvider; use rustical_store::{AddressbookStore, CalendarStore, CollectionOperation, SubscriptionStore}; use rustical_store_sqlite::addressbook_store::SqliteAddressbookStore; @@ -110,8 +109,6 @@ async fn main() -> Result<()> { })); } - let nextcloud_flows = Arc::new(NextcloudFlows::default()); - let app = make_app( addr_store.clone(), cal_store.clone(), @@ -120,7 +117,6 @@ async fn main() -> Result<()> { config.frontend.clone(), config.oidc.clone(), config.nextcloud_login.clone(), - nextcloud_flows.clone(), ); let app = ServiceExt::::into_make_service( NormalizePathLayer::trim_trailing_slash().layer(app),