1use futures::FutureExt;
2use ruma::{
3 RoomId, 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
17use crate::rooms::state::RoomMutexGuard;
18
19#[implement(super::Service)]
23pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
24 let Ok(room_id) = self.get_admin_room().await else {
25 debug_warn!(
26 "make_user_admin was called without an admin room being available or created"
27 );
28 return Ok(());
29 };
30
31 let state_lock = self.services.state.mutex.lock(&room_id).await;
32
33 let is_joined = self
34 .services
35 .state_cache
36 .is_joined(user_id, &room_id)
37 .await;
38
39 let is_invited = self
40 .services
41 .state_cache
42 .is_invited(user_id, &room_id)
43 .await;
44
45 let already_member = is_joined || is_invited;
46
47 if !already_member {
48 self.invite_new_admin(user_id, &room_id, &state_lock)
49 .await?;
50 }
51
52 let server_user: &UserId = self.services.globals.server_user.as_ref();
53
54 let mut room_power_levels = self
55 .services
56 .state_accessor
57 .room_state_get_content::<RoomPowerLevelsEventContent>(
58 &room_id,
59 &StateEventType::RoomPowerLevels,
60 "",
61 )
62 .await
63 .unwrap_or_default();
64
65 let server_level = 69420.into();
66 let admin_level = 100.into();
67 let already_granted = room_power_levels.users.get(server_user) == Some(&server_level)
68 && room_power_levels.users.get(user_id) == Some(&admin_level);
69
70 if !already_granted {
71 room_power_levels
72 .users
73 .insert(server_user.into(), server_level);
74 room_power_levels
75 .users
76 .insert(user_id.into(), admin_level);
77
78 self.services
79 .timeline
80 .build_and_append_pdu(
81 PduBuilder::state(String::new(), &room_power_levels),
82 server_user,
83 &room_id,
84 &state_lock,
85 )
86 .await?;
87 }
88
89 let room_tag = self
91 .services
92 .server
93 .config
94 .admin_room_tag
95 .as_str();
96
97 if !already_granted
98 && !room_tag.is_empty()
99 && let Err(e) = self
100 .services
101 .account_data
102 .set_room_tag(user_id, &room_id, room_tag.into(), None)
103 .await
104 {
105 error!(?room_id, ?user_id, ?room_tag, "Failed to set tag for admin grant: {e}");
106 }
107
108 if !already_member && self.services.server.config.admin_room_notices {
109 let welcome_message = String::from(
110 "## 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`",
111 );
112
113 self.services
115 .timeline
116 .build_and_append_pdu(
117 PduBuilder::timeline(&RoomMessageEventContent::text_markdown(welcome_message)),
118 server_user,
119 &room_id,
120 &state_lock,
121 )
122 .await?;
123 }
124
125 Ok(())
126}
127
128#[implement(super::Service)]
129async fn invite_new_admin(
130 &self,
131 user_id: &UserId,
132 room_id: &RoomId,
133 state_lock: &RoomMutexGuard,
134) -> Result {
135 let server_user = self.services.globals.server_user.as_ref();
136
137 if self.services.globals.user_is_local(user_id) {
140 debug_info!("Inviting local user {user_id} to admin room {room_id}");
141 self.services
142 .timeline
143 .build_and_append_pdu(
144 PduBuilder::state(
145 String::from(user_id),
146 &RoomMemberEventContent::new(MembershipState::Invite),
147 ),
148 server_user,
149 room_id,
150 state_lock,
151 )
152 .await?;
153
154 debug_info!("Force joining local user {user_id} to admin room {room_id}");
155 self.services
156 .timeline
157 .build_and_append_pdu(
158 PduBuilder::state(
159 String::from(user_id),
160 &RoomMemberEventContent::new(MembershipState::Join),
161 ),
162 user_id,
163 room_id,
164 state_lock,
165 )
166 .await?;
167 } else {
168 debug_info!("Inviting remote user {user_id} to admin room {room_id}");
169 self.services
170 .timeline
171 .build_and_append_pdu(
172 PduBuilder::state(
173 user_id.to_string(),
174 &RoomMemberEventContent::new(MembershipState::Invite),
175 ),
176 server_user,
177 room_id,
178 state_lock,
179 )
180 .await?;
181 }
182
183 Ok(())
184}
185
186#[implement(super::Service)]
188pub async fn revoke_admin(&self, user_id: &UserId) -> Result {
189 use MembershipState::{Invite, Join, Knock, Leave};
190
191 let Ok(room_id) = self.get_admin_room().await else {
192 return Err!(error!("No admin room available or created."));
193 };
194
195 let state_lock = self.services.state.mutex.lock(&room_id).await;
196
197 let event = match self
198 .services
199 .state_accessor
200 .get_member(&room_id, user_id)
201 .await
202 {
203 | Err(e) if e.is_not_found() => return Err!("{user_id} was never an admin."),
204
205 | Err(e) => return Err!(error!(?e, "Failure occurred while attempting revoke.")),
206
207 | Ok(event) if !matches!(event.membership, Invite | Knock | Join) => {
208 return Err!("Cannot revoke {user_id} in membership state {:?}.", event.membership);
209 },
210
211 | Ok(event) => {
212 assert!(
213 matches!(event.membership, Invite | Knock | Join),
214 "Incorrect membership state to remove user."
215 );
216
217 event
218 },
219 };
220
221 self.services
222 .timeline
223 .build_and_append_pdu(
224 PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
225 membership: Leave,
226 reason: Some("Admin Revoked".into()),
227 is_direct: None,
228 join_authorized_via_users_server: None,
229 third_party_invite: None,
230 ..event
231 }),
232 self.services.globals.server_user.as_ref(),
233 &room_id,
234 &state_lock,
235 )
236 .boxed()
237 .await
238 .map(|_| ())
239}