-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Dav Push: Logic to register subscriptions
- Loading branch information
Showing
12 changed files
with
261 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
pub mod mkcalendar; | ||
pub mod post; | ||
pub mod report; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
use crate::Error; | ||
use actix_web::http::header; | ||
use actix_web::web::{Data, Path}; | ||
use actix_web::{HttpRequest, HttpResponse}; | ||
use rustical_store::auth::User; | ||
use rustical_store::{CalendarStore, Subscription, SubscriptionStore}; | ||
use rustical_xml::{XmlDeserialize, XmlDocument, XmlRootTag}; | ||
use tracing::instrument; | ||
use tracing_actix_web::RootSpan; | ||
|
||
#[derive(XmlDeserialize, Clone, Debug, PartialEq)] | ||
#[xml(ns = "rustical_dav::namespace::NS_DAVPUSH")] | ||
struct WebPushSubscription { | ||
#[xml(ns = "rustical_dav::namespace::NS_DAVPUSH")] | ||
push_resource: String, | ||
} | ||
|
||
#[derive(XmlDeserialize, Clone, Debug, PartialEq)] | ||
struct SubscriptionElement { | ||
#[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 = "rustical_dav::namespace::NS_DAVPUSH")] | ||
struct PushRegister { | ||
#[xml(ns = "rustical_dav::namespace::NS_DAVPUSH")] | ||
subscription: SubscriptionElement, | ||
#[xml(ns = "rustical_dav::namespace::NS_DAVPUSH")] | ||
expires: Option<String>, | ||
} | ||
|
||
#[instrument(parent = root_span.id(), skip(store, subscription_store, root_span, req))] | ||
pub async fn route_post<C: CalendarStore + ?Sized, S: SubscriptionStore + ?Sized>( | ||
path: Path<(String, String)>, | ||
body: String, | ||
user: User, | ||
store: Data<C>, | ||
subscription_store: Data<S>, | ||
root_span: RootSpan, | ||
req: HttpRequest, | ||
) -> Result<HttpResponse, Error> { | ||
let (principal, cal_id) = path.into_inner(); | ||
if principal != user.id { | ||
return Err(Error::Unauthorized); | ||
} | ||
|
||
let calendar = store.get_calendar(&principal, &cal_id).await?; | ||
let request = PushRegister::parse_str(&body)?; | ||
let sub_id = uuid::Uuid::new_v4().to_string(); | ||
|
||
let expires = if let Some(expires) = request.expires { | ||
chrono::DateTime::parse_from_rfc2822(&expires) | ||
.map_err(|err| crate::Error::Other(err.into()))? | ||
} else { | ||
chrono::Utc::now().fixed_offset() + chrono::Duration::weeks(1) | ||
}; | ||
|
||
let subscription = Subscription { | ||
id: sub_id.to_owned(), | ||
push_resource: request | ||
.subscription | ||
.web_push_subscription | ||
.push_resource | ||
.to_owned(), | ||
topic: calendar.push_topic, | ||
expiration: expires.naive_local(), | ||
}; | ||
subscription_store.upsert_subscription(subscription).await?; | ||
|
||
let location = req | ||
.resource_map() | ||
.url_for(&req, "subscription", &[sub_id]) | ||
.unwrap(); | ||
|
||
Ok(HttpResponse::Created() | ||
.append_header((header::LOCATION, location.to_string())) | ||
.append_header((header::EXPIRES, expires.to_rfc2822())) | ||
.finish()) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_xml_push_register() { | ||
let push_register = PushRegister::parse_str( | ||
r#" | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<push-register xmlns="https://bitfire.at/webdav-push"> | ||
<subscription> | ||
<web-push-subscription> | ||
<push-resource>https://up.example.net/yohd4yai5Phiz1wi</push-resource> | ||
</web-push-subscription> | ||
</subscription> | ||
<expires>Wed, 20 Dec 2023 10:03:31 GMT</expires> | ||
</push-register> | ||
"#, | ||
) | ||
.unwrap(); | ||
assert_eq!( | ||
push_register, | ||
PushRegister { | ||
subscription: SubscriptionElement { | ||
web_push_subscription: WebPushSubscription { | ||
push_resource: "https://up.example.net/yohd4yai5Phiz1wi".to_owned() | ||
} | ||
}, | ||
expires: Some("Wed, 20 Dec 2023 10:03:31 GMT".to_owned()) | ||
} | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
use actix_web::{ | ||
web::{self, Data, Path}, | ||
HttpResponse, | ||
}; | ||
use rustical_store::SubscriptionStore; | ||
|
||
async fn handle_delete<S: SubscriptionStore + ?Sized>( | ||
store: Data<S>, | ||
path: Path<String>, | ||
) -> Result<HttpResponse, rustical_store::Error> { | ||
let id = path.into_inner(); | ||
store.delete_subscription(&id).await?; | ||
Ok(HttpResponse::NoContent().body("Unregistered")) | ||
} | ||
|
||
pub fn subscription_resource<S: SubscriptionStore + ?Sized>() -> actix_web::Resource { | ||
web::resource("/subscription/{id}") | ||
.name("subscription") | ||
.delete(handle_delete::<S>) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
use crate::Error; | ||
use async_trait::async_trait; | ||
use chrono::NaiveDateTime; | ||
|
||
pub struct Subscription { | ||
pub id: String, | ||
pub topic: String, | ||
pub expiration: NaiveDateTime, | ||
pub push_resource: String, | ||
} | ||
|
||
#[async_trait(?Send)] | ||
pub trait SubscriptionStore: Send + Sync + 'static { | ||
async fn get_subscriptions(&self, topic: &str) -> Result<Vec<Subscription>, Error>; | ||
async fn get_subscription(&self, id: &str) -> Result<Subscription, Error>; | ||
/// Returns whether a subscription under the id already existed | ||
async fn upsert_subscription(&self, sub: Subscription) -> Result<bool, Error>; | ||
async fn delete_subscription(&self, id: &str) -> Result<(), Error>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
CREATE TABLE subscriptions ( | ||
id TEXT NOT NULL, | ||
topic TEXT NOT NULL, | ||
expiration DATETIME NOT NULL, | ||
push_resource TEXT NOT NULL, | ||
PRIMARY KEY (id) | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
use crate::SqliteStore; | ||
use async_trait::async_trait; | ||
use rustical_store::{Error, Subscription, SubscriptionStore}; | ||
|
||
#[async_trait(?Send)] | ||
impl SubscriptionStore for SqliteStore { | ||
async fn get_subscriptions(&self, topic: &str) -> Result<Vec<Subscription>, Error> { | ||
Ok(sqlx::query_as!( | ||
Subscription, | ||
r#"SELECT id, topic, expiration, push_resource | ||
FROM subscriptions | ||
WHERE (topic) = (?)"#, | ||
topic | ||
) | ||
.fetch_all(&self.db) | ||
.await | ||
.map_err(crate::Error::from)?) | ||
} | ||
|
||
async fn get_subscription(&self, id: &str) -> Result<Subscription, Error> { | ||
Ok(sqlx::query_as!( | ||
Subscription, | ||
r#"SELECT id, topic, expiration, push_resource | ||
FROM subscriptions | ||
WHERE (id) = (?)"#, | ||
id | ||
) | ||
.fetch_one(&self.db) | ||
.await | ||
.map_err(crate::Error::from)?) | ||
} | ||
|
||
async fn upsert_subscription(&self, sub: Subscription) -> Result<bool, Error> { | ||
sqlx::query!( | ||
r#"INSERT OR REPLACE INTO subscriptions (id, topic, expiration, push_resource) VALUES (?, ?, ?, ?)"#, | ||
sub.id, | ||
sub.topic, | ||
sub.expiration, | ||
sub.push_resource | ||
).execute(&self.db).await.map_err(crate::Error::from)?; | ||
// TODO: Correctly return whether a subscription already existed | ||
Ok(false) | ||
} | ||
async fn delete_subscription(&self, id: &str) -> Result<(), Error> { | ||
sqlx::query!(r#"DELETE FROM subscriptions WHERE id = ? "#, id) | ||
.execute(&self.db) | ||
.await | ||
.map_err(crate::Error::from)?; | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.