tuwunel_service/rooms/state_accessor/
user_can.rs1use ruma::{
2 EventId, RoomId, UserId,
3 events::{
4 StateEventType, TimelineEventType,
5 room::{
6 history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
7 member::{MembershipState, RoomMemberEventContent},
8 tombstone::RoomTombstoneEventContent,
9 },
10 },
11};
12use tuwunel_core::{
13 Err, Result, implement,
14 matrix::{Event, StateKey},
15 pdu::PduBuilder,
16};
17
18use crate::rooms::state::RoomMutexGuard;
19
20#[implement(super::Service)]
25pub async fn user_can_redact(
26 &self,
27 redacts: &EventId,
28 sender: &UserId,
29 room_id: &RoomId,
30 federation: bool,
31) -> Result<bool> {
32 let redacting_event = self.services.timeline.get_pdu(redacts).await;
33
34 if redacting_event
35 .as_ref()
36 .is_ok_and(|pdu| *pdu.kind() == TimelineEventType::RoomCreate)
37 {
38 return Err!(Request(Forbidden("Redacting m.room.create is not safe, forbidding.")));
39 }
40
41 if redacting_event
42 .as_ref()
43 .is_ok_and(|pdu| *pdu.kind() == TimelineEventType::RoomServerAcl)
44 {
45 return Err!(Request(Forbidden(
46 "Redacting m.room.server_acl will result in the room being inaccessible for \
47 everyone (empty allow key), forbidding."
48 )));
49 }
50
51 match self.get_power_levels(room_id).await {
52 | Ok(power_levels) => Ok(power_levels.user_can_redact_event_of_other(sender)
53 || power_levels.user_can_redact_own_event(sender)
54 && match redacting_event {
55 | Ok(redacting_event) =>
56 if federation {
57 redacting_event.sender().server_name() == sender.server_name()
58 } else {
59 redacting_event.sender() == sender
60 },
61 | _ => false,
62 }),
63 | _ => {
64 match self
66 .room_state_get(room_id, &StateEventType::RoomCreate, "")
67 .await
68 {
69 | Ok(room_create) => Ok(room_create.sender() == sender
70 || redacting_event
71 .as_ref()
72 .is_ok_and(|redacting_event| redacting_event.sender() == sender)),
73 | _ => Err!(Database(
74 "No m.room.power_levels or m.room.create events in database for room"
75 )),
76 }
77 },
78 }
79}
80
81#[implement(super::Service)]
84#[tracing::instrument(skip_all, level = "trace")]
85pub async fn user_can_see_event(
86 &self,
87 user_id: &UserId,
88 room_id: &RoomId,
89 event_id: &EventId,
90) -> bool {
91 let Ok(shortstatehash) = self
92 .services
93 .state
94 .pdu_shortstatehash(event_id)
95 .await
96 else {
97 return true;
98 };
99
100 let history_visibility = self
101 .state_get_content(shortstatehash, &StateEventType::RoomHistoryVisibility, "")
102 .await
103 .map_or(HistoryVisibility::Shared, |c: RoomHistoryVisibilityEventContent| {
104 c.history_visibility
105 });
106
107 match history_visibility {
108 | HistoryVisibility::WorldReadable => true,
109
110 | HistoryVisibility::Invited =>
112 self.user_was_invited(shortstatehash, user_id)
113 .await,
114
115 | HistoryVisibility::Joined =>
117 self.user_was_joined(shortstatehash, user_id)
118 .await,
119
120 | HistoryVisibility::Shared | _ =>
121 self.services
122 .state_cache
123 .is_joined(user_id, room_id)
124 .await,
125 }
126}
127
128#[implement(super::Service)]
131#[tracing::instrument(skip_all, level = "trace")]
132pub async fn user_can_see_state_events(&self, user_id: &UserId, room_id: &RoomId) -> bool {
133 if self
134 .services
135 .state_cache
136 .is_joined(user_id, room_id)
137 .await
138 {
139 return true;
140 }
141
142 let history_visibility = self
143 .room_state_get_content(room_id, &StateEventType::RoomHistoryVisibility, "")
144 .await
145 .map_or(HistoryVisibility::Shared, |c: RoomHistoryVisibilityEventContent| {
146 c.history_visibility
147 });
148
149 match history_visibility {
150 | HistoryVisibility::WorldReadable => true,
151
152 | HistoryVisibility::Invited =>
153 self.services
154 .state_cache
155 .is_invited(user_id, room_id)
156 .await,
157
158 | HistoryVisibility::Shared =>
159 self.services
160 .state_cache
161 .once_joined(user_id, room_id)
162 .await,
163
164 | _ => false,
165 }
166}
167
168#[implement(super::Service)]
169pub async fn user_can_invite(
170 &self,
171 room_id: &RoomId,
172 sender: &UserId,
173 target_user: &UserId,
174 state_lock: &RoomMutexGuard,
175) -> bool {
176 self.services
177 .timeline
178 .create_hash_and_sign_event(
179 PduBuilder::state(
180 target_user.as_str(),
181 &RoomMemberEventContent::new(MembershipState::Invite),
182 ),
183 sender,
184 room_id,
185 state_lock,
186 )
187 .await
188 .is_ok()
189}
190
191#[implement(super::Service)]
192pub async fn user_can_tombstone(
193 &self,
194 room_id: &RoomId,
195 user_id: &UserId,
196 state_lock: &RoomMutexGuard,
197) -> bool {
198 if !self
199 .services
200 .state_cache
201 .is_joined(user_id, room_id)
202 .await
203 {
204 return false;
205 }
206
207 self.services
208 .timeline
209 .create_hash_and_sign_event(
210 PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
211 replacement_room: room_id.into(), body: "Not a valid m.room.tombstone.".into(),
213 }),
214 user_id,
215 room_id,
216 state_lock,
217 )
218 .await
219 .is_ok()
220}