1use futures::{FutureExt, future::join3};
2use ruma::{
3 Int, OwnedUserId, RoomId, UserId,
4 events::{
5 StateEventType,
6 room::{
7 member::{MembershipState, RoomMemberEventContent},
8 message::RoomMessageEventContent,
9 power_levels::{RoomPowerLevelsEventContent, UserPowerLevel},
10 },
11 },
12};
13use tuwunel_core::{
14 Err, Result, debug_info, debug_warn, error, implement, matrix::pdu::PduBuilder,
15 utils::stream::ReadyExt,
16};
17
18use crate::rooms::state::RoomMutexGuard;
19
20#[implement(super::Service)]
24pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
25 let Ok(room_id) = self.get_admin_room().await else {
26 debug_warn!(
27 "make_user_admin was called without an admin room being available or created"
28 );
29 return Ok(());
30 };
31
32 let state_lock = self.services.state.mutex.lock(&room_id).await;
33
34 let is_joined = self
35 .services
36 .state_cache
37 .is_joined(user_id, &room_id)
38 .await;
39
40 let is_invited = self
41 .services
42 .state_cache
43 .is_invited(user_id, &room_id)
44 .await;
45
46 let already_member = is_joined || is_invited;
47
48 if !already_member {
49 self.invite_new_admin(user_id, &room_id, &state_lock)
50 .await?;
51 }
52
53 let server_user: &UserId = self.services.globals.server_user.as_ref();
54
55 let mut room_power_levels = self
56 .services
57 .state_accessor
58 .room_state_get_content::<RoomPowerLevelsEventContent>(
59 &room_id,
60 &StateEventType::RoomPowerLevels,
61 "",
62 )
63 .await
64 .unwrap_or_default();
65
66 let server_level = 69420.into();
67 let admin_level = 100.into();
68 let already_granted = room_power_levels.users.get(server_user) == Some(&server_level)
69 && room_power_levels.users.get(user_id) == Some(&admin_level);
70
71 if !already_granted {
72 room_power_levels
73 .users
74 .insert(server_user.into(), server_level);
75 room_power_levels
76 .users
77 .insert(user_id.into(), admin_level);
78
79 self.services
80 .timeline
81 .build_and_append_pdu(
82 PduBuilder::state(String::new(), &room_power_levels),
83 server_user,
84 &room_id,
85 &state_lock,
86 )
87 .await?;
88 }
89
90 let room_tag = self
92 .services
93 .server
94 .config
95 .admin_room_tag
96 .as_str();
97
98 if !already_granted
99 && !room_tag.is_empty()
100 && let Err(e) = self
101 .services
102 .account_data
103 .set_room_tag(user_id, &room_id, room_tag.into(), None)
104 .await
105 {
106 error!(?room_id, ?user_id, ?room_tag, "Failed to set tag for admin grant: {e}");
107 }
108
109 if !already_member && self.services.server.config.admin_room_notices {
110 let welcome_message = String::from(
111 "## 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`",
112 );
113
114 self.services
116 .timeline
117 .build_and_append_pdu(
118 PduBuilder::timeline(&RoomMessageEventContent::text_markdown(welcome_message)),
119 server_user,
120 &room_id,
121 &state_lock,
122 )
123 .await?;
124 }
125
126 Ok(())
127}
128
129#[implement(super::Service)]
137pub async fn make_room_admin(&self, room_id: &RoomId, target: &UserId) -> Result {
138 let state_lock = self.services.state.mutex.lock(room_id).await;
139
140 let power_levels = self
141 .services
142 .state_accessor
143 .get_power_levels(room_id)
144 .await?;
145
146 let sender = self
147 .services
148 .state_cache
149 .local_users_in_room(room_id)
150 .ready_fold(None, |best: Option<(OwnedUserId, UserPowerLevel)>, user| {
151 let level = power_levels.for_user(user);
152 match best {
153 | Some((_, best_level)) if best_level >= level => best,
154 | _ => Some((user.to_owned(), level)),
155 }
156 })
157 .await;
158
159 let Some((sender, sender_level)) = sender else {
160 return Err!(Request(InvalidParam("Server not in room")));
161 };
162
163 if !power_levels.user_can_send_state(&sender, StateEventType::RoomPowerLevels) {
164 return Err!(Request(InvalidParam(
165 "No local admin in room with power to update power levels"
166 )));
167 }
168
169 let grant_level: Int = match sender_level {
170 | UserPowerLevel::Infinite => 100.into(),
171 | UserPowerLevel::Int(level) => level,
172 };
173
174 let is_joined = self
175 .services
176 .state_cache
177 .is_joined(target, room_id);
178
179 let is_invited = self
180 .services
181 .state_cache
182 .is_invited(target, room_id);
183
184 let is_public = self.services.metadata.is_public(room_id);
185 let (is_joined, is_invited, is_public) = join3(is_joined, is_invited, is_public).await;
186
187 if !is_joined && !is_invited && !is_public {
188 self.services
189 .membership
190 .invite(&sender, target, room_id, None, false)
191 .await?;
192 }
193
194 let mut content = self
195 .services
196 .state_accessor
197 .room_state_get_content::<RoomPowerLevelsEventContent>(
198 room_id,
199 &StateEventType::RoomPowerLevels,
200 "",
201 )
202 .await
203 .unwrap_or_default();
204
205 content.users.insert(target.into(), grant_level);
206
207 self.services
208 .timeline
209 .build_and_append_pdu(
210 PduBuilder::state(String::new(), &content),
211 &sender,
212 room_id,
213 &state_lock,
214 )
215 .await
216 .map(|_| ())
217}
218
219#[implement(super::Service)]
220async fn invite_new_admin(
221 &self,
222 user_id: &UserId,
223 room_id: &RoomId,
224 state_lock: &RoomMutexGuard,
225) -> Result {
226 let server_user = self.services.globals.server_user.as_ref();
227
228 if self.services.globals.user_is_local(user_id) {
231 debug_info!("Inviting local user {user_id} to admin room {room_id}");
232 self.services
233 .timeline
234 .build_and_append_pdu(
235 PduBuilder::state(
236 String::from(user_id),
237 &RoomMemberEventContent::new(MembershipState::Invite),
238 ),
239 server_user,
240 room_id,
241 state_lock,
242 )
243 .await?;
244
245 debug_info!("Force joining local user {user_id} to admin room {room_id}");
246 self.services
247 .timeline
248 .build_and_append_pdu(
249 PduBuilder::state(
250 String::from(user_id),
251 &RoomMemberEventContent::new(MembershipState::Join),
252 ),
253 user_id,
254 room_id,
255 state_lock,
256 )
257 .await?;
258 } else {
259 debug_info!("Inviting remote user {user_id} to admin room {room_id}");
260 self.services
261 .timeline
262 .build_and_append_pdu(
263 PduBuilder::state(
264 user_id.to_string(),
265 &RoomMemberEventContent::new(MembershipState::Invite),
266 ),
267 server_user,
268 room_id,
269 state_lock,
270 )
271 .await?;
272 }
273
274 Ok(())
275}
276
277#[implement(super::Service)]
279pub async fn revoke_admin(&self, user_id: &UserId) -> Result {
280 use MembershipState::{Invite, Join, Knock, Leave};
281
282 let Ok(room_id) = self.get_admin_room().await else {
283 return Err!(error!("No admin room available or created."));
284 };
285
286 let state_lock = self.services.state.mutex.lock(&room_id).await;
287
288 let event = match self
289 .services
290 .state_accessor
291 .get_member(&room_id, user_id)
292 .await
293 {
294 | Err(e) if e.is_not_found() => return Err!("{user_id} was never an admin."),
295
296 | Err(e) => return Err!(error!(?e, "Failure occurred while attempting revoke.")),
297
298 | Ok(event) if !matches!(event.membership, Invite | Knock | Join) => {
299 return Err!("Cannot revoke {user_id} in membership state {:?}.", event.membership);
300 },
301
302 | Ok(event) => {
303 assert!(
304 matches!(event.membership, Invite | Knock | Join),
305 "Incorrect membership state to remove user."
306 );
307
308 event
309 },
310 };
311
312 self.services
313 .timeline
314 .build_and_append_pdu(
315 PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
316 membership: Leave,
317 reason: Some("Admin Revoked".into()),
318 is_direct: false,
319 join_authorized_via_users_server: None,
320 third_party_invite: None,
321 ..event
322 }),
323 self.services.globals.server_user.as_ref(),
324 &room_id,
325 &state_lock,
326 )
327 .boxed()
328 .await
329 .map(|_| ())
330}