tuwunel_service/rooms/state_accessor/
user_can.rs1use futures::pin_mut;
2use ruma::{
3 EventId, RoomId, UserId,
4 events::{
5 StateEventType, TimelineEventType,
6 room::{
7 history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
8 member::{MembershipState, RoomMemberEventContent},
9 tombstone::RoomTombstoneEventContent,
10 },
11 },
12};
13use tuwunel_core::{
14 Err, Result, implement,
15 matrix::{Event, PduCount, StateKey},
16 pdu::PduBuilder,
17 utils::FutureBoolExt,
18};
19
20use crate::rooms::{short::ShortStateHash, state::RoomMutexGuard};
21
22#[implement(super::Service)]
27pub async fn user_can_redact(
28 &self,
29 redacts: &EventId,
30 sender: &UserId,
31 room_id: &RoomId,
32 federation: bool,
33) -> Result<bool> {
34 let redacting_event = self.services.timeline.get_pdu(redacts).await;
35
36 if redacting_event
37 .as_ref()
38 .is_ok_and(|pdu| *pdu.kind() == TimelineEventType::RoomCreate)
39 {
40 return Err!(Request(Forbidden("Redacting m.room.create is not safe, forbidding.")));
41 }
42
43 if redacting_event
44 .as_ref()
45 .is_ok_and(|pdu| *pdu.kind() == TimelineEventType::RoomServerAcl)
46 {
47 return Err!(Request(Forbidden(
48 "Redacting m.room.server_acl will result in the room being inaccessible for \
49 everyone (empty allow key), forbidding."
50 )));
51 }
52
53 match self.get_power_levels(room_id).await {
54 | Ok(power_levels) => Ok(power_levels.user_can_redact_event_of_other(sender)
55 || power_levels.user_can_redact_own_event(sender)
56 && match redacting_event {
57 | Ok(redacting_event) =>
58 if federation {
59 redacting_event.sender().server_name() == sender.server_name()
60 } else {
61 redacting_event.sender() == sender
62 },
63 | _ => false,
64 }),
65 | _ => {
66 match self
68 .room_state_get(room_id, &StateEventType::RoomCreate, "")
69 .await
70 {
71 | Ok(room_create) => Ok(room_create.sender() == sender
72 || redacting_event
73 .as_ref()
74 .is_ok_and(|redacting_event| redacting_event.sender() == sender)),
75 | _ => Err!(Database(
76 "No m.room.power_levels or m.room.create events in database for room"
77 )),
78 }
79 },
80 }
81}
82
83#[implement(super::Service)]
86#[tracing::instrument(skip_all, level = "trace")]
87pub async fn user_can_see_event(
88 &self,
89 user_id: &UserId,
90 room_id: &RoomId,
91 event_id: &EventId,
92) -> bool {
93 let Ok(shortstatehash) = self
94 .services
95 .state
96 .pdu_shortstatehash(event_id)
97 .await
98 else {
99 return true;
100 };
101
102 let history_visibility = self
103 .state_get_content(shortstatehash, &StateEventType::RoomHistoryVisibility, "")
104 .await
105 .map_or(HistoryVisibility::Shared, |c: RoomHistoryVisibilityEventContent| {
106 c.history_visibility
107 });
108
109 match history_visibility {
110 | HistoryVisibility::WorldReadable => true,
111
112 | HistoryVisibility::Invited =>
114 self.user_was_invited(shortstatehash, user_id)
115 .await,
116
117 | HistoryVisibility::Joined =>
119 self.user_was_joined(shortstatehash, user_id)
120 .await,
121
122 | HistoryVisibility::Shared | _ =>
124 self.user_shared_history(shortstatehash, room_id, event_id, user_id)
125 .await,
126 }
127}
128
129#[implement(super::Service)]
135async fn user_shared_history(
136 &self,
137 shortstatehash: ShortStateHash,
138 room_id: &RoomId,
139 event_id: &EventId,
140 user_id: &UserId,
141) -> bool {
142 let state_cache = &self.services.state_cache;
143
144 if state_cache.is_joined(user_id, room_id).await
145 || self
146 .user_was_joined(shortstatehash, user_id)
147 .await
148 {
149 return true;
150 }
151
152 if !state_cache.once_joined(user_id, room_id).await {
153 return false;
154 }
155
156 let Ok(left_count) = state_cache.get_left_count(room_id, user_id).await else {
157 return false;
158 };
159
160 let Ok(event_count) = self
161 .services
162 .timeline
163 .get_pdu_count(event_id)
164 .await
165 else {
166 return false;
167 };
168
169 event_count <= PduCount::from_unsigned(left_count)
170}
171
172#[implement(super::Service)]
175#[tracing::instrument(skip_all, level = "trace")]
176pub async fn user_can_see_state_events(&self, user_id: &UserId, room_id: &RoomId) -> bool {
177 if self
178 .services
179 .state_cache
180 .is_joined(user_id, room_id)
181 .await
182 {
183 return true;
184 }
185
186 let history_visibility = self
187 .room_state_get_content(room_id, &StateEventType::RoomHistoryVisibility, "")
188 .await
189 .map_or(HistoryVisibility::Shared, |c: RoomHistoryVisibilityEventContent| {
190 c.history_visibility
191 });
192
193 match history_visibility {
194 | HistoryVisibility::WorldReadable => true,
195
196 | HistoryVisibility::Invited =>
197 self.services
198 .state_cache
199 .is_invited(user_id, room_id)
200 .await,
201
202 | HistoryVisibility::Shared =>
203 self.services
204 .state_cache
205 .once_joined(user_id, room_id)
206 .await,
207
208 | _ => false,
209 }
210}
211
212#[implement(super::Service)]
216pub async fn user_can_see_room(&self, user_id: &UserId, room_id: &RoomId) -> bool {
217 let state_cache = &self.services.state_cache;
218 let joined = state_cache.is_joined(user_id, room_id);
219 let invited = state_cache.is_invited(user_id, room_id);
220 let left = state_cache.is_left(user_id, room_id);
221 let world_readable = self.is_world_readable(room_id);
222
223 pin_mut!(joined, invited, left, world_readable);
224 joined
225 .or(invited)
226 .or(left)
227 .or(world_readable)
228 .await
229}
230
231#[implement(super::Service)]
232pub async fn user_can_invite(
233 &self,
234 room_id: &RoomId,
235 sender: &UserId,
236 target_user: &UserId,
237 state_lock: &RoomMutexGuard,
238) -> bool {
239 self.services
240 .timeline
241 .create_hash_and_sign_event(
242 PduBuilder::state(
243 target_user.as_str(),
244 &RoomMemberEventContent::new(MembershipState::Invite),
245 ),
246 sender,
247 room_id,
248 state_lock,
249 )
250 .await
251 .is_ok()
252}
253
254#[implement(super::Service)]
255pub async fn user_can_tombstone(
256 &self,
257 room_id: &RoomId,
258 user_id: &UserId,
259 state_lock: &RoomMutexGuard,
260) -> bool {
261 if !self
262 .services
263 .state_cache
264 .is_joined(user_id, room_id)
265 .await
266 {
267 return false;
268 }
269
270 self.services
271 .timeline
272 .create_hash_and_sign_event(
273 PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
274 replacement_room: room_id.into(), body: "Not a valid m.room.tombstone.".into(),
276 }),
277 user_id,
278 room_id,
279 state_lock,
280 )
281 .await
282 .is_ok()
283}