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