From 69163404a129df0da936d4b7d21936d2057f7521 Mon Sep 17 00:00:00 2001 From: Lennart K <18233294+lennart-k@users.noreply.github.com> Date: Fri, 18 Jul 2025 12:18:27 +0200 Subject: [PATCH] caldav: Add endpoint with simplified calendar-home-set --- crates/caldav/src/lib.rs | 34 ++++++++++---------------- crates/caldav/src/principal/mod.rs | 16 +++++++++--- crates/caldav/src/principal/prop.rs | 5 +++- crates/caldav/src/principal/service.rs | 4 +++ crates/caldav/src/principal/tests.rs | 1 + src/app.rs | 16 ++++++++++-- 6 files changed, 49 insertions(+), 27 deletions(-) diff --git a/crates/caldav/src/lib.rs b/crates/caldav/src/lib.rs index 3198808..72e5071 100644 --- a/crates/caldav/src/lib.rs +++ b/crates/caldav/src/lib.rs @@ -1,5 +1,3 @@ -use axum::response::Redirect; -use axum::routing::any; use axum::{Extension, Router}; use derive_more::Constructor; use principal::PrincipalResourceService; @@ -14,7 +12,6 @@ pub mod calendar; pub mod calendar_object; pub mod error; pub mod principal; - pub use error::Error; #[derive(Debug, Clone, Constructor)] @@ -34,23 +31,18 @@ pub fn caldav_router, store: Arc, subscription_store: Arc, + simplified_home_set: bool, ) -> Router { - let principal_service = PrincipalResourceService { - auth_provider: auth_provider.clone(), - sub_store: subscription_store.clone(), - cal_store: store.clone(), - }; - - Router::new() - .nest( - prefix, - RootResourceService::<_, Principal, CalDavPrincipalUri>::new(principal_service.clone()) - .axum_router() - .layer(AuthenticationLayer::new(auth_provider)) - .layer(Extension(CalDavPrincipalUri(prefix))), - ) - .route( - "/.well-known/caldav", - any(async || Redirect::permanent(prefix)), - ) + Router::new().nest( + prefix, + RootResourceService::<_, Principal, CalDavPrincipalUri>::new(PrincipalResourceService { + auth_provider: auth_provider.clone(), + sub_store: subscription_store.clone(), + cal_store: store.clone(), + simplified_home_set, + }) + .axum_router() + .layer(AuthenticationLayer::new(auth_provider)) + .layer(Extension(CalDavPrincipalUri(prefix))), + ) } diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index 434411c..33486db 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -18,6 +18,8 @@ pub mod tests; pub struct PrincipalResource { principal: Principal, members: Vec, + // If true only return the principal as the calendar home set, otherwise also groups + simplified_home_set: bool, } impl ResourceName for PrincipalResource { @@ -64,9 +66,17 @@ impl Resource for PrincipalResource { PrincipalPropName::PrincipalUrl => { PrincipalProp::PrincipalUrl(principal_url.into()) } - PrincipalPropName::CalendarHomeSet => { - PrincipalProp::CalendarHomeSet(principal_url.into()) - } + PrincipalPropName::CalendarHomeSet => PrincipalProp::CalendarHomeSet( + CalendarHomeSet(if self.simplified_home_set { + vec![principal_url.into()] + } else { + self.principal + .memberships() + .iter() + .map(|principal| puri.principal_uri(principal).into()) + .collect() + }), + ), PrincipalPropName::CalendarUserAddressSet => { PrincipalProp::CalendarUserAddressSet(principal_url.into()) } diff --git a/crates/caldav/src/principal/prop.rs b/crates/caldav/src/principal/prop.rs index 6bd16a5..2073cff 100644 --- a/crates/caldav/src/principal/prop.rs +++ b/crates/caldav/src/principal/prop.rs @@ -31,9 +31,12 @@ pub enum PrincipalProp { // CalDAV (RFC 4791) #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] - CalendarHomeSet(HrefElement), + CalendarHomeSet(CalendarHomeSet), } +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone)] +pub struct CalendarHomeSet(#[xml(ty = "untagged", flatten)] pub Vec); + #[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] #[xml(unit_variants_ident = "PrincipalPropWrapperName", untagged)] pub enum PrincipalPropWrapper { diff --git a/crates/caldav/src/principal/service.rs b/crates/caldav/src/principal/service.rs index f938ffb..0cc5596 100644 --- a/crates/caldav/src/principal/service.rs +++ b/crates/caldav/src/principal/service.rs @@ -18,6 +18,8 @@ pub struct PrincipalResourceService< pub(crate) auth_provider: Arc, pub(crate) sub_store: Arc, pub(crate) cal_store: Arc, + // If true only return the principal as the calendar home set, otherwise also groups + pub(crate) simplified_home_set: bool, } impl Clone @@ -28,6 +30,7 @@ impl Clone auth_provider: self.auth_provider.clone(), sub_store: self.sub_store.clone(), cal_store: self.cal_store.clone(), + simplified_home_set: self.simplified_home_set, } } } @@ -58,6 +61,7 @@ impl Resour Ok(PrincipalResource { members: self.auth_provider.list_members(&user.id).await?, principal: user, + simplified_home_set: self.simplified_home_set, }) } diff --git a/crates/caldav/src/principal/tests.rs b/crates/caldav/src/principal/tests.rs index c5f68a8..ccdd41a 100644 --- a/crates/caldav/src/principal/tests.rs +++ b/crates/caldav/src/principal/tests.rs @@ -27,6 +27,7 @@ async fn test_principal_resource( cal_store: Arc::new(cal_store.await), sub_store: Arc::new(sub_store.await), auth_provider: Arc::new(auth_provider.await), + simplified_home_set: false, }; assert!(matches!( diff --git a/src/app.rs b/src/app.rs index 8b2a42d..4fd1c9b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,8 +2,8 @@ use crate::config::NextcloudLoginConfig; use axum::Router; use axum::body::Body; use axum::extract::Request; -use axum::response::Response; -use axum::routing::options; +use axum::response::{Redirect, Response}; +use axum::routing::{any, options}; use headers::{HeaderMapExt, UserAgent}; use http::{HeaderValue, StatusCode}; use rustical_caldav::caldav_router; @@ -47,7 +47,19 @@ pub fn make_app( auth_provider.clone(), combined_cal_store.clone(), subscription_store.clone(), + false, )) + .merge(caldav_router( + "/caldav-compat", + auth_provider.clone(), + combined_cal_store.clone(), + subscription_store.clone(), + true, + )) + .route( + "/.well-known/caldav", + any(async || Redirect::permanent("/caldav")), + ) .merge(carddav_router( "/carddav", auth_provider.clone(),