From aa6bd1cbc06ce360ab0c69be41d8c3271842913c Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Sun, 2 Feb 2025 15:12:15 +0100 Subject: [PATCH] implement principal types --- crates/caldav/src/lib.rs | 8 +++-- crates/caldav/src/principal/mod.rs | 35 ++++++++++++++-------- crates/carddav/src/lib.rs | 5 ++-- crates/carddav/src/principal/mod.rs | 35 +++++++++++++--------- crates/store/src/auth/mod.rs | 2 ++ crates/store/src/auth/static_user_store.rs | 13 ++++++-- crates/store/src/auth/user_store.rs | 7 +++++ src/app.rs | 5 +++- src/main.rs | 1 + 9 files changed, 75 insertions(+), 36 deletions(-) create mode 100644 crates/store/src/auth/user_store.rs diff --git a/crates/caldav/src/lib.rs b/crates/caldav/src/lib.rs index fe6fcda..c832476 100644 --- a/crates/caldav/src/lib.rs +++ b/crates/caldav/src/lib.rs @@ -10,7 +10,7 @@ use calendar_set::CalendarSetResourceService; use principal::{PrincipalResource, PrincipalResourceService}; use rustical_dav::resource::{NamedRoute, ResourceService, ResourceServiceRoute}; use rustical_dav::resources::RootResourceService; -use rustical_store::auth::{AuthenticationMiddleware, AuthenticationProvider}; +use rustical_store::auth::{AuthenticationMiddleware, AuthenticationProvider, UserStore}; use rustical_store::{AddressbookStore, CalendarStore, ContactBirthdayStore, SubscriptionStore}; use std::sync::Arc; use subscription::subscription_resource; @@ -25,11 +25,13 @@ mod subscription; pub use error::Error; pub fn caldav_service< + US: UserStore, AP: AuthenticationProvider, AS: AddressbookStore, C: CalendarStore, S: SubscriptionStore, >( + user_store: Arc, auth_provider: Arc, store: Arc, addr_store: Arc, @@ -66,9 +68,9 @@ pub fn caldav_service< .service( web::scope("/principal").service( web::scope("/{principal}") - .service(PrincipalResourceService(&[ + .service(PrincipalResourceService{store: user_store, home_set: &[ ("calendar", false), ("birthdays", true) - ]).actix_resource().name(PrincipalResource::route_name())) + ]}.actix_resource().name(PrincipalResource::route_name())) .service(web::scope("/calendar") .service(CalendarSetResourceService::new(store.clone()).actix_resource()) .service( diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index 6595072..4c00d90 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use crate::calendar_set::CalendarSetResource; use crate::Error; use actix_web::dev::ResourceMap; @@ -7,12 +9,12 @@ use rustical_dav::privileges::UserPrivilegeSet; use rustical_dav::resource::{NamedRoute, Resource, ResourceService}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_store::auth::user::PrincipalType; -use rustical_store::auth::User; +use rustical_store::auth::{User, UserStore}; use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; #[derive(Clone)] pub struct PrincipalResource { - principal: String, + principal: User, home_set: &'static [(&'static str, bool)], } @@ -77,9 +79,8 @@ impl Resource for PrincipalResource { user: &User, prop: &PrincipalPropWrapperName, ) -> Result { - let principal_url = Self::get_url(rmap, vec![&self.principal]).unwrap(); + let principal_url = Self::get_url(rmap, vec![&self.principal.id]).unwrap(); - // BUG: We need to read the properties of the principal, not the requesting user let home_set = CalendarHomeSet( user.memberships() .into_iter() @@ -96,10 +97,10 @@ impl Resource for PrincipalResource { PrincipalPropWrapperName::Principal(prop) => { PrincipalPropWrapper::Principal(match prop { PrincipalPropName::CalendarUserType => { - PrincipalProp::CalendarUserType(user.user_type.to_owned()) + PrincipalProp::CalendarUserType(self.principal.user_type.to_owned()) } PrincipalPropName::Displayname => { - PrincipalProp::Displayname(self.principal.to_owned()) + PrincipalProp::Displayname(self.principal.id.to_owned()) } PrincipalPropName::PrincipalUrl => { PrincipalProp::PrincipalUrl(principal_url.into()) @@ -117,20 +118,23 @@ impl Resource for PrincipalResource { } fn get_owner(&self) -> Option<&str> { - Some(&self.principal) + Some(&self.principal.id) } fn get_user_privileges(&self, user: &User) -> Result { Ok(UserPrivilegeSet::owner_read( - user.is_principal(&self.principal), + user.is_principal(&self.principal.id), )) } } -pub struct PrincipalResourceService(pub &'static [(&'static str, bool)]); +pub struct PrincipalResourceService { + pub store: Arc, + pub home_set: &'static [(&'static str, bool)], +} #[async_trait(?Send)] -impl ResourceService for PrincipalResourceService { +impl ResourceService for PrincipalResourceService { type PathComponents = (String,); type MemberType = CalendarSetResource; type Resource = PrincipalResource; @@ -140,9 +144,14 @@ impl ResourceService for PrincipalResourceService { &self, (principal,): &Self::PathComponents, ) -> Result { + let user = self + .store + .get_user(principal) + .await? + .ok_or(crate::Error::NotFound)?; Ok(PrincipalResource { - principal: principal.to_owned(), - home_set: self.0, + principal: user, + home_set: self.home_set, }) } @@ -151,7 +160,7 @@ impl ResourceService for PrincipalResourceService { (principal,): &Self::PathComponents, ) -> Result, Self::Error> { Ok(self - .0 + .home_set .iter() .map(|&(set_name, read_only)| { ( diff --git a/crates/carddav/src/lib.rs b/crates/carddav/src/lib.rs index a7534f1..e1855fc 100644 --- a/crates/carddav/src/lib.rs +++ b/crates/carddav/src/lib.rs @@ -15,7 +15,7 @@ use principal::{PrincipalResource, PrincipalResourceService}; use rustical_dav::resource::{NamedRoute, ResourceService}; use rustical_dav::resources::RootResourceService; use rustical_store::{ - auth::{AuthenticationMiddleware, AuthenticationProvider}, + auth::{AuthenticationMiddleware, AuthenticationProvider, UserStore}, AddressbookStore, SubscriptionStore, }; use std::sync::Arc; @@ -27,6 +27,7 @@ pub mod principal; pub fn carddav_service( auth_provider: Arc, + user_store: Arc, store: Arc, subscription_store: Arc, ) -> impl HttpServiceFactory { @@ -59,7 +60,7 @@ pub fn carddav_service { +pub struct PrincipalResourceService { addr_store: Arc, + user_store: Arc, } -impl PrincipalResourceService { - pub fn new(addr_store: Arc) -> Self { - Self { addr_store } +impl PrincipalResourceService { + pub fn new(addr_store: Arc, user_store: Arc) -> Self { + Self { + addr_store, + user_store, + } } } #[derive(Debug, Clone)] pub struct PrincipalResource { - principal: String, + principal: User, } #[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone)] @@ -84,7 +88,7 @@ impl Resource for PrincipalResource { user: &User, prop: &PrincipalPropWrapperName, ) -> Result { - let principal_href = HrefElement::new(Self::get_principal_url(rmap, &self.principal)); + let principal_href = HrefElement::new(Self::get_principal_url(rmap, &self.principal.id)); let home_set = AddressbookHomeSet( user.memberships() @@ -98,7 +102,7 @@ impl Resource for PrincipalResource { PrincipalPropWrapperName::Principal(prop) => { PrincipalPropWrapper::Principal(match prop { PrincipalPropName::Displayname => { - PrincipalProp::Displayname(self.principal.to_owned()) + PrincipalProp::Displayname(self.principal.id.to_owned()) } PrincipalPropName::PrincipalUrl => PrincipalProp::PrincipalUrl(principal_href), PrincipalPropName::AddressbookHomeSet => { @@ -115,18 +119,18 @@ impl Resource for PrincipalResource { } fn get_owner(&self) -> Option<&str> { - Some(&self.principal) + Some(&self.principal.id) } fn get_user_privileges(&self, user: &User) -> Result { Ok(UserPrivilegeSet::owner_only( - user.is_principal(&self.principal), + user.is_principal(&self.principal.id), )) } } #[async_trait(?Send)] -impl ResourceService for PrincipalResourceService { +impl ResourceService for PrincipalResourceService { type PathComponents = (String,); type MemberType = AddressbookResource; type Resource = PrincipalResource; @@ -136,9 +140,12 @@ impl ResourceService for PrincipalResourceService { &self, (principal,): &Self::PathComponents, ) -> Result { - Ok(PrincipalResource { - principal: principal.to_owned(), - }) + let user = self + .user_store + .get_user(principal) + .await? + .ok_or(crate::Error::NotFound)?; + Ok(PrincipalResource { principal: user }) } async fn get_members( diff --git a/crates/store/src/auth/mod.rs b/crates/store/src/auth/mod.rs index b03a38d..28a8f6f 100644 --- a/crates/store/src/auth/mod.rs +++ b/crates/store/src/auth/mod.rs @@ -1,6 +1,7 @@ pub mod middleware; pub mod static_user_store; pub mod user; +mod user_store; use crate::error::Error; use async_trait::async_trait; @@ -12,3 +13,4 @@ pub trait AuthenticationProvider: 'static { pub use middleware::AuthenticationMiddleware; pub use static_user_store::{StaticUserStore, StaticUserStoreConfig}; pub use user::User; +pub use user_store::UserStore; diff --git a/crates/store/src/auth/static_user_store.rs b/crates/store/src/auth/static_user_store.rs index 11d02c6..c24e333 100644 --- a/crates/store/src/auth/static_user_store.rs +++ b/crates/store/src/auth/static_user_store.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use super::AuthenticationProvider; +use super::{AuthenticationProvider, UserStore}; #[derive(Debug, Clone, Deserialize, Serialize, Default)] pub struct StaticUserStoreConfig { @@ -23,11 +23,18 @@ impl StaticUserStore { } } +#[async_trait] +impl UserStore for StaticUserStore { + async fn get_user(&self, id: &str) -> Result, crate::Error> { + Ok(self.users.get(id).cloned()) + } +} + #[async_trait] impl AuthenticationProvider for StaticUserStore { async fn validate_user_token(&self, user_id: &str, token: &str) -> Result, Error> { - let user: User = match self.users.get(user_id) { - Some(user) => user.clone(), + let user: User = match self.get_user(user_id).await? { + Some(user) => user, None => return Ok(None), }; diff --git a/crates/store/src/auth/user_store.rs b/crates/store/src/auth/user_store.rs new file mode 100644 index 0000000..7c186e1 --- /dev/null +++ b/crates/store/src/auth/user_store.rs @@ -0,0 +1,7 @@ +use super::User; +use async_trait::async_trait; + +#[async_trait] +pub trait UserStore: 'static { + async fn get_user(&self, id: &str) -> Result, crate::Error>; +} diff --git a/src/app.rs b/src/app.rs index 8a46f2e..c8f953e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,7 +5,7 @@ use actix_web::{web, App}; use rustical_caldav::caldav_service; use rustical_carddav::carddav_service; use rustical_frontend::{configure_frontend, FrontendConfig}; -use rustical_store::auth::AuthenticationProvider; +use rustical_store::auth::{AuthenticationProvider, UserStore}; use rustical_store::{AddressbookStore, CalendarStore, SubscriptionStore}; use std::sync::Arc; use tracing_actix_web::TracingLogger; @@ -15,6 +15,7 @@ pub fn make_app( cal_store: Arc, subscription_store: Arc, auth_provider: Arc, + user_store: Arc, frontend_config: FrontendConfig, ) -> App< impl ServiceFactory< @@ -30,6 +31,7 @@ pub fn make_app( .wrap(TracingLogger::default()) .wrap(NormalizePath::trim()) .service(web::scope("/caldav").service(caldav_service( + user_store.clone(), auth_provider.clone(), cal_store.clone(), addr_store.clone(), @@ -37,6 +39,7 @@ pub fn make_app( ))) .service(web::scope("/carddav").service(carddav_service( auth_provider.clone(), + user_store.clone(), addr_store.clone(), subscription_store, ))) diff --git a/src/main.rs b/src/main.rs index 4bb099e..b6adb56 100644 --- a/src/main.rs +++ b/src/main.rs @@ -100,6 +100,7 @@ async fn main() -> Result<()> { cal_store.clone(), subscription_store.clone(), user_store.clone(), + user_store.clone(), config.frontend.clone(), ) })