dav: Make reusable for other projects

This commit is contained in:
Lennart
2025-04-18 13:26:44 +02:00
parent 626eff0373
commit 54e327d764
36 changed files with 210 additions and 121 deletions

View File

@@ -27,3 +27,4 @@ chrono-tz = { workspace = true }
sha2 = { workspace = true }
rustical_xml.workspace = true
uuid.workspace = true
rustical_dav_push.workspace = true

View File

@@ -1,11 +1,11 @@
use crate::calendar::resource::CalendarResource;
use crate::Error;
use crate::calendar::resource::CalendarResource;
use actix_web::http::header;
use actix_web::web::{Data, Path};
use actix_web::{HttpRequest, HttpResponse};
use rustical_dav::privileges::UserPrivilege;
use rustical_dav::push::PushRegister;
use rustical_dav::resource::Resource;
use rustical_dav_push::register::PushRegister;
use rustical_store::auth::User;
use rustical_store::{CalendarStore, Subscription, SubscriptionStore};
use rustical_xml::XmlDocument;

View File

@@ -2,9 +2,9 @@ use super::methods::mkcalendar::route_mkcalendar;
use super::methods::post::route_post;
use super::methods::report::route_report_calendar;
use super::prop::{SupportedCalendarComponentSet, SupportedCalendarData, SupportedReportSet};
use crate::Error;
use crate::calendar_object::resource::CalendarObjectResource;
use crate::principal::PrincipalResource;
use crate::Error;
use actix_web::dev::ResourceMap;
use actix_web::http::Method;
use actix_web::web;
@@ -12,12 +12,12 @@ use async_trait::async_trait;
use chrono::{DateTime, Utc};
use derive_more::derive::{From, Into};
use rustical_dav::extensions::{
CommonPropertiesExtension, CommonPropertiesProp, DavPushExtension, DavPushExtensionProp,
SyncTokenExtension, SyncTokenExtensionProp,
CommonPropertiesExtension, CommonPropertiesProp, SyncTokenExtension, SyncTokenExtensionProp,
};
use rustical_dav::privileges::UserPrivilegeSet;
use rustical_dav::resource::{Resource, ResourceService};
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
use rustical_dav_push::{DavPushExtension, DavPushExtensionProp};
use rustical_store::auth::User;
use rustical_store::calendar::CalDateTime;
use rustical_store::{Calendar, CalendarStore, SubscriptionStore};
@@ -104,6 +104,7 @@ impl Resource for CalendarResource {
type Prop = CalendarPropWrapper;
type Error = Error;
type PrincipalResource = PrincipalResource;
type Principal = User;
fn get_resourcetype(&self) -> Resourcetype {
if self.cal.subscription_url.is_none() {
@@ -331,6 +332,7 @@ impl<C: CalendarStore, S: SubscriptionStore> ResourceService for CalendarResourc
type PathComponents = (String, String); // principal, calendar_id
type Resource = CalendarResource;
type Error = Error;
type Principal = User;
async fn get_resource(
&self,

View File

@@ -1,5 +1,5 @@
use super::methods::{get_event, put_event};
use crate::{principal::PrincipalResource, Error};
use crate::{Error, principal::PrincipalResource};
use actix_web::dev::ResourceMap;
use async_trait::async_trait;
use derive_more::derive::{From, Into};
@@ -9,7 +9,7 @@ use rustical_dav::{
resource::{Resource, ResourceService},
xml::Resourcetype,
};
use rustical_store::{auth::User, CalendarObject, CalendarStore};
use rustical_store::{CalendarObject, CalendarStore, auth::User};
use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize};
use serde::Deserialize;
use std::sync::Arc;
@@ -55,6 +55,7 @@ impl Resource for CalendarObjectResource {
type Prop = CalendarObjectPropWrapper;
type Error = Error;
type PrincipalResource = PrincipalResource;
type Principal = User;
fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[])
@@ -132,6 +133,7 @@ impl<C: CalendarStore> ResourceService for CalendarObjectResourceService<C> {
type Resource = CalendarObjectResource;
type MemberType = CalendarObjectResource;
type Error = Error;
type Principal = User;
async fn get_resource(
&self,

View File

@@ -1,14 +1,14 @@
use crate::Error;
use crate::calendar::resource::CalendarResource;
use crate::principal::PrincipalResource;
use crate::Error;
use actix_web::dev::ResourceMap;
use async_trait::async_trait;
use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp};
use rustical_dav::privileges::UserPrivilegeSet;
use rustical_dav::resource::{Resource, ResourceService};
use rustical_dav::xml::{Resourcetype, ResourcetypeInner};
use rustical_store::auth::User;
use rustical_store::CalendarStore;
use rustical_store::auth::User;
use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize};
use std::sync::Arc;
@@ -28,6 +28,7 @@ impl Resource for CalendarSetResource {
type Prop = PrincipalPropWrapper;
type Error = Error;
type PrincipalResource = PrincipalResource;
type Principal = User;
fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[ResourcetypeInner(
@@ -78,6 +79,7 @@ impl<C: CalendarStore> ResourceService for CalendarSetResourceService<C> {
type MemberType = CalendarResource;
type Resource = CalendarSetResource;
type Error = Error;
type Principal = User;
async fn get_resource(
&self,

View File

@@ -1,16 +1,16 @@
use actix_web::HttpResponse;
use actix_web::dev::{HttpServiceFactory, ServiceResponse};
use actix_web::http::header::{HeaderName, HeaderValue};
use actix_web::http::{Method, StatusCode};
use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers};
use actix_web::web::{self, Data};
use actix_web::HttpResponse;
use calendar::resource::CalendarResourceService;
use calendar_object::resource::CalendarObjectResourceService;
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, User};
use rustical_store::{AddressbookStore, CalendarStore, ContactBirthdayStore, SubscriptionStore};
use std::sync::Arc;
use subscription::subscription_resource;
@@ -62,7 +62,7 @@ pub fn caldav_service<
.app_data(Data::from(store.clone()))
.app_data(Data::from(birthday_store.clone()))
.app_data(Data::from(subscription_store))
.service(RootResourceService::<PrincipalResource>::default().actix_resource())
.service(RootResourceService::<PrincipalResource, User>::default().actix_resource())
.service(
web::scope("/principal").service(
web::scope("/{principal}")

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use crate::calendar_set::CalendarSetResource;
use crate::Error;
use crate::calendar_set::CalendarSetResource;
use actix_web::dev::ResourceMap;
use async_trait::async_trait;
use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp};
@@ -65,6 +65,7 @@ impl Resource for PrincipalResource {
type Prop = PrincipalPropWrapper;
type Error = Error;
type PrincipalResource = PrincipalResource;
type Principal = User;
fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[
@@ -142,6 +143,7 @@ impl<AP: AuthenticationProvider> ResourceService for PrincipalResourceService<AP
type MemberType = CalendarSetResource;
type Resource = PrincipalResource;
type Error = Error;
type Principal = User;
async fn get_resource(
&self,

View File

@@ -25,3 +25,4 @@ rustical_store = { workspace = true }
chrono = { workspace = true }
rustical_xml.workspace = true
uuid.workspace = true
rustical_dav_push.workspace = true

View File

@@ -1,4 +1,4 @@
use crate::{principal::PrincipalResource, Error};
use crate::{Error, principal::PrincipalResource};
use actix_web::dev::ResourceMap;
use async_trait::async_trait;
use derive_more::derive::{Constructor, From, Into};
@@ -8,7 +8,7 @@ use rustical_dav::{
resource::{Resource, ResourceService},
xml::Resourcetype,
};
use rustical_store::{auth::User, AddressObject, AddressbookStore};
use rustical_store::{AddressObject, AddressbookStore, auth::User};
use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize};
use serde::Deserialize;
use std::sync::Arc;
@@ -51,6 +51,7 @@ impl Resource for AddressObjectResource {
type Prop = AddressObjectPropWrapper;
type Error = Error;
type PrincipalResource = PrincipalResource;
type Principal = User;
fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[])
@@ -128,6 +129,7 @@ impl<AS: AddressbookStore> ResourceService for AddressObjectResourceService<AS>
type Resource = AddressObjectResource;
type MemberType = AddressObjectResource;
type Error = Error;
type Principal = User;
async fn get_resource(
&self,

View File

@@ -2,7 +2,7 @@ use crate::Error;
use actix_web::http::header;
use actix_web::web::{Data, Path};
use actix_web::{HttpRequest, HttpResponse};
use rustical_dav::push::PushRegister;
use rustical_dav_push::register::PushRegister;
use rustical_store::auth::User;
use rustical_store::{AddressbookStore, Subscription, SubscriptionStore};
use rustical_xml::XmlDocument;

View File

@@ -2,21 +2,21 @@ use super::methods::mkcol::route_mkcol;
use super::methods::post::route_post;
use super::methods::report::route_report_addressbook;
use super::prop::{SupportedAddressData, SupportedReportSet};
use crate::Error;
use crate::address_object::resource::AddressObjectResource;
use crate::principal::PrincipalResource;
use crate::Error;
use actix_web::dev::ResourceMap;
use actix_web::http::Method;
use actix_web::web;
use async_trait::async_trait;
use derive_more::derive::{From, Into};
use rustical_dav::extensions::{
CommonPropertiesExtension, CommonPropertiesProp, DavPushExtension, DavPushExtensionProp,
SyncTokenExtension, SyncTokenExtensionProp,
CommonPropertiesExtension, CommonPropertiesProp, SyncTokenExtension, SyncTokenExtensionProp,
};
use rustical_dav::privileges::UserPrivilegeSet;
use rustical_dav::resource::{Resource, ResourceService};
use rustical_dav::xml::{Resourcetype, ResourcetypeInner};
use rustical_dav_push::{DavPushExtension, DavPushExtensionProp};
use rustical_store::auth::User;
use rustical_store::{Addressbook, AddressbookStore, SubscriptionStore};
use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize};
@@ -84,6 +84,7 @@ impl Resource for AddressbookResource {
type Prop = AddressbookPropWrapper;
type Error = Error;
type PrincipalResource = PrincipalResource;
type Principal = User;
fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[
@@ -199,6 +200,7 @@ impl<AS: AddressbookStore, S: SubscriptionStore> ResourceService
type PathComponents = (String, String); // principal, addressbook_id
type Resource = AddressbookResource;
type Error = Error;
type Principal = User;
async fn get_resource(
&self,

View File

@@ -1,12 +1,12 @@
use actix_web::{
HttpResponse,
dev::{HttpServiceFactory, ServiceResponse},
http::{
header::{HeaderName, HeaderValue},
Method, StatusCode,
header::{HeaderName, HeaderValue},
},
middleware::{ErrorHandlerResponse, ErrorHandlers},
web::{self, Data},
HttpResponse,
};
use address_object::resource::AddressObjectResourceService;
use addressbook::resource::AddressbookResourceService;
@@ -15,8 +15,8 @@ use principal::{PrincipalResource, PrincipalResourceService};
use rustical_dav::resource::{NamedRoute, ResourceService};
use rustical_dav::resources::RootResourceService;
use rustical_store::{
auth::{AuthenticationMiddleware, AuthenticationProvider},
AddressbookStore, SubscriptionStore,
auth::{AuthenticationMiddleware, AuthenticationProvider, User},
};
use std::sync::Arc;
@@ -54,7 +54,7 @@ pub fn carddav_service<AP: AuthenticationProvider, A: AddressbookStore, S: Subsc
)
.app_data(Data::from(store.clone()))
.app_data(Data::from(subscription_store))
.service(RootResourceService::<PrincipalResource>::default().actix_resource())
.service(RootResourceService::<PrincipalResource, User>::default().actix_resource())
.service(
web::scope("/principal").service(
web::scope("/{principal}")

View File

@@ -1,13 +1,13 @@
use crate::addressbook::resource::AddressbookResource;
use crate::Error;
use crate::addressbook::resource::AddressbookResource;
use actix_web::dev::ResourceMap;
use async_trait::async_trait;
use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp};
use rustical_dav::privileges::UserPrivilegeSet;
use rustical_dav::resource::{NamedRoute, Resource, ResourceService};
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
use rustical_store::auth::{AuthenticationProvider, User};
use rustical_store::AddressbookStore;
use rustical_store::auth::{AuthenticationProvider, User};
use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize};
use std::sync::Arc;
@@ -74,6 +74,7 @@ impl Resource for PrincipalResource {
type Prop = PrincipalPropWrapper;
type Error = Error;
type PrincipalResource = PrincipalResource;
type Principal = User;
fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[
@@ -140,6 +141,7 @@ impl<A: AddressbookStore, AP: AuthenticationProvider> ResourceService
type MemberType = AddressbookResource;
type Resource = PrincipalResource;
type Error = Error;
type Principal = User;
async fn get_resource(
&self,

View File

@@ -12,7 +12,6 @@ actix-web = { workspace = true }
async-trait = { workspace = true }
futures-util = { workspace = true }
quick-xml = { workspace = true }
rustical_store = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }
itertools = { workspace = true }

View File

@@ -1,10 +1,10 @@
use crate::{
Principal,
privileges::UserPrivilegeSet,
resource::{NamedRoute, Resource},
xml::{HrefElement, Resourcetype},
};
use actix_web::dev::ResourceMap;
use rustical_store::auth::User;
use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize};
#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumUnitVariants, EnumVariants)]
@@ -31,7 +31,7 @@ pub trait CommonPropertiesExtension: Resource {
fn get_prop(
&self,
rmap: &ResourceMap,
user: &User,
principal: &Self::Principal,
prop: &CommonPropertiesPropName,
) -> Result<CommonPropertiesProp, <Self as Resource>::Error> {
Ok(match prop {
@@ -40,13 +40,13 @@ pub trait CommonPropertiesExtension: Resource {
}
CommonPropertiesPropName::CurrentUserPrincipal => {
CommonPropertiesProp::CurrentUserPrincipal(
Self::PrincipalResource::get_url(rmap, [&user.id])
Self::PrincipalResource::get_url(rmap, [&principal.get_id()])
.unwrap()
.into(),
)
}
CommonPropertiesPropName::CurrentUserPrivilegeSet => {
CommonPropertiesProp::CurrentUserPrivilegeSet(self.get_user_privileges(user)?)
CommonPropertiesProp::CurrentUserPrivilegeSet(self.get_user_privileges(principal)?)
}
CommonPropertiesPropName::Owner => {
CommonPropertiesProp::Owner(self.get_owner().map(|owner| {

View File

@@ -1,7 +1,5 @@
mod common;
mod davpush;
mod synctoken;
pub use common::*;
pub use davpush::*;
pub use synctoken::*;

View File

@@ -3,9 +3,13 @@ pub mod error;
pub mod extensions;
pub mod namespace;
pub mod privileges;
pub mod push;
pub mod resource;
pub mod resources;
pub mod xml;
use actix_web::FromRequest;
pub use error::Error;
pub trait Principal: std::fmt::Debug + Clone + FromRequest + 'static {
fn get_id(&self) -> &str;
}

View File

@@ -1,7 +0,0 @@
mod prop;
mod push_notifier;
mod push_register;
pub use prop::*;
pub use push_notifier::push_notifier;
pub use push_register::*;

View File

@@ -1,16 +1,15 @@
use crate::Error;
use crate::privileges::UserPrivilege;
use crate::resource::Resource;
use crate::resource::ResourceService;
use crate::Error;
use actix_web::HttpRequest;
use actix_web::HttpResponse;
use actix_web::Responder;
use actix_web::http::header::IfMatch;
use actix_web::http::header::IfNoneMatch;
use actix_web::web;
use actix_web::web::Data;
use actix_web::web::Path;
use actix_web::HttpRequest;
use actix_web::HttpResponse;
use actix_web::Responder;
use rustical_store::auth::User;
use tracing::instrument;
use tracing_actix_web::RootSpan;
@@ -18,7 +17,7 @@ use tracing_actix_web::RootSpan;
pub async fn route_delete<R: ResourceService>(
path: Path<R::PathComponents>,
req: HttpRequest,
user: User,
principal: R::Principal,
resource_service: Data<R>,
root_span: RootSpan,
if_match: web::Header<IfMatch>,
@@ -32,7 +31,7 @@ pub async fn route_delete<R: ResourceService>(
let resource = resource_service.get_resource(&path).await?;
let privileges = resource.get_user_privileges(&user)?;
let privileges = resource.get_user_privileges(&principal)?;
if !privileges.has(&UserPrivilege::Write) {
return Err(Error::Unauthorized.into());
}

View File

@@ -1,3 +1,4 @@
use crate::Error;
use crate::depth_header::Depth;
use crate::privileges::UserPrivilege;
use crate::resource::Resource;
@@ -6,11 +7,9 @@ use crate::xml::MultistatusElement;
use crate::xml::PropElement;
use crate::xml::PropfindElement;
use crate::xml::PropfindType;
use crate::Error;
use actix_web::HttpRequest;
use actix_web::web::Data;
use actix_web::web::Path;
use actix_web::HttpRequest;
use rustical_store::auth::User;
use rustical_xml::XmlDocument;
use tracing::instrument;
use tracing_actix_web::RootSpan;
@@ -21,7 +20,7 @@ pub(crate) async fn route_propfind<R: ResourceService>(
path: Path<R::PathComponents>,
body: String,
req: HttpRequest,
user: User,
user: R::Principal,
depth: Depth,
root_span: RootSpan,
resource_service: Data<R>,

View File

@@ -1,15 +1,14 @@
use crate::Error;
use crate::privileges::UserPrivilege;
use crate::resource::Resource;
use crate::resource::ResourceService;
use crate::xml::multistatus::{PropstatElement, PropstatWrapper, ResponseElement};
use crate::xml::MultistatusElement;
use crate::xml::TagList;
use crate::Error;
use crate::xml::multistatus::{PropstatElement, PropstatWrapper, ResponseElement};
use actix_web::http::StatusCode;
use actix_web::web::Data;
use actix_web::{web::Path, HttpRequest};
use actix_web::{HttpRequest, web::Path};
use quick_xml::name::Namespace;
use rustical_store::auth::User;
use rustical_xml::EnumUnitVariants;
use rustical_xml::Unparsed;
use rustical_xml::XmlDeserialize;
@@ -69,7 +68,7 @@ pub(crate) async fn route_proppatch<R: ResourceService>(
path: Path<R::PathComponents>,
body: String,
req: HttpRequest,
user: User,
principal: R::Principal,
root_span: RootSpan,
resource_service: Data<R>,
) -> Result<MultistatusElement<String, String>, R::Error> {
@@ -81,7 +80,7 @@ pub(crate) async fn route_proppatch<R: ResourceService>(
) = XmlDocument::parse_str(&body).map_err(Error::XmlError)?;
let mut resource = resource_service.get_resource(&path).await?;
let privileges = resource.get_user_privileges(&user)?;
let privileges = resource.get_user_privileges(&principal)?;
if !privileges.has(&UserPrivilege::Write) {
return Err(Error::Unauthorized.into());
}
@@ -131,7 +130,7 @@ pub(crate) async fn route_proppatch<R: ResourceService>(
}
}
Operation::Remove(remove_el) => {
let propname = remove_el.prop.0 .0;
let propname = remove_el.prop.0.0;
match <<R::Resource as Resource>::Prop as EnumUnitVariants>::UnitVariants::from_str(
&propname,
) {

View File

@@ -1,15 +1,14 @@
use crate::privileges::UserPrivilegeSet;
use crate::xml::multistatus::{PropTagWrapper, PropstatElement, PropstatWrapper};
use crate::xml::Resourcetype;
use crate::xml::{multistatus::ResponseElement, TagList};
use crate::Error;
use crate::xml::multistatus::{PropTagWrapper, PropstatElement, PropstatWrapper};
use crate::xml::{TagList, multistatus::ResponseElement};
use crate::{Error, Principal};
use actix_web::dev::ResourceMap;
use actix_web::http::header::{EntityTag, IfMatch, IfNoneMatch};
use actix_web::{http::StatusCode, ResponseError};
use actix_web::{ResponseError, http::StatusCode};
use itertools::Itertools;
use quick_xml::name::Namespace;
pub use resource_service::ResourceService;
use rustical_store::auth::User;
use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize};
use std::str::FromStr;
@@ -28,6 +27,7 @@ pub trait Resource: Clone + 'static {
type Prop: ResourceProp + PartialEq + Clone + EnumVariants + EnumUnitVariants;
type Error: ResponseError + From<crate::Error>;
type PrincipalResource: Resource + NamedRoute;
type Principal: Principal;
fn get_resourcetype(&self) -> Resourcetype;
@@ -38,7 +38,7 @@ pub trait Resource: Clone + 'static {
fn get_prop(
&self,
rmap: &ResourceMap,
user: &User,
principal: &Self::Principal,
prop: &<Self::Prop as EnumUnitVariants>::UnitVariants,
) -> Result<Self::Prop, Self::Error>;
@@ -93,13 +93,16 @@ pub trait Resource: Clone + 'static {
}
}
fn get_user_privileges(&self, user: &User) -> Result<UserPrivilegeSet, Self::Error>;
fn get_user_privileges(
&self,
principal: &Self::Principal,
) -> Result<UserPrivilegeSet, Self::Error>;
fn propfind(
&self,
path: &str,
props: &[&str],
user: &User,
principal: &Self::Principal,
rmap: &ResourceMap,
) -> Result<ResponseElement<Self::Prop>, Self::Error> {
let mut props = props.to_vec();
@@ -152,7 +155,7 @@ pub trait Resource: Clone + 'static {
let prop_responses = valid_props
.into_iter()
.map(|prop| self.get_prop(rmap, user, &prop))
.map(|prop| self.get_prop(rmap, principal, &prop))
.collect::<Result<Vec<_>, Self::Error>>()?;
let mut propstats = vec![PropstatWrapper::Normal(PropstatElement {

View File

@@ -2,20 +2,23 @@ use actix_web::dev::{AppService, HttpServiceFactory};
use actix_web::error::UrlGenerationError;
use actix_web::test::TestRequest;
use actix_web::web::Data;
use actix_web::{dev::ResourceMap, http::Method, web, ResponseError};
use actix_web::{ResponseError, dev::ResourceMap, http::Method, web};
use async_trait::async_trait;
use serde::Deserialize;
use std::str::FromStr;
use super::methods::{route_delete, route_propfind, route_proppatch};
use crate::Principal;
use super::Resource;
use super::methods::{route_delete, route_propfind, route_proppatch};
#[async_trait(?Send)]
pub trait ResourceService: Sized + 'static {
type MemberType: Resource<Error = Self::Error>;
type MemberType: Resource<Error = Self::Error, Principal = Self::Principal>;
type PathComponents: for<'de> Deserialize<'de> + Sized + Clone + 'static; // defines how the resource URI maps to parameters, i.e. /{principal}/{calendar} -> (String, String)
type Resource: Resource<Error = Self::Error>;
type Resource: Resource<Error = Self::Error, Principal = Self::Principal>;
type Error: ResponseError + From<crate::Error>;
type Principal: Principal;
async fn get_members(
&self,

View File

@@ -1,3 +1,4 @@
use crate::Principal;
use crate::extensions::{
CommonPropertiesExtension, CommonPropertiesProp, CommonPropertiesPropName,
};
@@ -6,22 +7,22 @@ use crate::resource::{NamedRoute, Resource, ResourceService};
use crate::xml::{Resourcetype, ResourcetypeInner};
use actix_web::dev::ResourceMap;
use async_trait::async_trait;
use rustical_store::auth::User;
use std::marker::PhantomData;
#[derive(Clone)]
pub struct RootResource<PR: Resource>(PhantomData<PR>);
pub struct RootResource<PR: Resource, P: Principal>(PhantomData<PR>, PhantomData<P>);
impl<PR: Resource> Default for RootResource<PR> {
impl<PR: Resource, P: Principal> Default for RootResource<PR, P> {
fn default() -> Self {
Self(Default::default())
Self(PhantomData, PhantomData)
}
}
impl<PR: Resource + NamedRoute> Resource for RootResource<PR> {
impl<PR: Resource + NamedRoute, P: Principal> Resource for RootResource<PR, P> {
type Prop = CommonPropertiesProp;
type Error = PR::Error;
type PrincipalResource = PR;
type Principal = P;
fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[ResourcetypeInner(
@@ -33,34 +34,37 @@ impl<PR: Resource + NamedRoute> Resource for RootResource<PR> {
fn get_prop(
&self,
rmap: &ResourceMap,
user: &User,
user: &P,
prop: &CommonPropertiesPropName,
) -> Result<Self::Prop, Self::Error> {
CommonPropertiesExtension::get_prop(self, rmap, user, prop)
}
fn get_user_privileges(&self, _user: &User) -> Result<UserPrivilegeSet, Self::Error> {
fn get_user_privileges(&self, _user: &P) -> Result<UserPrivilegeSet, Self::Error> {
Ok(UserPrivilegeSet::all())
}
}
#[derive(Clone)]
pub struct RootResourceService<PR: Resource>(PhantomData<PR>);
pub struct RootResourceService<PR: Resource, P: Principal>(PhantomData<PR>, PhantomData<P>);
impl<PR: Resource> Default for RootResourceService<PR> {
impl<PR: Resource, P: Principal> Default for RootResourceService<PR, P> {
fn default() -> Self {
Self(PhantomData)
Self(PhantomData, PhantomData)
}
}
#[async_trait(?Send)]
impl<PR: Resource + NamedRoute> ResourceService for RootResourceService<PR> {
impl<PR: Resource<Principal = P> + NamedRoute, P: Principal> ResourceService
for RootResourceService<PR, P>
{
type PathComponents = ();
type MemberType = PR;
type Resource = RootResource<PR>;
type Resource = RootResource<PR, P>;
type Error = PR::Error;
type Principal = P;
async fn get_resource(&self, _: &()) -> Result<Self::Resource, Self::Error> {
Ok(RootResource::<PR>::default())
Ok(RootResource::<PR, P>::default())
}
}

View File

@@ -0,0 +1,25 @@
[package]
name = "rustical_dav_push"
version.workspace = true
edition.workspace = true
description.workspace = true
repository.workspace = true
publish = false
[dependencies]
rustical_xml.workspace = true
actix-web = { workspace = true }
async-trait = { workspace = true }
futures-util = { workspace = true }
quick-xml = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }
itertools = { workspace = true }
log = { workspace = true }
derive_more = { workspace = true }
tracing = { workspace = true }
tracing-actix-web = { workspace = true }
reqwest.workspace = true
tokio.workspace = true
rustical_dav.workspace = true
rustical_store.workspace = true

View File

@@ -1,4 +1,4 @@
use crate::push::Transports;
use crate::Transports;
use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize};
#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumUnitVariants, EnumVariants)]
@@ -6,9 +6,9 @@ use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}
pub enum DavPushExtensionProp {
// WebDav Push
#[xml(skip_deserializing)]
#[xml(ns = "crate::namespace::NS_DAVPUSH")]
#[xml(ns = "rustical_dav::namespace::NS_DAVPUSH")]
Transports(Transports),
#[xml(ns = "crate::namespace::NS_DAVPUSH")]
#[xml(ns = "rustical_dav::namespace::NS_DAVPUSH")]
Topic(String),
}
@@ -18,7 +18,7 @@ pub trait DavPushExtension {
fn get_prop(
&self,
prop: &DavPushExtensionPropName,
) -> Result<DavPushExtensionProp, crate::Error> {
) -> Result<DavPushExtensionProp, rustical_dav::Error> {
Ok(match &prop {
DavPushExtensionPropName::Transports => {
DavPushExtensionProp::Transports(Default::default())
@@ -27,11 +27,11 @@ pub trait DavPushExtension {
})
}
fn set_prop(&self, _prop: DavPushExtensionProp) -> Result<(), crate::Error> {
Err(crate::Error::PropReadOnly)
fn set_prop(&self, _prop: DavPushExtensionProp) -> Result<(), rustical_dav::Error> {
Err(rustical_dav::Error::PropReadOnly)
}
fn remove_prop(&self, _prop: &DavPushExtensionPropName) -> Result<(), crate::Error> {
Err(crate::Error::PropReadOnly)
fn remove_prop(&self, _prop: &DavPushExtensionPropName) -> Result<(), rustical_dav::Error> {
Err(rustical_dav::Error::PropReadOnly)
}
}

View File

@@ -0,0 +1,7 @@
mod extension;
pub mod notifier;
mod prop;
pub mod register;
pub use extension::*;
pub use prop::*;

View File

@@ -1,5 +1,5 @@
use crate::xml::multistatus::PropstatElement;
use actix_web::http::StatusCode;
use rustical_dav::xml::multistatus::PropstatElement;
use rustical_store::{CollectionOperation, CollectionOperationType, SubscriptionStore};
use rustical_xml::{XmlRootTag, XmlSerialize, XmlSerializeRoot};
use std::sync::Arc;
@@ -8,17 +8,20 @@ use tracing::{error, info, warn};
#[derive(XmlSerialize, Debug)]
struct PushMessageProp {
#[xml(ns = "crate::namespace::NS_DAV")]
#[xml(ns = "rustical_dav::namespace::NS_DAV")]
topic: String,
#[xml(ns = "crate::namespace::NS_DAV")]
#[xml(ns = "rustical_dav::namespace::NS_DAV")]
sync_token: Option<String>,
}
#[derive(XmlSerialize, XmlRootTag, Debug)]
#[xml(root = b"push-message", ns = "crate::namespace::NS_DAVPUSH")]
#[xml(ns_prefix(crate::namespace::NS_DAVPUSH = b"", crate::namespace::NS_DAV = b"D",))]
#[xml(root = b"push-message", ns = "rustical_dav::namespace::NS_DAVPUSH")]
#[xml(ns_prefix(
rustical_dav::namespace::NS_DAVPUSH = b"",
rustical_dav::namespace::NS_DAV = b"D",
))]
struct PushMessage {
#[xml(ns = "crate::namespace::NS_DAV")]
#[xml(ns = "rustical_dav::namespace::NS_DAV")]
propstat: PropstatElement<PushMessageProp>,
}
@@ -87,7 +90,10 @@ pub async fn push_notifier(
error!("{err}");
}
} else {
warn!("Not sending a push notification to {} since it's not allowed in dav_push::allowed_push_servers", push_resource);
warn!(
"Not sending a push notification to {} since it's not allowed in dav_push::allowed_push_servers",
push_resource
);
}
}
}

View File

@@ -2,7 +2,7 @@ use rustical_xml::XmlSerialize;
#[derive(Debug, Clone, XmlSerialize, PartialEq)]
pub enum Transport {
#[xml(ns = "crate::namespace::NS_DAVPUSH")]
#[xml(ns = "rustical_dav::namespace::NS_DAVPUSH")]
WebPush,
}

View File

@@ -3,23 +3,23 @@ use rustical_xml::{XmlDeserialize, XmlRootTag};
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
#[xml(ns = "crate::namespace::NS_DAVPUSH")]
pub struct WebPushSubscription {
#[xml(ns = "crate::namespace::NS_DAVPUSH")]
#[xml(ns = "rustical_dav::namespace::NS_DAVPUSH")]
pub push_resource: String,
}
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
pub struct SubscriptionElement {
#[xml(ns = "crate::namespace::NS_DAVPUSH")]
#[xml(ns = "rustical_dav::namespace::NS_DAVPUSH")]
pub web_push_subscription: WebPushSubscription,
}
#[derive(XmlDeserialize, XmlRootTag, Clone, Debug, PartialEq)]
#[xml(root = b"push-register")]
#[xml(ns = "crate::namespace::NS_DAVPUSH")]
#[xml(ns = "rustical_dav::namespace::NS_DAVPUSH")]
pub struct PushRegister {
#[xml(ns = "crate::namespace::NS_DAVPUSH")]
#[xml(ns = "rustical_dav::namespace::NS_DAVPUSH")]
pub subscription: SubscriptionElement,
#[xml(ns = "crate::namespace::NS_DAVPUSH")]
#[xml(ns = "rustical_dav::namespace::NS_DAVPUSH")]
pub expires: Option<String>,
}

View File

@@ -10,9 +10,9 @@ use actix_web::{
};
use error::OidcError;
use openidconnect::{
AuthenticationFlow, AuthorizationCode, CsrfToken, EmptyAdditionalClaims, EndpointMaybeSet,
EndpointNotSet, EndpointSet, IssuerUrl, Nonce, OAuth2TokenResponse, PkceCodeChallenge,
PkceCodeVerifier, RedirectUrl, TokenResponse, UserInfoClaims,
AuthenticationFlow, AuthorizationCode, CsrfToken, EndpointMaybeSet, EndpointNotSet,
EndpointSet, IssuerUrl, Nonce, OAuth2TokenResponse, PkceCodeChallenge, PkceCodeVerifier,
RedirectUrl, TokenResponse, UserInfoClaims,
core::{CoreClient, CoreGenderClaim, CoreProviderMetadata, CoreResponseType},
};
use rustical_store::auth::{AuthenticationProvider, User, user::PrincipalType::Individual};

View File

@@ -30,6 +30,7 @@ tokio.workspace = true
rand.workspace = true
uuid.workspace = true
clap.workspace = true
rustical_dav.workspace = true
[dev-dependencies]
rstest = { workspace = true }

View File

@@ -81,6 +81,12 @@ impl User {
}
}
impl rustical_dav::Principal for User {
fn get_id(&self) -> &str {
&self.id
}
}
#[derive(Clone, Debug, Display)]
pub struct UnauthorizedError;