Skip to main content

tuwunel_service/admin/
grant.rs

1use futures::FutureExt;
2use ruma::{
3	UserId,
4	events::{
5		StateEventType,
6		room::{
7			member::{MembershipState, RoomMemberEventContent},
8			message::RoomMessageEventContent,
9			power_levels::RoomPowerLevelsEventContent,
10		},
11	},
12};
13use tuwunel_core::{
14	Err, Result, debug_info, debug_warn, error, implement, matrix::pdu::PduBuilder,
15};
16
17/// Invite the user to the tuwunel admin room.
18///
19/// This is equivalent to granting server admin privileges.
20#[implement(super::Service)]
21pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
22	let Ok(room_id) = self.get_admin_room().await else {
23		debug_warn!(
24			"make_user_admin was called without an admin room being available or created"
25		);
26		return Ok(());
27	};
28
29	let state_lock = self.services.state.mutex.lock(&room_id).await;
30
31	if self
32		.services
33		.state_cache
34		.is_joined(user_id, &room_id)
35		.await
36	{
37		return Err!(debug_warn!("User is already joined in the admin room"));
38	}
39
40	if self
41		.services
42		.state_cache
43		.is_invited(user_id, &room_id)
44		.await
45	{
46		return Err!(debug_warn!("User is already pending an invitation to the admin room"));
47	}
48
49	// Use the server user to grant the new admin's power level
50	let server_user = self.services.globals.server_user.as_ref();
51
52	// if this is our local user, just forcefully join them in the room. otherwise,
53	// invite the remote user.
54	if self.services.globals.user_is_local(user_id) {
55		debug_info!("Inviting local user {user_id} to admin room {room_id}");
56		self.services
57			.timeline
58			.build_and_append_pdu(
59				PduBuilder::state(
60					String::from(user_id),
61					&RoomMemberEventContent::new(MembershipState::Invite),
62				),
63				server_user,
64				&room_id,
65				&state_lock,
66			)
67			.await?;
68
69		debug_info!("Force joining local user {user_id} to admin room {room_id}");
70		self.services
71			.timeline
72			.build_and_append_pdu(
73				PduBuilder::state(
74					String::from(user_id),
75					&RoomMemberEventContent::new(MembershipState::Join),
76				),
77				user_id,
78				&room_id,
79				&state_lock,
80			)
81			.await?;
82	} else {
83		debug_info!("Inviting remote user {user_id} to admin room {room_id}");
84		self.services
85			.timeline
86			.build_and_append_pdu(
87				PduBuilder::state(
88					user_id.to_string(),
89					&RoomMemberEventContent::new(MembershipState::Invite),
90				),
91				server_user,
92				&room_id,
93				&state_lock,
94			)
95			.await?;
96	}
97
98	// Set power levels
99	let mut room_power_levels = self
100		.services
101		.state_accessor
102		.room_state_get_content::<RoomPowerLevelsEventContent>(
103			&room_id,
104			&StateEventType::RoomPowerLevels,
105			"",
106		)
107		.await
108		.unwrap_or_default();
109
110	room_power_levels
111		.users
112		.insert(server_user.into(), 69420.into());
113	room_power_levels
114		.users
115		.insert(user_id.into(), 100.into());
116
117	self.services
118		.timeline
119		.build_and_append_pdu(
120			PduBuilder::state(String::new(), &room_power_levels),
121			server_user,
122			&room_id,
123			&state_lock,
124		)
125		.await?;
126
127	// Set room tag
128	let room_tag = self
129		.services
130		.server
131		.config
132		.admin_room_tag
133		.as_str();
134
135	if !room_tag.is_empty()
136		&& let Err(e) = self
137			.services
138			.account_data
139			.set_room_tag(user_id, &room_id, room_tag.into(), None)
140			.await
141	{
142		error!(?room_id, ?user_id, ?room_tag, "Failed to set tag for admin grant: {e}");
143	}
144
145	if self.services.server.config.admin_room_notices {
146		let welcome_message = String::from(
147			"## Thank you for trying out tuwunel!\n\nTuwunel is a continuation of conduwuit which was technically a hard fork of Conduit.\n\nHelpful links:\n> GitHub Repo: https://github.com/matrix-construct/tuwunel\n> Documentation: https://matrix-construct.github.io/tuwunel\n> Report issues: https://github.com/matrix-construct/tuwunel/issues\n\nFor a list of available commands, send the following message in this room: `!admin --help`",
148		);
149
150		// Send welcome message
151		self.services
152			.timeline
153			.build_and_append_pdu(
154				PduBuilder::timeline(&RoomMessageEventContent::text_markdown(welcome_message)),
155				server_user,
156				&room_id,
157				&state_lock,
158			)
159			.await?;
160	}
161
162	Ok(())
163}
164
165/// Demote an admin, removing its rights.
166#[implement(super::Service)]
167pub async fn revoke_admin(&self, user_id: &UserId) -> Result {
168	use MembershipState::{Invite, Join, Knock, Leave};
169
170	let Ok(room_id) = self.get_admin_room().await else {
171		return Err!(error!("No admin room available or created."));
172	};
173
174	let state_lock = self.services.state.mutex.lock(&room_id).await;
175
176	let event = match self
177		.services
178		.state_accessor
179		.get_member(&room_id, user_id)
180		.await
181	{
182		| Err(e) if e.is_not_found() => return Err!("{user_id} was never an admin."),
183
184		| Err(e) => return Err!(error!(?e, "Failure occurred while attempting revoke.")),
185
186		| Ok(event) if !matches!(event.membership, Invite | Knock | Join) => {
187			return Err!("Cannot revoke {user_id} in membership state {:?}.", event.membership);
188		},
189
190		| Ok(event) => {
191			assert!(
192				matches!(event.membership, Invite | Knock | Join),
193				"Incorrect membership state to remove user."
194			);
195
196			event
197		},
198	};
199
200	self.services
201		.timeline
202		.build_and_append_pdu(
203			PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
204				membership: Leave,
205				reason: Some("Admin Revoked".into()),
206				is_direct: None,
207				join_authorized_via_users_server: None,
208				third_party_invite: None,
209				..event
210			}),
211			self.services.globals.server_user.as_ref(),
212			&room_id,
213			&state_lock,
214		)
215		.boxed()
216		.await
217		.map(|_| ())
218}