1use std::{collections::BTreeMap, mem::take};
2
3use axum::extract::State;
4use base64::{Engine as _, engine::general_purpose};
5use futures::StreamExt;
6use ruma::{
7 CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, OwnedUserId,
8 RoomId, RoomVersionId, ServerName, UserId,
9 api::{
10 appservice::event::push_events::{self, v1::DeviceLists},
11 federation::membership::{RawStrippedState, create_invite},
12 },
13 events::{
14 AnyStrippedStateEvent, GlobalAccountDataEventType, StateEventType,
15 invite_permission_config::InvitePermission,
16 push_rules::PushRulesEvent,
17 room::member::{MembershipState, RoomMemberEventContent},
18 },
19 push,
20 serde::{JsonObject, Raw},
21};
22use serde::Deserialize;
23use tuwunel_core::{Err, Result, debug_warn, err, utils, utils::hash::sha256};
24use tuwunel_matrix::{Event, PduCount, PduEvent, event::gen_event_id};
25use tuwunel_service::{
26 Services,
27 membership::{
28 StrippedCreateVerdict, dedup_stripped_state, enforce_stripped_create,
29 into_client_stripped, v12_room_ids, without_member,
30 },
31 rooms::state_cache::MembershipUpdate,
32};
33
34use crate::{ClientIp, Ruma};
35
36#[derive(Deserialize)]
38struct ExtractIsDirect {
39 #[serde(default)]
40 is_direct: bool,
41}
42
43struct LocalInvite<'a> {
44 invited_user: &'a UserId,
45 sender: &'a UserId,
46 invite_state: Vec<Raw<AnyStrippedStateEvent>>,
47 pdu: &'a PduEvent,
48 invite_event: &'a EventId,
49}
50
51#[tracing::instrument(skip_all, fields(%client), name = "invite")]
55pub(crate) async fn create_invite_route(
56 State(services): State<crate::State>,
57 ClientIp(client): ClientIp,
58 mut body: Ruma<create_invite::v2::Request>,
59) -> Result<create_invite::v2::Response> {
60 services
61 .sending
62 .notify_peer_alive(body.origin())
63 .await;
64
65 validate_request(&services, &body).await?;
66
67 let stripped_state = dedup_stripped_state(take(&mut body.body.invite_room_state));
69
70 enforce_stripped_state(&services, &body, &stripped_state).await?;
71
72 let (mut signed_event, invited_user) = parse_and_validate_event(&services, &body).await?;
73
74 let invite_event = sign_event(&services, &mut signed_event, &body.room_version)?;
75
76 let sender = validate_origins(&signed_event, body.origin())?;
77
78 check_invite_permitted(&services, &body, &invited_user, sender).await?;
79
80 let pdu = build_pdu(&body)?;
81
82 let invite_state: Vec<_> = without_member(stripped_state, &invited_user)
84 .filter_map(|state| into_client_stripped(&body.room_id, state))
85 .chain([pdu.to_format()])
86 .collect();
87
88 let _federation_lock = services
91 .event_handler
92 .mutex_federation
93 .lock(&body.room_id)
94 .await;
95
96 let invite = LocalInvite {
97 invited_user: &invited_user,
98 sender,
99 invite_state,
100 pdu: &pdu,
101 invite_event: &invite_event,
102 };
103
104 record_local_invite(&services, &body, invite).await?;
105
106 Ok(create_invite::v2::Response {
107 event: services
108 .federation
109 .format_pdu_into(signed_event, Some(&body.room_version))
110 .await,
111 })
112}
113
114async fn validate_request(
115 services: &Services,
116 body: &Ruma<create_invite::v2::Request>,
117) -> Result<()> {
118 services
119 .event_handler
120 .acl_check(body.origin(), &body.room_id)
121 .await?;
122
123 services
124 .config
125 .require_supported_room_version(&body.room_version)?;
126
127 if let Some(server) = body.room_id.server_name()
128 && services
129 .config
130 .is_forbidden_remote_server_name(server)
131 {
132 return Err!(Request(Forbidden("Server is banned on this homeserver.")));
133 }
134
135 Ok(())
136}
137
138async fn enforce_stripped_state(
141 services: &Services,
142 body: &Ruma<create_invite::v2::Request>,
143 stripped_state: &[RawStrippedState],
144) -> Result {
145 let verdict = Box::pin(services.membership.validate_stripped_create(
147 stripped_state,
148 &body.room_id,
149 &body.room_version,
150 ))
151 .await?;
152
153 if verdict != StrippedCreateVerdict::Valid {
154 debug_warn!(
155 ?verdict,
156 room_id = %body.room_id,
157 "MSC4311 invite create-event validation failed",
158 );
159 }
160
161 let enforce = services
162 .config
163 .federation
164 .enforce_stripped_state_pdu_validation;
165
166 if enforce_stripped_create(verdict, v12_room_ids(&body.room_version), enforce) {
167 return Err!(Request(MissingParam(
168 "The invite's m.room.create event is missing or does not validate for this room."
169 )));
170 }
171
172 Ok(())
173}
174
175async fn parse_and_validate_event(
176 services: &Services,
177 body: &Ruma<create_invite::v2::Request>,
178) -> Result<(CanonicalJsonObject, OwnedUserId)> {
179 let signed_event = utils::to_canonical_object(&body.event)
180 .map_err(|_| err!(Request(InvalidParam("Invite event is invalid."))))?;
181
182 let room_id: OwnedRoomId = signed_event
183 .get("room_id")
184 .try_into()
185 .map(RoomId::to_owned)
186 .map_err(|e| err!(Request(InvalidParam("Invalid room_id property: {e}"))))?;
187
188 if body.room_id != room_id {
189 return Err!(Request(InvalidParam("Event room_id does not match the request path.")));
190 }
191
192 let kind: StateEventType = signed_event
193 .get("type")
194 .and_then(CanonicalJsonValue::as_str)
195 .ok_or_else(|| err!(Request(BadJson("Missing type in event."))))?
196 .into();
197
198 if kind != StateEventType::RoomMember {
199 return Err!(Request(InvalidParam("Event must be m.room.member type.")));
200 }
201
202 let invited_user: OwnedUserId = signed_event
203 .get("state_key")
204 .try_into()
205 .map(UserId::to_owned)
206 .map_err(|e| err!(Request(InvalidParam("Invalid state_key property: {e}"))))?;
207
208 if !services.globals.user_is_local(&invited_user) {
209 return Err!(Request(InvalidParam("User does not belong to this homeserver.")));
210 }
211
212 let content: RoomMemberEventContent = signed_event
213 .get("content")
214 .cloned()
215 .map(Into::into)
216 .map(serde_json::from_value)
217 .transpose()
218 .map_err(|e| err!(Request(InvalidParam("Invalid content object in event: {e}"))))?
219 .ok_or_else(|| err!(Request(BadJson("Missing content in event."))))?;
220
221 if content.membership != MembershipState::Invite {
222 return Err!(Request(InvalidParam("Event membership must be invite.")));
223 }
224
225 services
226 .event_handler
227 .acl_check(invited_user.server_name(), &body.room_id)
228 .await?;
229
230 Ok((signed_event, invited_user))
231}
232
233fn sign_event(
234 services: &Services,
235 signed_event: &mut CanonicalJsonObject,
236 room_version: &RoomVersionId,
237) -> Result<OwnedEventId> {
238 services
239 .server_keys
240 .hash_and_sign_event(signed_event, room_version)
241 .map_err(|e| err!(Request(InvalidParam("Failed to sign event: {e}"))))?;
242
243 let event_id = gen_event_id(signed_event, room_version)?;
244 signed_event.insert("event_id".into(), CanonicalJsonValue::String(event_id.to_string()));
245
246 Ok(event_id)
247}
248
249fn validate_origins<'a>(
250 signed_event: &'a CanonicalJsonObject,
251 body_origin: &ServerName,
252) -> Result<&'a UserId> {
253 let origin: Option<&str> = signed_event
254 .get("origin")
255 .and_then(CanonicalJsonValue::as_str);
256
257 let sender: &UserId = signed_event
258 .get("sender")
259 .try_into()
260 .map_err(|e| err!(Request(InvalidParam("Invalid sender property: {e}"))))?;
261
262 if sender.server_name() != body_origin {
263 return Err!(Request(Forbidden("Can only send invites on behalf of your users.")));
264 }
265
266 if origin.is_some_and(|origin| origin != body_origin) {
267 return Err!(Request(Forbidden("Can only send events from your origin.")));
268 }
269
270 Ok(sender)
271}
272
273async fn check_invite_permitted(
274 services: &Services,
275 body: &Ruma<create_invite::v2::Request>,
276 invited_user: &UserId,
277 sender: &UserId,
278) -> Result<()> {
279 if services.metadata.is_banned(&body.room_id).await
280 && !services.admin.user_is_admin(invited_user).await
281 {
282 return Err!(Request(Forbidden("This room is banned on this homeserver.")));
283 }
284
285 if services.config.rooms.invites.block_non_admin
286 && !services.admin.user_is_admin(invited_user).await
287 {
288 return Err!(Request(Forbidden("This server does not allow room invites.")));
289 }
290
291 if services
296 .users
297 .invite_permission(sender, invited_user)
298 .await
299 .eq(&InvitePermission::Block)
300 {
301 return Err!(Request(InviteBlocked("{invited_user} has blocked this invite.")));
302 }
303
304 Ok(())
305}
306
307fn build_pdu(body: &Ruma<create_invite::v2::Request>) -> Result<PduEvent> {
308 let mut event: JsonObject = serde_json::from_str(body.event.get())
309 .map_err(|e| err!(Request(BadJson("Invalid invite event PDU: {e}"))))?;
310
311 event.insert("event_id".into(), "$placeholder".into());
312
313 serde_json::from_value(event.into())
314 .map_err(|e| err!(Request(BadJson("Invalid invite event PDU: {e}"))))
315}
316
317async fn record_local_invite(
324 services: &Services,
325 body: &Ruma<create_invite::v2::Request>,
326 LocalInvite {
327 invited_user,
328 sender,
329 invite_state,
330 pdu,
331 invite_event,
332 }: LocalInvite<'_>,
333) -> Result {
334 let state_lock = services.state.mutex.lock(&body.room_id).await;
335
336 if services
337 .state_cache
338 .server_in_room(services.globals.server_name(), &body.room_id)
339 .await
340 {
341 return Ok(());
342 }
343
344 if services
345 .state_accessor
346 .room_state_get_content::<RoomMemberEventContent>(
347 &body.room_id,
348 &StateEventType::RoomMember,
349 invited_user.as_str(),
350 )
351 .await
352 .is_ok_and(|content| content.membership == MembershipState::Ban)
353 {
354 debug_warn!(
355 room_id = %body.room_id,
356 user_id = %invited_user,
357 "Recording invite while local room state shows banned membership.",
358 );
359 }
360
361 let count = services.globals.next_count();
362 services
363 .state_cache
364 .update_membership(MembershipUpdate {
365 room_id: &body.room_id,
366 user_id: invited_user,
367 membership_event: RoomMemberEventContent::new(MembershipState::Invite),
368 sender,
369 last_state: Some(invite_state),
370 invite_via: body.via.clone(),
371 invite_event: Some(invite_event),
372 update_joined_count: true,
373 replaced_join: None,
374 count: PduCount::Normal(*count),
375 })
376 .await?;
377
378 drop(count);
379 drop(state_lock);
380
381 let is_direct = pdu
382 .get_content()
383 .is_ok_and(|content: ExtractIsDirect| content.is_direct);
384
385 services
386 .membership
387 .auto_accept(&body.room_id, invited_user, sender, is_direct);
388
389 notify_pushers(services, invited_user, pdu).await;
390
391 for appservice in services.appservice.read().await.values() {
392 if appservice.is_user_match(invited_user) {
393 services
394 .appservice
395 .send_request(appservice.registration.clone(), push_events::v1::Request {
396 events: vec![pdu.to_format()],
397 txn_id: general_purpose::URL_SAFE_NO_PAD
398 .encode(sha256::hash(pdu.event_id.as_bytes()))
399 .into(),
400 ephemeral: Vec::new(),
401 to_device: Vec::new(),
402 device_lists: DeviceLists::new(),
403 device_one_time_keys_count: BTreeMap::new(),
404 device_unused_fallback_key_types: BTreeMap::new(),
405 })
406 .await
407 .map_err(|_| {
408 err!(BadServerResponse("Failed to notify appservice about incoming invite."))
409 })?;
410 }
411 }
412
413 Ok(())
414}
415
416async fn notify_pushers(services: &Services, invited_user: &UserId, pdu: &PduEvent) {
417 services
418 .pusher
419 .get_pushkeys(invited_user)
420 .map(ToOwned::to_owned)
421 .for_each(async |pushkey| {
422 let Ok(pusher) = services
423 .pusher
424 .get_pusher(invited_user, &pushkey)
425 .await
426 else {
427 return;
428 };
429
430 let ruleset = services
431 .account_data
432 .get_global(invited_user, GlobalAccountDataEventType::PushRules)
433 .await
434 .map_or_else(
435 |_| push::Ruleset::server_default(invited_user),
436 |ev: PushRulesEvent| ev.content.global,
437 );
438
439 services
440 .pusher
441 .send_push_notice(invited_user, &pusher, &ruleset, pdu)
442 .await
443 .ok();
444 })
445 .await;
446}