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