Skip to main content

tuwunel_service/pusher/
send.rs

1use ipaddress::IPAddress;
2use ruma::{
3	UInt, UserId,
4	api::{
5		client::push::{Pusher, PusherKind},
6		push_gateway::send_event_notification::{
7			self,
8			v1::{Device, Notification, NotificationCounts, NotificationPriority},
9		},
10	},
11	events::TimelineEventType,
12	push::{Action, PushFormat, Ruleset, Tweak},
13	uint,
14};
15use tuwunel_core::{Err, Result, err, implement, matrix::Event};
16
17#[implement(super::Service)]
18#[tracing::instrument(level = "debug", skip_all)]
19pub async fn send_push_notice<E>(
20	&self,
21	user_id: &UserId,
22	pusher: &Pusher,
23	ruleset: &Ruleset,
24	event: &E,
25) -> Result
26where
27	E: Event,
28{
29	let mut notify = None;
30	let mut tweaks = Vec::new();
31
32	let power_levels = self
33		.services
34		.state_accessor
35		.get_power_levels(event.room_id())
36		.await
37		.ok();
38
39	let serialized = event.to_format();
40	let actions = self
41		.get_actions(user_id, ruleset, power_levels.as_ref(), &serialized, event.room_id())
42		.await;
43
44	for action in actions {
45		let n = match action {
46			| Action::Notify => true,
47			| Action::SetTweak(tweak) => {
48				tweaks.push(tweak.clone());
49				continue;
50			},
51			| _ => false,
52		};
53
54		if notify.is_some() {
55			return Err!(Request(BadJson(
56				r#"Malformed pushrule contains more than one of these actions: ["dont_notify", "notify", "coalesce"]"#
57			)));
58		}
59
60		notify = Some(n);
61	}
62
63	if notify == Some(true) || self.services.config.push_everything {
64		let unread: UInt = self
65			.services
66			.pusher
67			.notification_count(user_id, event.room_id())
68			.await
69			.try_into()
70			.unwrap_or_else(|_| uint!(1));
71
72		self.send_notice(unread, pusher, tweaks, event)
73			.await?;
74	}
75
76	Ok(())
77}
78
79#[implement(super::Service)]
80#[tracing::instrument(level = "debug", skip_all)]
81async fn send_notice<Pdu: Event>(
82	&self,
83	unread: UInt,
84	pusher: &Pusher,
85	tweaks: Vec<Tweak>,
86	event: &Pdu,
87) -> Result {
88	// TODO: email
89	match &pusher.kind {
90		| PusherKind::Http(http) => {
91			let url = &http.url;
92			let url = url::Url::parse(&http.url).map_err(|e| {
93				err!(Request(InvalidParam(
94					warn!(%url, "HTTP pusher URL is not a valid URL: {e}")
95				)))
96			})?;
97
98			if ["http", "https"]
99				.iter()
100				.all(|&scheme| !scheme.eq_ignore_ascii_case(url.scheme()))
101			{
102				return Err!(Request(InvalidParam(
103					warn!(%url, "HTTP pusher URL is not a valid HTTP/HTTPS URL")
104				)));
105			}
106
107			if let Ok(ip) = IPAddress::parse(url.host_str().expect("URL previously validated"))
108				&& !self.services.client.valid_cidr_range(&ip)
109			{
110				return Err!(Request(InvalidParam(
111					warn!(%url, "HTTP pusher URL is a forbidden remote address")
112				)));
113			}
114
115			// TODO (timo): can pusher/devices have conflicting formats
116			let event_id_only = http.format == Some(PushFormat::EventIdOnly);
117
118			let mut device = Device::new(pusher.ids.app_id.clone(), pusher.ids.pushkey.clone());
119			device.data.data.clone_from(&http.data);
120			device.data.format.clone_from(&http.format);
121
122			// Tweaks are only added if the format is NOT event_id_only
123			if !event_id_only {
124				device.tweaks.clone_from(&tweaks);
125			}
126
127			let d = vec![device];
128			let mut notify = Notification::new(d);
129
130			notify.event_id = Some(event.event_id().to_owned());
131			notify.room_id = Some(event.room_id().to_owned());
132			if http
133				.data
134				.get("org.matrix.msc4076.disable_badge_count")
135				.is_none() && http.data.get("disable_badge_count").is_none()
136			{
137				notify.counts = NotificationCounts::new(unread, uint!(0));
138			} else {
139				// counts will not be serialised if it's the default (0, 0)
140				// skip_serializing_if = "NotificationCounts::is_default"
141				notify.counts = NotificationCounts::default();
142			}
143
144			if !event_id_only {
145				if *event.kind() == TimelineEventType::RoomEncrypted
146					|| tweaks.iter().any(|t| {
147						matches!(
148							t,
149							Tweak::Highlight(ruma::push::HighlightTweakValue::Yes)
150								| Tweak::Sound(_)
151						)
152					}) {
153					notify.prio = NotificationPriority::High;
154				} else {
155					notify.prio = NotificationPriority::Low;
156				}
157				notify.sender = Some(event.sender().to_owned());
158				notify.event_type = Some(event.kind().to_owned());
159				notify.content = serde_json::value::to_raw_value(event.content()).ok();
160
161				if *event.kind() == TimelineEventType::RoomMember {
162					notify.user_is_target = event.state_key() == Some(event.sender().as_str());
163				}
164
165				notify.sender_display_name = self
166					.services
167					.users
168					.displayname(event.sender())
169					.await
170					.ok();
171
172				notify.room_name = self
173					.services
174					.state_accessor
175					.get_name(event.room_id())
176					.await
177					.ok();
178
179				notify.room_alias = self
180					.services
181					.state_accessor
182					.get_canonical_alias(event.room_id())
183					.await
184					.ok();
185			}
186
187			self.send_request(&http.url, send_event_notification::v1::Request::new(notify))
188				.await?;
189
190			Ok(())
191		},
192		// TODO: Handle email
193		//PusherKind::Email(_) => Ok(()),
194		| _ => Ok(()),
195	}
196}