Skip to main content

tuwunel_service/rooms/state_accessor/
user_can.rs

1use 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/// Checks if a given user can redact a given event
21///
22/// If federation is true, it allows redaction events from any user of the
23/// same server as the original event sender
24#[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			// Falling back on m.room.create to judge power level
65			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/// Whether a user is allowed to see an event, based on
82/// the room's history_visibility at that event's state.
83#[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		// Allow if any member on requesting server was AT LEAST invited, else deny
111		| HistoryVisibility::Invited =>
112			self.user_was_invited(shortstatehash, user_id)
113				.await,
114
115		// Allow if any member on requested server was joined, else deny
116		| 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/// Whether a user is allowed to see an event, based on
129/// the room's history_visibility at that event's state.
130#[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(), // placeholder,
212				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}