1use std::{borrow::Borrow, iter::once};
2
3use futures::{
4 FutureExt, Stream, StreamExt,
5 future::{join, join3, try_join},
6};
7use itertools::Itertools;
8use ruma::{EventId, OwnedEventId, OwnedRoomId, RoomId, RoomVersionId, ServerName};
9use serde::Deserialize;
10use serde_json::value::RawValue as RawJsonValue;
11use tuwunel_core::{
12 Err, Result, at, err, implement, is_false,
13 result::NotFound,
14 utils::{FutureBoolExt, IterStream, ReadyExt, future::ReadyBoolExt, option::OptionExt},
15 warn,
16};
17use tuwunel_service::{Services, rooms::short::ShortEventId};
18
19type StateAtEvent = (RoomVersionId, Vec<OwnedEventId>);
20
21pub(super) struct AccessCheck<'a> {
22 pub(super) services: &'a Services,
23 pub(super) origin: &'a ServerName,
24 pub(super) room_id: &'a RoomId,
25 pub(super) event_id: Option<&'a EventId>,
26}
27
28#[implement(AccessCheck, params = "<'_>")]
29pub(super) async fn check(&self) -> Result {
30 let acl_check = self
31 .services
32 .event_handler
33 .acl_check(self.origin, self.room_id)
34 .map(|result| result.is_ok());
35
36 let server_in_room = self
37 .services
38 .state_cache
39 .server_in_room(self.origin, self.room_id);
40
41 let world_readable = self
42 .services
43 .state_accessor
44 .is_world_readable(self.room_id);
45
46 let server_can_see = self.event_id.map_async(|event_id| {
47 self.services
48 .state_accessor
49 .server_can_see_event(self.origin, self.room_id, event_id)
50 });
51
52 let room_unreachable = server_in_room
54 .is_false()
55 .and(world_readable.is_false());
56
57 let (acl_check, room_unreachable, server_can_see) =
58 join3(acl_check, room_unreachable, server_can_see).await;
59
60 if !acl_check {
61 return Err!(Request(Forbidden("Server access denied.")));
62 }
63
64 if room_unreachable {
65 return Err!(Request(Forbidden("Server is not in room.")));
66 }
67
68 if server_can_see.is_some_and(is_false!()) {
69 return Err!(Request(Forbidden("Server is not allowed to see event.")));
70 }
71
72 Ok(())
73}
74
75pub(super) async fn require_known_room(
76 services: &Services,
77 room_id: &RoomId,
78 origin: &ServerName,
79) -> Result {
80 if !services.metadata.exists(room_id).await {
81 return Err!(Request(NotFound("Room is unknown to this server.")));
82 }
83
84 services
85 .event_handler
86 .acl_check(origin, room_id)
87 .await
88}
89
90pub(super) async fn state_at_event(
95 services: &Services,
96 origin: &ServerName,
97 room_id: &RoomId,
98 event_id: &EventId,
99) -> Result<StateAtEvent> {
100 AccessCheck {
101 services,
102 origin,
103 room_id,
104 event_id: None,
105 }
106 .check()
107 .await?;
108
109 require_event_in_room(services, event_id, room_id).await?;
110
111 let shortstatehash = services
112 .state
113 .pdu_shortstatehash(event_id)
114 .await
115 .map_err(|_| err!(Request(NotFound("PDU state not found."))))?;
116
117 let state_ids = services
118 .state_accessor
119 .state_full_ids(shortstatehash)
120 .map(at!(1))
121 .collect()
122 .map(Ok);
123
124 let room_version = services.state.get_room_version(room_id);
125
126 try_join(room_version, state_ids).await
127}
128
129pub(super) async fn require_event_in_room(
130 services: &Services,
131 event_id: &EventId,
132 room_id: &RoomId,
133) -> Result {
134 #[derive(Deserialize)]
135 struct PduRoomId {
136 room_id: OwnedRoomId,
137 }
138
139 services
140 .timeline
141 .get::<PduRoomId>(event_id)
142 .await
143 .is_ok_and(|pdu| pdu.room_id == room_id)
144 .then_some(())
145 .ok_or_else(|| err!(Request(NotFound("Event not found."))))
146}
147
148pub(super) fn walk_state_auth_chain<'a>(
155 services: &'a Services,
156 room_id: &'a RoomId,
157 room_version: &'a RoomVersionId,
158 state: &'a [OwnedEventId],
159 event_id: &'a EventId,
160) -> impl Stream<Item = OwnedEventId> + Send + 'a {
161 let state_heads = state.iter().map(Borrow::borrow);
162 let state_chain = short_auth_chain(services, room_id, room_version, state_heads);
163 let event_chain = short_auth_chain(services, room_id, room_version, once(event_id));
164
165 let chain = join(state_chain, event_chain)
166 .map(|(state_chain, event_chain)| state_chain.into_iter().merge(event_chain).dedup());
167
168 auth_chain_ids(services, chain)
169}
170
171pub(super) fn walk_auth_chain<'a, I>(
176 services: &'a Services,
177 room_id: &'a RoomId,
178 room_version: &'a RoomVersionId,
179 heads: I,
180) -> impl Stream<Item = OwnedEventId> + Send + 'a
181where
182 I: Iterator<Item = &'a EventId> + Clone + ExactSizeIterator + Send + 'a,
183{
184 let chain = short_auth_chain(services, room_id, room_version, heads);
185
186 auth_chain_ids(services, chain)
187}
188
189async fn short_auth_chain<'a, I>(
190 services: &'a Services,
191 room_id: &RoomId,
192 room_version: &RoomVersionId,
193 heads: I,
194) -> Vec<ShortEventId>
195where
196 I: Iterator<Item = &'a EventId> + Clone + ExactSizeIterator + Send + 'a,
197{
198 services
199 .auth_chain
200 .get_auth_chain(room_id, room_version, heads)
201 .await
202 .inspect_err(|error| warn!(%room_id, %error, "Omitting the auth chain of a failed walk."))
203 .unwrap_or_default()
204}
205
206fn auth_chain_ids<'a, F>(
207 services: &'a Services,
208 chain: F,
209) -> impl Stream<Item = OwnedEventId> + Send + 'a
210where
211 F: Future + Send + 'a,
212 F::Output: IntoIterator<Item = ShortEventId> + Send + 'a,
213 <F::Output as IntoIterator>::IntoIter: Send + 'a,
214{
215 chain
216 .map(|chain| {
217 services
218 .short
219 .multi_get_eventid_from_short(chain.stream())
220 })
221 .flatten_stream()
222 .ready_filter_map(Result::ok)
223}
224
225pub(super) async fn auth_chain_pdu(
231 services: &Services,
232 event_id: &EventId,
233 room_version: &RoomVersionId,
234) -> Option<Box<RawJsonValue>> {
235 services
236 .federation
237 .get_pdu_into(event_id, Some(room_version))
238 .await
239 .present()
240 .inspect_err(|error| warn!(%event_id, %error, "Omitting unreadable auth event."))
241 .ok()
242 .flatten()
243}
244
245pub(super) async fn state_pdu(
251 services: &Services,
252 event_id: &EventId,
253 room_version: &RoomVersionId,
254) -> Option<Box<RawJsonValue>> {
255 services
256 .federation
257 .get_pdu_into(event_id, Some(room_version))
258 .await
259 .inspect_err(|error| warn!(%event_id, %error, "Omitting unreadable state event."))
260 .ok()
261}