1use std::borrow::Borrow;
2
3use axum::extract::State;
4use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, future::try_join4};
5use ruma::{
6 CanonicalJsonObject, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId,
7 ServerName, UserId,
8 api::federation::membership::create_join_event,
9 events::{
10 StateEventType,
11 room::member::{MembershipState, RoomMemberEventContent},
12 },
13};
14use serde_json::value::RawValue as RawJsonValue;
15use tuwunel_core::{
16 Err, Result, at, debug_error, err,
17 itertools::Itertools,
18 matrix::{RoomVersionRules, event::gen_event_id_canonical_json, room_version},
19 utils::{
20 BoolExt,
21 future::{BoolExt as _, ReadyBoolExt},
22 stream::{BroadbandExt, IterStream, TryBroadbandExt, TryReadyExt},
23 },
24 warn,
25};
26use tuwunel_service::Services;
27
28use super::utils::require_known_room;
29use crate::{Ruma, client::sync::calculate_heroes};
30
31pub(crate) async fn create_join_event_v2_route(
35 State(services): State<crate::State>,
36 body: Ruma<create_join_event::v2::Request>,
37) -> Result<create_join_event::v2::Response> {
38 let room_id = &body.room_id;
39 let origin = body.origin();
40 let members_omitted = body.omit_members;
41
42 if let Some(server) = room_id.server_name()
43 && services
44 .config
45 .is_forbidden_remote_server_name(server)
46 {
47 warn!(
48 "Server {origin} tried joining {room_id} through us which has a server name that is \
49 globally forbidden. Rejecting.",
50 );
51
52 return Err!(Request(Forbidden(warn!(
53 "Room ID server name {server} is banned on this homeserver."
54 ))));
55 }
56
57 services.sending.notify_peer_alive(origin).await;
58
59 let servers_in_room = members_omitted
61 .then_async(|| {
62 services
63 .state_cache
64 .room_servers(room_id)
65 .map(ToOwned::to_owned)
66 .collect::<Vec<_>>()
67 })
68 .await;
69
70 let mut room_state =
71 create_join_event(&services, origin, room_id, &body.pdu, members_omitted)
72 .boxed()
73 .await?;
74
75 room_state.members_omitted = members_omitted;
76 room_state.servers_in_room =
77 servers_in_room.map(|servers| servers.into_iter().map(Into::into).collect());
78
79 Ok(create_join_event::v2::Response { room_state })
80}
81
82async fn create_join_event(
83 services: &Services,
84 origin: &ServerName,
85 room_id: &RoomId,
86 pdu: &RawJsonValue,
87 omit_members: bool,
88) -> Result<create_join_event::v2::RoomState> {
89 require_known_room(services, room_id, origin).await?;
90
91 let shortstatehash = services
94 .state
95 .get_room_shortstatehash(room_id)
96 .await
97 .map_err(|e| err!(Request(NotFound(error!("Room has no state: {e}")))))?;
98
99 let room_version_id = services.state.get_room_version(room_id).await?;
102
103 let Ok((event_id, mut value)) = gen_event_id_canonical_json(pdu, &room_version_id) else {
104 return Err!(Request(BadJson("Could not convert event to canonical json.")));
106 };
107
108 let (content, joining_user) =
109 validate_join_event_shape(services, &value, origin, room_id).await?;
110
111 let room_version_rules = room_version::rules(&room_version_id)?;
112
113 if let Some(authorising_user) = content.join_authorized_via_users_server {
114 validate_restricted_join(
115 services,
116 &authorising_user,
117 &joining_user,
118 room_id,
119 &room_version_rules,
120 )
121 .await?;
122 }
123
124 services
125 .server_keys
126 .hash_and_sign_event(&mut value, &room_version_id)
127 .map_err(|e| err!(Request(InvalidParam(warn!("Failed to sign send_join event: {e}")))))?;
128
129 let origin: OwnedServerName = serde_json::from_value(
130 value
131 .get("origin")
132 .ok_or_else(|| err!(Request(BadJson("Event does not have an origin server name."))))?
133 .clone()
134 .into(),
135 )
136 .map_err(|e| err!(Request(BadJson("Event has an invalid origin server name: {e}"))))?;
137
138 let heroes = omit_members
141 .then_async(|| {
142 let has_name = services.state_accessor.state_contains(
143 shortstatehash,
144 &StateEventType::RoomName,
145 "",
146 );
147
148 let has_alias = services.state_accessor.state_contains(
149 shortstatehash,
150 &StateEventType::RoomCanonicalAlias,
151 "",
152 );
153
154 has_name
155 .is_false()
156 .and(has_alias.is_false())
157 .then(|_| calculate_heroes(services, room_id, &joining_user))
158 })
159 .await
160 .unwrap_or_default();
161
162 let state_ids = services
164 .state_accessor
165 .state_full_ids(shortstatehash)
166 .broad_filter_map(async |(ssk, event_id)| {
167 if omit_members
171 && let Ok((kind, sk)) = services.short.get_statekey_from_short(ssk).await
172 && kind == StateEventType::RoomMember
173 && let Ok(user_id) = sk.as_str().try_into()
174 && joining_user != user_id
175 && !heroes.contains(&user_id)
176 {
177 return None;
178 }
179
180 Some(event_id)
181 })
182 .collect::<Vec<_>>();
183
184 let mutex_lock = services
185 .event_handler
186 .mutex_federation
187 .lock(room_id)
188 .await;
189
190 let pdu_id = services
191 .event_handler
192 .handle_incoming_pdu(&origin, room_id, &event_id, value.clone(), true)
193 .await?
194 .map(at!(0))
195 .ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
196
197 drop(mutex_lock);
198
199 let state_ids = state_ids
201 .await
202 .into_iter()
203 .sorted_unstable()
204 .collect::<Vec<_>>();
205
206 let into_federation_format = |pdu: CanonicalJsonObject| {
207 services
208 .federation
209 .format_pdu_into(pdu, Some(&room_version_id))
210 .map(Ok)
211 };
212
213 let include_auth_event =
215 |event_id: &OwnedEventId| !omit_members || state_ids.binary_search(event_id).is_err();
216
217 let auth_heads = state_ids.iter().map(Borrow::borrow);
218
219 let auth_chain = services
220 .auth_chain
221 .event_ids_iter(room_id, &room_version_id, auth_heads)
222 .ready_try_filter(include_auth_event)
223 .broad_and_then(async |event_id| {
224 services
225 .timeline
226 .get_pdu_json(&event_id)
227 .and_then(into_federation_format)
228 .inspect_err(|e| debug_error!(?event_id, "auth_chain event not found: {e}"))
229 .await
230 })
231 .try_collect();
232
233 let state = state_ids
234 .iter()
235 .try_stream()
236 .broad_and_then(async |event_id| {
237 services
238 .timeline
239 .get_pdu_json(event_id)
240 .and_then(into_federation_format)
241 .inspect_err(|e| debug_error!(?event_id, "state event not found: {e}"))
242 .await
243 })
244 .try_collect();
245
246 let event = services
248 .federation
249 .format_pdu_into(value, Some(&room_version_id))
250 .map(Some)
251 .map(Ok);
252
253 let broadcast = services.sending.send_pdu_room(room_id, &pdu_id);
255
256 let (auth_chain, state, event, ()) = try_join4(auth_chain, state, event, broadcast)
257 .boxed()
258 .await?;
259
260 Ok(create_join_event::v2::RoomState {
261 auth_chain,
262 state,
263 event,
264 ..Default::default()
265 })
266}
267
268async fn validate_join_event_shape(
269 services: &Services,
270 value: &CanonicalJsonObject,
271 origin: &ServerName,
272 room_id: &RoomId,
273) -> Result<(RoomMemberEventContent, OwnedUserId)> {
274 let event_room_id: OwnedRoomId = serde_json::from_value(
275 value
276 .get("room_id")
277 .ok_or_else(|| err!(Request(BadJson("Event missing room_id property."))))?
278 .clone()
279 .into(),
280 )
281 .map_err(|e| err!(Request(BadJson(warn!("room_id field is not a valid room ID: {e}")))))?;
282
283 if event_room_id != room_id {
284 return Err!(Request(BadJson("Event room_id does not match request path room ID.")));
285 }
286
287 let event_type: StateEventType = serde_json::from_value(
288 value
289 .get("type")
290 .ok_or_else(|| err!(Request(BadJson("Event missing type property."))))?
291 .clone()
292 .into(),
293 )
294 .map_err(|e| err!(Request(BadJson(warn!("Event has invalid state event type: {e}")))))?;
295
296 if event_type != StateEventType::RoomMember {
297 return Err!(Request(BadJson(
298 "Not allowed to send non-membership state event to join endpoint."
299 )));
300 }
301
302 let content: RoomMemberEventContent = serde_json::from_value(
303 value
304 .get("content")
305 .ok_or_else(|| err!(Request(BadJson("Event missing content property"))))?
306 .clone()
307 .into(),
308 )
309 .map_err(|e| err!(Request(BadJson(warn!("Event content is empty or invalid: {e}")))))?;
310
311 if content.membership != MembershipState::Join {
312 return Err!(Request(BadJson(
313 "Not allowed to send a non-join membership event to join endpoint."
314 )));
315 }
316
317 let sender: OwnedUserId = serde_json::from_value(
319 value
320 .get("sender")
321 .ok_or_else(|| err!(Request(BadJson("Event missing sender property."))))?
322 .clone()
323 .into(),
324 )
325 .map_err(|e| err!(Request(BadJson(warn!("sender property is not a valid user ID: {e}")))))?;
326
327 services
328 .event_handler
329 .acl_check(sender.server_name(), room_id)
330 .await?;
331
332 if sender.server_name() != origin {
334 return Err!(Request(Forbidden("Not allowed to join on behalf of another server.")));
335 }
336
337 let joining_user: OwnedUserId = serde_json::from_value(
338 value
339 .get("state_key")
340 .ok_or_else(|| err!(Request(BadJson("Event missing state_key property."))))?
341 .clone()
342 .into(),
343 )
344 .map_err(|e| err!(Request(BadJson(warn!("State key is not a valid user ID: {e}")))))?;
345
346 if joining_user != sender {
347 return Err!(Request(BadJson("State key does not match sender user.")));
348 }
349
350 Ok((content, joining_user))
351}
352
353async fn validate_restricted_join(
354 services: &Services,
355 authorising_user: &UserId,
356 joining_user: &UserId,
357 room_id: &RoomId,
358 room_version_rules: &RoomVersionRules,
359) -> Result {
360 if !room_version_rules
361 .authorization
362 .restricted_join_rule
363 {
364 return Err!(Request(InvalidParam(
365 "Room version does not support restricted rooms but \
366 join_authorised_via_users_server ({authorising_user}) was found in the event."
367 )));
368 }
369
370 if !services.globals.user_is_local(authorising_user) {
371 return Err!(Request(InvalidParam(
372 "Cannot authorise membership event through {authorising_user} as they do not belong \
373 to this homeserver"
374 )));
375 }
376
377 if !services
378 .state_cache
379 .is_joined(authorising_user, room_id)
380 .await
381 {
382 return Err!(Request(InvalidParam(
383 "Authorising user {authorising_user} is not in the room you are trying to join, \
384 they cannot authorise your join."
385 )));
386 }
387
388 if !super::user_can_perform_restricted_join(
389 services,
390 joining_user,
391 room_id,
392 room_version_rules,
393 )
394 .await?
395 {
396 return Err!(Request(UnableToAuthorizeJoin(
397 "Joining user did not pass restricted room's rules."
398 )));
399 }
400
401 Ok(())
402}