mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 16:32:29 +00:00
DAV Push: Configurable list of allowed push targets
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
use crate::xml::multistatus::PropstatElement;
|
use crate::xml::multistatus::PropstatElement;
|
||||||
use actix_web::http::StatusCode;
|
use actix_web::http::StatusCode;
|
||||||
|
use reqwest::Url;
|
||||||
use rustical_store::{CollectionOperation, CollectionOperationType, SubscriptionStore};
|
use rustical_store::{CollectionOperation, CollectionOperationType, SubscriptionStore};
|
||||||
use rustical_xml::{XmlRootTag, XmlSerialize, XmlSerializeRoot};
|
use rustical_xml::{XmlRootTag, XmlSerialize, XmlSerializeRoot};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::mpsc::Receiver;
|
use tokio::sync::mpsc::Receiver;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
#[derive(XmlSerialize, Debug)]
|
#[derive(XmlSerialize, Debug)]
|
||||||
struct PushMessageProp {
|
struct PushMessageProp {
|
||||||
@@ -23,15 +24,26 @@ struct PushMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn push_notifier(
|
pub async fn push_notifier(
|
||||||
|
allowed_push_servers: Option<Vec<String>>,
|
||||||
mut recv: Receiver<CollectionOperation>,
|
mut recv: Receiver<CollectionOperation>,
|
||||||
sub_store: Arc<impl SubscriptionStore>,
|
sub_store: Arc<impl SubscriptionStore>,
|
||||||
) {
|
) {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
while let Some(message) = recv.recv().await {
|
while let Some(message) = recv.recv().await {
|
||||||
if let Ok(subscribers) = sub_store.get_subscriptions(&message.topic).await {
|
let subscribers = match sub_store.get_subscriptions(&message.topic).await {
|
||||||
|
Ok(subs) => subs,
|
||||||
|
Err(err) => {
|
||||||
|
error!("{err}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let status = match message.r#type {
|
let status = match message.r#type {
|
||||||
CollectionOperationType::Object => StatusCode::OK,
|
CollectionOperationType::Object => StatusCode::OK,
|
||||||
CollectionOperationType::Delete => StatusCode::NOT_FOUND,
|
CollectionOperationType::Delete => StatusCode::NOT_FOUND,
|
||||||
};
|
};
|
||||||
|
|
||||||
let push_message = PushMessage {
|
let push_message = PushMessage {
|
||||||
propstat: PropstatElement {
|
propstat: PropstatElement {
|
||||||
prop: PushMessageProp {
|
prop: PushMessageProp {
|
||||||
@@ -41,6 +53,7 @@ pub async fn push_notifier(
|
|||||||
status,
|
status,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut output: Vec<_> = b"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".into();
|
let mut output: Vec<_> = b"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".into();
|
||||||
let mut writer = quick_xml::Writer::new_with_indent(&mut output, b' ', 4);
|
let mut writer = quick_xml::Writer::new_with_indent(&mut output, b' ', 4);
|
||||||
if let Err(err) = push_message.serialize_root(&mut writer) {
|
if let Err(err) = push_message.serialize_root(&mut writer) {
|
||||||
@@ -49,19 +62,33 @@ pub async fn push_notifier(
|
|||||||
}
|
}
|
||||||
let payload = String::from_utf8(output).unwrap();
|
let payload = String::from_utf8(output).unwrap();
|
||||||
for subscriber in subscribers {
|
for subscriber in subscribers {
|
||||||
info!(
|
let push_resource = subscriber.push_resource;
|
||||||
"Sending a push message to {}: {}",
|
let allowed = if let Some(allowed_push_servers) = &allowed_push_servers {
|
||||||
subscriber.push_resource, payload
|
if let Ok(resource_url) = reqwest::Url::parse(&push_resource) {
|
||||||
);
|
let origin = resource_url.origin().ascii_serialization();
|
||||||
let client = reqwest::Client::new();
|
allowed_push_servers
|
||||||
|
.iter()
|
||||||
|
.any(|allowed_push_server| allowed_push_server == &origin)
|
||||||
|
} else {
|
||||||
|
warn!("Invalid push url: {push_resource}");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
if allowed {
|
||||||
|
info!("Sending a push message to {}: {}", push_resource, payload);
|
||||||
if let Err(err) = client
|
if let Err(err) = client
|
||||||
.post(subscriber.push_resource)
|
.post(push_resource)
|
||||||
.body(payload.to_owned())
|
.body(payload.to_owned())
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
error!("{err}");
|
error!("{err}");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Not sending a push notification to {} since it's not allowed in dav_push::allowed_push_servers", push_resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ use rustical_frontend::FrontendConfig;
|
|||||||
use rustical_store::auth::{static_user_store::UserEntry, StaticUserStoreConfig, User};
|
use rustical_store::auth::{static_user_store::UserEntry, StaticUserStoreConfig, User};
|
||||||
|
|
||||||
use crate::config::{
|
use crate::config::{
|
||||||
AuthConfig, Config, DataStoreConfig, HttpConfig, SqliteDataStoreConfig, TracingConfig,
|
AuthConfig, Config, DataStoreConfig, DavPushConfig, HttpConfig, SqliteDataStoreConfig,
|
||||||
|
TracingConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
@@ -46,6 +47,7 @@ pub fn cmd_gen_config(_args: GenConfigArgs) -> anyhow::Result<()> {
|
|||||||
frontend: FrontendConfig {
|
frontend: FrontendConfig {
|
||||||
secret_key: generate_frontend_secret(),
|
secret_key: generate_frontend_secret(),
|
||||||
},
|
},
|
||||||
|
dav_push: DavPushConfig::default(),
|
||||||
};
|
};
|
||||||
let generated_config = toml::to_string(&config)?;
|
let generated_config = toml::to_string(&config)?;
|
||||||
println!("{generated_config}");
|
println!("{generated_config}");
|
||||||
|
|||||||
@@ -44,6 +44,25 @@ pub struct TracingConfig {
|
|||||||
pub opentelemetry: bool,
|
pub opentelemetry: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct DavPushConfig {
|
||||||
|
pub enable: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
// Allowed Push servers, accepts any by default
|
||||||
|
// Specify as URL origins
|
||||||
|
pub allowed_push_servers: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DavPushConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enable: true,
|
||||||
|
allowed_push_servers: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
@@ -54,4 +73,6 @@ pub struct Config {
|
|||||||
pub frontend: FrontendConfig,
|
pub frontend: FrontendConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub tracing: TracingConfig,
|
pub tracing: TracingConfig,
|
||||||
|
#[serde(default)]
|
||||||
|
pub dav_push: DavPushConfig,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,13 @@ async fn main() -> Result<()> {
|
|||||||
let (addr_store, cal_store, subscription_store, update_recv) =
|
let (addr_store, cal_store, subscription_store, update_recv) =
|
||||||
get_data_stores(!args.no_migrations, &config.data_store).await?;
|
get_data_stores(!args.no_migrations, &config.data_store).await?;
|
||||||
|
|
||||||
tokio::spawn(push_notifier(update_recv, subscription_store.clone()));
|
if config.dav_push.enable {
|
||||||
|
tokio::spawn(push_notifier(
|
||||||
|
config.dav_push.allowed_push_servers,
|
||||||
|
update_recv,
|
||||||
|
subscription_store.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let user_store = Arc::new(match config.auth {
|
let user_store = Arc::new(match config.auth {
|
||||||
config::AuthConfig::Static(config) => StaticUserStore::new(config),
|
config::AuthConfig::Static(config) => StaticUserStore::new(config),
|
||||||
|
|||||||
Reference in New Issue
Block a user