implement principal types

This commit is contained in:
Lennart
2025-02-02 15:12:15 +01:00
parent bb8f2bb370
commit aa6bd1cbc0
9 changed files with 75 additions and 36 deletions

View File

@@ -10,7 +10,7 @@ use calendar_set::CalendarSetResourceService;
use principal::{PrincipalResource, PrincipalResourceService}; use principal::{PrincipalResource, PrincipalResourceService};
use rustical_dav::resource::{NamedRoute, ResourceService, ResourceServiceRoute}; use rustical_dav::resource::{NamedRoute, ResourceService, ResourceServiceRoute};
use rustical_dav::resources::RootResourceService; 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 rustical_store::{AddressbookStore, CalendarStore, ContactBirthdayStore, SubscriptionStore};
use std::sync::Arc; use std::sync::Arc;
use subscription::subscription_resource; use subscription::subscription_resource;
@@ -25,11 +25,13 @@ mod subscription;
pub use error::Error; pub use error::Error;
pub fn caldav_service< pub fn caldav_service<
US: UserStore,
AP: AuthenticationProvider, AP: AuthenticationProvider,
AS: AddressbookStore, AS: AddressbookStore,
C: CalendarStore, C: CalendarStore,
S: SubscriptionStore, S: SubscriptionStore,
>( >(
user_store: Arc<US>,
auth_provider: Arc<AP>, auth_provider: Arc<AP>,
store: Arc<C>, store: Arc<C>,
addr_store: Arc<AS>, addr_store: Arc<AS>,
@@ -66,9 +68,9 @@ pub fn caldav_service<
.service( .service(
web::scope("/principal").service( web::scope("/principal").service(
web::scope("/{principal}") web::scope("/{principal}")
.service(PrincipalResourceService(&[ .service(PrincipalResourceService{store: user_store, home_set: &[
("calendar", false), ("birthdays", true) ("calendar", false), ("birthdays", true)
]).actix_resource().name(PrincipalResource::route_name())) ]}.actix_resource().name(PrincipalResource::route_name()))
.service(web::scope("/calendar") .service(web::scope("/calendar")
.service(CalendarSetResourceService::new(store.clone()).actix_resource()) .service(CalendarSetResourceService::new(store.clone()).actix_resource())
.service( .service(

View File

@@ -1,3 +1,5 @@
use std::sync::Arc;
use crate::calendar_set::CalendarSetResource; use crate::calendar_set::CalendarSetResource;
use crate::Error; use crate::Error;
use actix_web::dev::ResourceMap; use actix_web::dev::ResourceMap;
@@ -7,12 +9,12 @@ use rustical_dav::privileges::UserPrivilegeSet;
use rustical_dav::resource::{NamedRoute, Resource, ResourceService}; use rustical_dav::resource::{NamedRoute, Resource, ResourceService};
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
use rustical_store::auth::user::PrincipalType; use rustical_store::auth::user::PrincipalType;
use rustical_store::auth::User; use rustical_store::auth::{User, UserStore};
use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize};
#[derive(Clone)] #[derive(Clone)]
pub struct PrincipalResource { pub struct PrincipalResource {
principal: String, principal: User,
home_set: &'static [(&'static str, bool)], home_set: &'static [(&'static str, bool)],
} }
@@ -77,9 +79,8 @@ impl Resource for PrincipalResource {
user: &User, user: &User,
prop: &PrincipalPropWrapperName, prop: &PrincipalPropWrapperName,
) -> Result<Self::Prop, Self::Error> { ) -> Result<Self::Prop, Self::Error> {
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( let home_set = CalendarHomeSet(
user.memberships() user.memberships()
.into_iter() .into_iter()
@@ -96,10 +97,10 @@ impl Resource for PrincipalResource {
PrincipalPropWrapperName::Principal(prop) => { PrincipalPropWrapperName::Principal(prop) => {
PrincipalPropWrapper::Principal(match prop { PrincipalPropWrapper::Principal(match prop {
PrincipalPropName::CalendarUserType => { PrincipalPropName::CalendarUserType => {
PrincipalProp::CalendarUserType(user.user_type.to_owned()) PrincipalProp::CalendarUserType(self.principal.user_type.to_owned())
} }
PrincipalPropName::Displayname => { PrincipalPropName::Displayname => {
PrincipalProp::Displayname(self.principal.to_owned()) PrincipalProp::Displayname(self.principal.id.to_owned())
} }
PrincipalPropName::PrincipalUrl => { PrincipalPropName::PrincipalUrl => {
PrincipalProp::PrincipalUrl(principal_url.into()) PrincipalProp::PrincipalUrl(principal_url.into())
@@ -117,20 +118,23 @@ impl Resource for PrincipalResource {
} }
fn get_owner(&self) -> Option<&str> { fn get_owner(&self) -> Option<&str> {
Some(&self.principal) Some(&self.principal.id)
} }
fn get_user_privileges(&self, user: &User) -> Result<UserPrivilegeSet, Self::Error> { fn get_user_privileges(&self, user: &User) -> Result<UserPrivilegeSet, Self::Error> {
Ok(UserPrivilegeSet::owner_read( 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<US: UserStore> {
pub store: Arc<US>,
pub home_set: &'static [(&'static str, bool)],
}
#[async_trait(?Send)] #[async_trait(?Send)]
impl ResourceService for PrincipalResourceService { impl<US: UserStore> ResourceService for PrincipalResourceService<US> {
type PathComponents = (String,); type PathComponents = (String,);
type MemberType = CalendarSetResource; type MemberType = CalendarSetResource;
type Resource = PrincipalResource; type Resource = PrincipalResource;
@@ -140,9 +144,14 @@ impl ResourceService for PrincipalResourceService {
&self, &self,
(principal,): &Self::PathComponents, (principal,): &Self::PathComponents,
) -> Result<Self::Resource, Self::Error> { ) -> Result<Self::Resource, Self::Error> {
let user = self
.store
.get_user(principal)
.await?
.ok_or(crate::Error::NotFound)?;
Ok(PrincipalResource { Ok(PrincipalResource {
principal: principal.to_owned(), principal: user,
home_set: self.0, home_set: self.home_set,
}) })
} }
@@ -151,7 +160,7 @@ impl ResourceService for PrincipalResourceService {
(principal,): &Self::PathComponents, (principal,): &Self::PathComponents,
) -> Result<Vec<(String, Self::MemberType)>, Self::Error> { ) -> Result<Vec<(String, Self::MemberType)>, Self::Error> {
Ok(self Ok(self
.0 .home_set
.iter() .iter()
.map(|&(set_name, read_only)| { .map(|&(set_name, read_only)| {
( (

View File

@@ -15,7 +15,7 @@ use principal::{PrincipalResource, PrincipalResourceService};
use rustical_dav::resource::{NamedRoute, ResourceService}; use rustical_dav::resource::{NamedRoute, ResourceService};
use rustical_dav::resources::RootResourceService; use rustical_dav::resources::RootResourceService;
use rustical_store::{ use rustical_store::{
auth::{AuthenticationMiddleware, AuthenticationProvider}, auth::{AuthenticationMiddleware, AuthenticationProvider, UserStore},
AddressbookStore, SubscriptionStore, AddressbookStore, SubscriptionStore,
}; };
use std::sync::Arc; use std::sync::Arc;
@@ -27,6 +27,7 @@ pub mod principal;
pub fn carddav_service<AP: AuthenticationProvider, A: AddressbookStore, S: SubscriptionStore>( pub fn carddav_service<AP: AuthenticationProvider, A: AddressbookStore, S: SubscriptionStore>(
auth_provider: Arc<AP>, auth_provider: Arc<AP>,
user_store: Arc<impl UserStore>,
store: Arc<A>, store: Arc<A>,
subscription_store: Arc<S>, subscription_store: Arc<S>,
) -> impl HttpServiceFactory { ) -> impl HttpServiceFactory {
@@ -59,7 +60,7 @@ pub fn carddav_service<AP: AuthenticationProvider, A: AddressbookStore, S: Subsc
web::scope("/principal").service( web::scope("/principal").service(
web::scope("/{principal}") web::scope("/{principal}")
.service( .service(
PrincipalResourceService::new(store.clone()) PrincipalResourceService::new(store.clone(), user_store)
.actix_resource() .actix_resource()
.name(PrincipalResource::route_name()), .name(PrincipalResource::route_name()),
) )

View File

@@ -6,24 +6,28 @@ use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp};
use rustical_dav::privileges::UserPrivilegeSet; use rustical_dav::privileges::UserPrivilegeSet;
use rustical_dav::resource::{NamedRoute, Resource, ResourceService}; use rustical_dav::resource::{NamedRoute, Resource, ResourceService};
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
use rustical_store::auth::User; use rustical_store::auth::{User, UserStore};
use rustical_store::AddressbookStore; use rustical_store::AddressbookStore;
use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize};
use std::sync::Arc; use std::sync::Arc;
pub struct PrincipalResourceService<A: AddressbookStore> { pub struct PrincipalResourceService<A: AddressbookStore, US: UserStore> {
addr_store: Arc<A>, addr_store: Arc<A>,
user_store: Arc<US>,
} }
impl<A: AddressbookStore> PrincipalResourceService<A> { impl<A: AddressbookStore, US: UserStore> PrincipalResourceService<A, US> {
pub fn new(addr_store: Arc<A>) -> Self { pub fn new(addr_store: Arc<A>, user_store: Arc<US>) -> Self {
Self { addr_store } Self {
addr_store,
user_store,
}
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PrincipalResource { pub struct PrincipalResource {
principal: String, principal: User,
} }
#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone)] #[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone)]
@@ -84,7 +88,7 @@ impl Resource for PrincipalResource {
user: &User, user: &User,
prop: &PrincipalPropWrapperName, prop: &PrincipalPropWrapperName,
) -> Result<Self::Prop, Self::Error> { ) -> Result<Self::Prop, Self::Error> {
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( let home_set = AddressbookHomeSet(
user.memberships() user.memberships()
@@ -98,7 +102,7 @@ impl Resource for PrincipalResource {
PrincipalPropWrapperName::Principal(prop) => { PrincipalPropWrapperName::Principal(prop) => {
PrincipalPropWrapper::Principal(match prop { PrincipalPropWrapper::Principal(match prop {
PrincipalPropName::Displayname => { PrincipalPropName::Displayname => {
PrincipalProp::Displayname(self.principal.to_owned()) PrincipalProp::Displayname(self.principal.id.to_owned())
} }
PrincipalPropName::PrincipalUrl => PrincipalProp::PrincipalUrl(principal_href), PrincipalPropName::PrincipalUrl => PrincipalProp::PrincipalUrl(principal_href),
PrincipalPropName::AddressbookHomeSet => { PrincipalPropName::AddressbookHomeSet => {
@@ -115,18 +119,18 @@ impl Resource for PrincipalResource {
} }
fn get_owner(&self) -> Option<&str> { fn get_owner(&self) -> Option<&str> {
Some(&self.principal) Some(&self.principal.id)
} }
fn get_user_privileges(&self, user: &User) -> Result<UserPrivilegeSet, Self::Error> { fn get_user_privileges(&self, user: &User) -> Result<UserPrivilegeSet, Self::Error> {
Ok(UserPrivilegeSet::owner_only( Ok(UserPrivilegeSet::owner_only(
user.is_principal(&self.principal), user.is_principal(&self.principal.id),
)) ))
} }
} }
#[async_trait(?Send)] #[async_trait(?Send)]
impl<A: AddressbookStore> ResourceService for PrincipalResourceService<A> { impl<A: AddressbookStore, US: UserStore> ResourceService for PrincipalResourceService<A, US> {
type PathComponents = (String,); type PathComponents = (String,);
type MemberType = AddressbookResource; type MemberType = AddressbookResource;
type Resource = PrincipalResource; type Resource = PrincipalResource;
@@ -136,9 +140,12 @@ impl<A: AddressbookStore> ResourceService for PrincipalResourceService<A> {
&self, &self,
(principal,): &Self::PathComponents, (principal,): &Self::PathComponents,
) -> Result<Self::Resource, Self::Error> { ) -> Result<Self::Resource, Self::Error> {
Ok(PrincipalResource { let user = self
principal: principal.to_owned(), .user_store
}) .get_user(principal)
.await?
.ok_or(crate::Error::NotFound)?;
Ok(PrincipalResource { principal: user })
} }
async fn get_members( async fn get_members(

View File

@@ -1,6 +1,7 @@
pub mod middleware; pub mod middleware;
pub mod static_user_store; pub mod static_user_store;
pub mod user; pub mod user;
mod user_store;
use crate::error::Error; use crate::error::Error;
use async_trait::async_trait; use async_trait::async_trait;
@@ -12,3 +13,4 @@ pub trait AuthenticationProvider: 'static {
pub use middleware::AuthenticationMiddleware; pub use middleware::AuthenticationMiddleware;
pub use static_user_store::{StaticUserStore, StaticUserStoreConfig}; pub use static_user_store::{StaticUserStore, StaticUserStoreConfig};
pub use user::User; pub use user::User;
pub use user_store::UserStore;

View File

@@ -3,7 +3,7 @@ use async_trait::async_trait;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use super::AuthenticationProvider; use super::{AuthenticationProvider, UserStore};
#[derive(Debug, Clone, Deserialize, Serialize, Default)] #[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct StaticUserStoreConfig { pub struct StaticUserStoreConfig {
@@ -23,11 +23,18 @@ impl StaticUserStore {
} }
} }
#[async_trait]
impl UserStore for StaticUserStore {
async fn get_user(&self, id: &str) -> Result<Option<User>, crate::Error> {
Ok(self.users.get(id).cloned())
}
}
#[async_trait] #[async_trait]
impl AuthenticationProvider for StaticUserStore { impl AuthenticationProvider for StaticUserStore {
async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error> { async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error> {
let user: User = match self.users.get(user_id) { let user: User = match self.get_user(user_id).await? {
Some(user) => user.clone(), Some(user) => user,
None => return Ok(None), None => return Ok(None),
}; };

View File

@@ -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<Option<User>, crate::Error>;
}

View File

@@ -5,7 +5,7 @@ use actix_web::{web, App};
use rustical_caldav::caldav_service; use rustical_caldav::caldav_service;
use rustical_carddav::carddav_service; use rustical_carddav::carddav_service;
use rustical_frontend::{configure_frontend, FrontendConfig}; use rustical_frontend::{configure_frontend, FrontendConfig};
use rustical_store::auth::AuthenticationProvider; use rustical_store::auth::{AuthenticationProvider, UserStore};
use rustical_store::{AddressbookStore, CalendarStore, SubscriptionStore}; use rustical_store::{AddressbookStore, CalendarStore, SubscriptionStore};
use std::sync::Arc; use std::sync::Arc;
use tracing_actix_web::TracingLogger; use tracing_actix_web::TracingLogger;
@@ -15,6 +15,7 @@ pub fn make_app<AS: AddressbookStore, CS: CalendarStore, S: SubscriptionStore>(
cal_store: Arc<CS>, cal_store: Arc<CS>,
subscription_store: Arc<S>, subscription_store: Arc<S>,
auth_provider: Arc<impl AuthenticationProvider>, auth_provider: Arc<impl AuthenticationProvider>,
user_store: Arc<impl UserStore>,
frontend_config: FrontendConfig, frontend_config: FrontendConfig,
) -> App< ) -> App<
impl ServiceFactory< impl ServiceFactory<
@@ -30,6 +31,7 @@ pub fn make_app<AS: AddressbookStore, CS: CalendarStore, S: SubscriptionStore>(
.wrap(TracingLogger::default()) .wrap(TracingLogger::default())
.wrap(NormalizePath::trim()) .wrap(NormalizePath::trim())
.service(web::scope("/caldav").service(caldav_service( .service(web::scope("/caldav").service(caldav_service(
user_store.clone(),
auth_provider.clone(), auth_provider.clone(),
cal_store.clone(), cal_store.clone(),
addr_store.clone(), addr_store.clone(),
@@ -37,6 +39,7 @@ pub fn make_app<AS: AddressbookStore, CS: CalendarStore, S: SubscriptionStore>(
))) )))
.service(web::scope("/carddav").service(carddav_service( .service(web::scope("/carddav").service(carddav_service(
auth_provider.clone(), auth_provider.clone(),
user_store.clone(),
addr_store.clone(), addr_store.clone(),
subscription_store, subscription_store,
))) )))

View File

@@ -100,6 +100,7 @@ async fn main() -> Result<()> {
cal_store.clone(), cal_store.clone(),
subscription_store.clone(), subscription_store.clone(),
user_store.clone(), user_store.clone(),
user_store.clone(),
config.frontend.clone(), config.frontend.clone(),
) )
}) })