diff --git a/Cargo.lock b/Cargo.lock index ed3f42c..dc38ed2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3035,6 +3035,7 @@ dependencies = [ "rustical_caldav", "rustical_carddav", "rustical_dav", + "rustical_dav_push", "rustical_frontend", "rustical_store", "rustical_store_sqlite", @@ -3063,6 +3064,7 @@ dependencies = [ "futures-util", "quick-xml", "rustical_dav", + "rustical_dav_push", "rustical_store", "rustical_xml", "serde", @@ -3088,6 +3090,7 @@ dependencies = [ "futures-util", "quick-xml", "rustical_dav", + "rustical_dav_push", "rustical_store", "rustical_xml", "serde", @@ -3111,6 +3114,27 @@ dependencies = [ "log", "quick-xml", "reqwest", + "rustical_xml", + "serde", + "thiserror 2.0.12", + "tokio", + "tracing", + "tracing-actix-web", +] + +[[package]] +name = "rustical_dav_push" +version = "0.1.0" +dependencies = [ + "actix-web", + "async-trait", + "derive_more 2.0.1", + "futures-util", + "itertools 0.14.0", + "log", + "quick-xml", + "reqwest", + "rustical_dav", "rustical_store", "rustical_xml", "serde", @@ -3167,6 +3191,7 @@ dependencies = [ "regex", "rstest", "rstest_reuse", + "rustical_dav", "rustical_store_sqlite", "rustical_xml", "serde", diff --git a/Cargo.toml b/Cargo.toml index 3af58df..19a31b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,7 @@ sqlx-sqlite = { version = "0.8", features = ["bundled"] } ical = { version = "0.11", features = ["generator", "serde"] } toml = "0.8" rustical_dav = { path = "./crates/dav/" } +rustical_dav_push = { path = "./crates/dav_push/" } rustical_store = { path = "./crates/store/" } rustical_store_sqlite = { path = "./crates/store_sqlite/" } rustical_caldav = { path = "./crates/caldav/" } @@ -155,4 +156,5 @@ pbkdf2.workspace = true password-hash.workspace = true reqwest.workspace = true rustical_dav.workspace = true +rustical_dav_push.workspace = true quick-xml.workspace = true diff --git a/crates/caldav/Cargo.toml b/crates/caldav/Cargo.toml index 549d28e..ac4990f 100644 --- a/crates/caldav/Cargo.toml +++ b/crates/caldav/Cargo.toml @@ -27,3 +27,4 @@ chrono-tz = { workspace = true } sha2 = { workspace = true } rustical_xml.workspace = true uuid.workspace = true +rustical_dav_push.workspace = true diff --git a/crates/caldav/src/calendar/methods/post.rs b/crates/caldav/src/calendar/methods/post.rs index 607c356..1e52180 100644 --- a/crates/caldav/src/calendar/methods/post.rs +++ b/crates/caldav/src/calendar/methods/post.rs @@ -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; diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs index cfbf8b3..cd62e41 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -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 ResourceService for CalendarResourc type PathComponents = (String, String); // principal, calendar_id type Resource = CalendarResource; type Error = Error; + type Principal = User; async fn get_resource( &self, diff --git a/crates/caldav/src/calendar_object/resource.rs b/crates/caldav/src/calendar_object/resource.rs index 650e969..dd800ad 100644 --- a/crates/caldav/src/calendar_object/resource.rs +++ b/crates/caldav/src/calendar_object/resource.rs @@ -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 ResourceService for CalendarObjectResourceService { type Resource = CalendarObjectResource; type MemberType = CalendarObjectResource; type Error = Error; + type Principal = User; async fn get_resource( &self, diff --git a/crates/caldav/src/calendar_set/mod.rs b/crates/caldav/src/calendar_set/mod.rs index 555db61..bc3d995 100644 --- a/crates/caldav/src/calendar_set/mod.rs +++ b/crates/caldav/src/calendar_set/mod.rs @@ -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 ResourceService for CalendarSetResourceService { type MemberType = CalendarResource; type Resource = CalendarSetResource; type Error = Error; + type Principal = User; async fn get_resource( &self, diff --git a/crates/caldav/src/lib.rs b/crates/caldav/src/lib.rs index cc3aa69..dddb823 100644 --- a/crates/caldav/src/lib.rs +++ b/crates/caldav/src/lib.rs @@ -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::::default().actix_resource()) + .service(RootResourceService::::default().actix_resource()) .service( web::scope("/principal").service( web::scope("/{principal}") diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index 231e800..1ba14df 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -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 ResourceService for PrincipalResourceService Resourcetype { Resourcetype(&[]) @@ -128,6 +129,7 @@ impl ResourceService for AddressObjectResourceService type Resource = AddressObjectResource; type MemberType = AddressObjectResource; type Error = Error; + type Principal = User; async fn get_resource( &self, diff --git a/crates/carddav/src/addressbook/methods/post.rs b/crates/carddav/src/addressbook/methods/post.rs index 33a14b6..b66c830 100644 --- a/crates/carddav/src/addressbook/methods/post.rs +++ b/crates/carddav/src/addressbook/methods/post.rs @@ -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; diff --git a/crates/carddav/src/addressbook/resource.rs b/crates/carddav/src/addressbook/resource.rs index e8a0a5c..17c5725 100644 --- a/crates/carddav/src/addressbook/resource.rs +++ b/crates/carddav/src/addressbook/resource.rs @@ -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 ResourceService type PathComponents = (String, String); // principal, addressbook_id type Resource = AddressbookResource; type Error = Error; + type Principal = User; async fn get_resource( &self, diff --git a/crates/carddav/src/lib.rs b/crates/carddav/src/lib.rs index 87e3197..3260202 100644 --- a/crates/carddav/src/lib.rs +++ b/crates/carddav/src/lib.rs @@ -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::default().actix_resource()) + .service(RootResourceService::::default().actix_resource()) .service( web::scope("/principal").service( web::scope("/{principal}") diff --git a/crates/carddav/src/principal/mod.rs b/crates/carddav/src/principal/mod.rs index a6a337b..63c3d9d 100644 --- a/crates/carddav/src/principal/mod.rs +++ b/crates/carddav/src/principal/mod.rs @@ -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 ResourceService type MemberType = AddressbookResource; type Resource = PrincipalResource; type Error = Error; + type Principal = User; async fn get_resource( &self, diff --git a/crates/dav/Cargo.toml b/crates/dav/Cargo.toml index ed382cd..eb1b12d 100644 --- a/crates/dav/Cargo.toml +++ b/crates/dav/Cargo.toml @@ -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 } diff --git a/crates/dav/src/extensions/common.rs b/crates/dav/src/extensions/common.rs index c30f415..f2c797f 100644 --- a/crates/dav/src/extensions/common.rs +++ b/crates/dav/src/extensions/common.rs @@ -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::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| { diff --git a/crates/dav/src/extensions/mod.rs b/crates/dav/src/extensions/mod.rs index b711782..0da1bf5 100644 --- a/crates/dav/src/extensions/mod.rs +++ b/crates/dav/src/extensions/mod.rs @@ -1,7 +1,5 @@ mod common; -mod davpush; mod synctoken; pub use common::*; -pub use davpush::*; pub use synctoken::*; diff --git a/crates/dav/src/lib.rs b/crates/dav/src/lib.rs index 56207c9..b76eab9 100644 --- a/crates/dav/src/lib.rs +++ b/crates/dav/src/lib.rs @@ -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; +} diff --git a/crates/dav/src/push/mod.rs b/crates/dav/src/push/mod.rs deleted file mode 100644 index fb0824a..0000000 --- a/crates/dav/src/push/mod.rs +++ /dev/null @@ -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::*; diff --git a/crates/dav/src/resource/methods/delete.rs b/crates/dav/src/resource/methods/delete.rs index f63ed2e..bfbb115 100644 --- a/crates/dav/src/resource/methods/delete.rs +++ b/crates/dav/src/resource/methods/delete.rs @@ -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( path: Path, req: HttpRequest, - user: User, + principal: R::Principal, resource_service: Data, root_span: RootSpan, if_match: web::Header, @@ -32,7 +31,7 @@ pub async fn route_delete( 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()); } diff --git a/crates/dav/src/resource/methods/propfind.rs b/crates/dav/src/resource/methods/propfind.rs index 07d1a6c..67c1f07 100644 --- a/crates/dav/src/resource/methods/propfind.rs +++ b/crates/dav/src/resource/methods/propfind.rs @@ -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( path: Path, body: String, req: HttpRequest, - user: User, + user: R::Principal, depth: Depth, root_span: RootSpan, resource_service: Data, diff --git a/crates/dav/src/resource/methods/proppatch.rs b/crates/dav/src/resource/methods/proppatch.rs index 6b544a5..d3b4dcd 100644 --- a/crates/dav/src/resource/methods/proppatch.rs +++ b/crates/dav/src/resource/methods/proppatch.rs @@ -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( path: Path, body: String, req: HttpRequest, - user: User, + principal: R::Principal, root_span: RootSpan, resource_service: Data, ) -> Result, R::Error> { @@ -81,7 +80,7 @@ pub(crate) async fn route_proppatch( ) = 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( } } Operation::Remove(remove_el) => { - let propname = remove_el.prop.0 .0; + let propname = remove_el.prop.0.0; match <::Prop as EnumUnitVariants>::UnitVariants::from_str( &propname, ) { diff --git a/crates/dav/src/resource/mod.rs b/crates/dav/src/resource/mod.rs index ff8ee11..c167d6d 100644 --- a/crates/dav/src/resource/mod.rs +++ b/crates/dav/src/resource/mod.rs @@ -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; 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: &::UnitVariants, ) -> Result; @@ -93,13 +93,16 @@ pub trait Resource: Clone + 'static { } } - fn get_user_privileges(&self, user: &User) -> Result; + fn get_user_privileges( + &self, + principal: &Self::Principal, + ) -> Result; fn propfind( &self, path: &str, props: &[&str], - user: &User, + principal: &Self::Principal, rmap: &ResourceMap, ) -> Result, 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::, Self::Error>>()?; let mut propstats = vec![PropstatWrapper::Normal(PropstatElement { diff --git a/crates/dav/src/resource/resource_service.rs b/crates/dav/src/resource/resource_service.rs index 8cd854f..3a774d7 100644 --- a/crates/dav/src/resource/resource_service.rs +++ b/crates/dav/src/resource/resource_service.rs @@ -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; + type MemberType: Resource; 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; + type Resource: Resource; type Error: ResponseError + From; + type Principal: Principal; async fn get_members( &self, diff --git a/crates/dav/src/resources/root.rs b/crates/dav/src/resources/root.rs index deed6ab..14c54e6 100644 --- a/crates/dav/src/resources/root.rs +++ b/crates/dav/src/resources/root.rs @@ -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(PhantomData); +pub struct RootResource(PhantomData, PhantomData

); -impl Default for RootResource { +impl Default for RootResource { fn default() -> Self { - Self(Default::default()) + Self(PhantomData, PhantomData) } } -impl Resource for RootResource { +impl Resource for RootResource { 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 Resource for RootResource { fn get_prop( &self, rmap: &ResourceMap, - user: &User, + user: &P, prop: &CommonPropertiesPropName, ) -> Result { CommonPropertiesExtension::get_prop(self, rmap, user, prop) } - fn get_user_privileges(&self, _user: &User) -> Result { + fn get_user_privileges(&self, _user: &P) -> Result { Ok(UserPrivilegeSet::all()) } } #[derive(Clone)] -pub struct RootResourceService(PhantomData); +pub struct RootResourceService(PhantomData, PhantomData

); -impl Default for RootResourceService { +impl Default for RootResourceService { fn default() -> Self { - Self(PhantomData) + Self(PhantomData, PhantomData) } } #[async_trait(?Send)] -impl ResourceService for RootResourceService { +impl + NamedRoute, P: Principal> ResourceService + for RootResourceService +{ type PathComponents = (); type MemberType = PR; - type Resource = RootResource; + type Resource = RootResource; type Error = PR::Error; + type Principal = P; async fn get_resource(&self, _: &()) -> Result { - Ok(RootResource::::default()) + Ok(RootResource::::default()) } } diff --git a/crates/dav_push/Cargo.toml b/crates/dav_push/Cargo.toml new file mode 100644 index 0000000..9752236 --- /dev/null +++ b/crates/dav_push/Cargo.toml @@ -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 diff --git a/crates/dav/src/extensions/davpush.rs b/crates/dav_push/src/extension.rs similarity index 72% rename from crates/dav/src/extensions/davpush.rs rename to crates/dav_push/src/extension.rs index 60b467c..988e869 100644 --- a/crates/dav/src/extensions/davpush.rs +++ b/crates/dav_push/src/extension.rs @@ -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 { + ) -> Result { 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) } } diff --git a/crates/dav_push/src/lib.rs b/crates/dav_push/src/lib.rs new file mode 100644 index 0000000..45049d9 --- /dev/null +++ b/crates/dav_push/src/lib.rs @@ -0,0 +1,7 @@ +mod extension; +pub mod notifier; +mod prop; +pub mod register; + +pub use extension::*; +pub use prop::*; diff --git a/crates/dav/src/push/push_notifier.rs b/crates/dav_push/src/notifier.rs similarity index 83% rename from crates/dav/src/push/push_notifier.rs rename to crates/dav_push/src/notifier.rs index 867cf11..b9ccd14 100644 --- a/crates/dav/src/push/push_notifier.rs +++ b/crates/dav_push/src/notifier.rs @@ -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, } #[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, } @@ -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 + ); } } } diff --git a/crates/dav/src/push/prop.rs b/crates/dav_push/src/prop.rs similarity index 89% rename from crates/dav/src/push/prop.rs rename to crates/dav_push/src/prop.rs index ffb8307..5a3ea2a 100644 --- a/crates/dav/src/push/prop.rs +++ b/crates/dav_push/src/prop.rs @@ -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, } diff --git a/crates/dav/src/push/push_register.rs b/crates/dav_push/src/register.rs similarity index 86% rename from crates/dav/src/push/push_register.rs rename to crates/dav_push/src/register.rs index aae3c76..44c40de 100644 --- a/crates/dav/src/push/push_register.rs +++ b/crates/dav_push/src/register.rs @@ -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, } diff --git a/crates/frontend/src/oidc/mod.rs b/crates/frontend/src/oidc/mod.rs index 9d8f8aa..40fe3d2 100644 --- a/crates/frontend/src/oidc/mod.rs +++ b/crates/frontend/src/oidc/mod.rs @@ -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}; diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml index 018b700..a427fa8 100644 --- a/crates/store/Cargo.toml +++ b/crates/store/Cargo.toml @@ -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 } diff --git a/crates/store/src/auth/user.rs b/crates/store/src/auth/user.rs index 47d5fda..71bf200 100644 --- a/crates/store/src/auth/user.rs +++ b/crates/store/src/auth/user.rs @@ -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; diff --git a/src/main.rs b/src/main.rs index 8919260..2b0b4b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use commands::{cmd_gen_config, cmd_pwhash}; use config::{DataStoreConfig, SqliteDataStoreConfig}; use figment::Figment; use figment::providers::{Env, Format, Toml}; -use rustical_dav::push::push_notifier; +use rustical_dav_push::notifier::push_notifier; use rustical_frontend::nextcloud_login::NextcloudFlows; use rustical_store::auth::TomlPrincipalStore; use rustical_store::{AddressbookStore, CalendarStore, CollectionOperation, SubscriptionStore}; @@ -150,51 +150,51 @@ mod tests { } async fn get_principal( &self, - id: &str, + _id: &str, ) -> Result, rustical_store::Error> { Err(rustical_store::Error::Other(anyhow!("Not implemented"))) } - async fn remove_principal(&self, id: &str) -> Result<(), rustical_store::Error> { + async fn remove_principal(&self, _id: &str) -> Result<(), rustical_store::Error> { Err(rustical_store::Error::Other(anyhow!("Not implemented"))) } async fn validate_password( &self, - user_id: &str, - password: &str, + _user_id: &str, + _password: &str, ) -> Result, rustical_store::Error> { Err(rustical_store::Error::Other(anyhow!("Not implemented"))) } async fn validate_app_token( &self, - user_id: &str, - token: &str, + _user_id: &str, + _token: &str, ) -> Result, rustical_store::Error> { Err(rustical_store::Error::Other(anyhow!("Not implemented"))) } async fn add_app_token( &self, - user_id: &str, - name: String, - token: String, + _user_id: &str, + _name: String, + _token: String, ) -> Result { Err(rustical_store::Error::Other(anyhow!("Not implemented"))) } async fn remove_app_token( &self, - user_id: &str, - token_id: &str, + _user_id: &str, + _token_id: &str, ) -> Result<(), rustical_store::Error> { Err(rustical_store::Error::Other(anyhow!("Not implemented"))) } async fn insert_principal( &self, - user: rustical_store::auth::User, + _user: rustical_store::auth::User, ) -> Result<(), rustical_store::Error> { Err(rustical_store::Error::Other(anyhow!("Not implemented"))) }