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, StateKey},
16 pdu::PduBuilder,
17 utils::FutureBoolExt,
18};
19
20use crate::rooms::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 | _ =>
123 self.services
124 .state_cache
125 .is_joined(user_id, room_id)
126 .await,
127 }
128}
129
130#[implement(super::Service)]
133#[tracing::instrument(skip_all, level = "trace")]
134pub async fn user_can_see_state_events(&self, user_id: &UserId, room_id: &RoomId) -> bool {
135 if self
136 .services
137 .state_cache
138 .is_joined(user_id, room_id)
139 .await
140 {
141 return true;
142 }
143
144 let history_visibility = self
145 .room_state_get_content(room_id, &StateEventType::RoomHistoryVisibility, "")
146 .await
147 .map_or(HistoryVisibility::Shared, |c: RoomHistoryVisibilityEventContent| {
148 c.history_visibility
149 });
150
151 match history_visibility {
152 | HistoryVisibility::WorldReadable => true,
153
154 | HistoryVisibility::Invited =>
155 self.services
156 .state_cache
157 .is_invited(user_id, room_id)
158 .await,
159
160 | HistoryVisibility::Shared =>
161 self.services
162 .state_cache
163 .once_joined(user_id, room_id)
164 .await,
165
166 | _ => false,
167 }
168}
169
170#[implement(super::Service)]
174pub async fn user_can_see_room(&self, user_id: &UserId, room_id: &RoomId) -> bool {
175 let state_cache = &self.services.state_cache;
176 let joined = state_cache.is_joined(user_id, room_id);
177 let invited = state_cache.is_invited(user_id, room_id);
178 let left = state_cache.is_left(user_id, room_id);
179 let world_readable = self.is_world_readable(room_id);
180
181 pin_mut!(joined, invited, left, world_readable);
182 joined
183 .or(invited)
184 .or(left)
185 .or(world_readable)
186 .await
187}
188
189#[implement(super::Service)]
190pub async fn user_can_invite(
191 &self,
192 room_id: &RoomId,
193 sender: &UserId,
194 target_user: &UserId,
195 state_lock: &RoomMutexGuard,
196) -> bool {
197 self.services
198 .timeline
199 .create_hash_and_sign_event(
200 PduBuilder::state(
201 target_user.as_str(),
202 &RoomMemberEventContent::new(MembershipState::Invite),
203 ),
204 sender,
205 room_id,
206 state_lock,
207 )
208 .await
209 .is_ok()
210}
211
212#[implement(super::Service)]
213pub async fn user_can_tombstone(
214 &self,
215 room_id: &RoomId,
216 user_id: &UserId,
217 state_lock: &RoomMutexGuard,
218) -> bool {
219 if !self
220 .services
221 .state_cache
222 .is_joined(user_id, room_id)
223 .await
224 {
225 return false;
226 }
227
228 self.services
229 .timeline
230 .create_hash_and_sign_event(
231 PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
232 replacement_room: room_id.into(), body: "Not a valid m.room.tombstone.".into(),
234 }),
235 user_id,
236 room_id,
237 state_lock,
238 )
239 .await
240 .is_ok()
241}