tuwunel_api/client/admin/users/
create_or_modify.rs1use std::collections::BTreeSet;
2
3use axum::extract::State;
4use futures::StreamExt;
5use ruma::{MilliSecondsSinceUnixEpoch, MxcUri, UserId, thirdparty::Medium};
6use synapse_admin_api::users::create_or_modify::v2 as create_or_modify;
7use tuwunel_core::{
8 Err, Result,
9 utils::{IterStream, ReadyExt, stream::automatic_width},
10};
11use tuwunel_service::{threepid::canonicalize_email, users::PASSWORD_SENTINEL};
12
13use super::user_details;
14use crate::{
15 Ruma,
16 client::admin::{refuse_server_user, require_admin},
17};
18
19pub(crate) async fn admin_create_or_modify_route(
24 State(services): State<crate::State>,
25 body: Ruma<create_or_modify::Request>,
26) -> Result<create_or_modify::Response> {
27 let sender_user = body.sender_user();
28
29 require_admin(&services, sender_user).await?;
30
31 let user_id = &body.user_id;
32
33 if !services.globals.user_is_local(user_id) {
34 return Err!(Request(InvalidParam("Can only create or modify local users")));
35 }
36
37 if body.deactivated == Some(true) && body.locked == Some(true) {
38 return Err!(Request(InvalidParam("An account cannot be deactivated and locked")));
39 }
40
41 if body.admin == Some(false) && sender_user == body.user_id {
42 return Err!(Request(InvalidParam("You may not demote yourself.")));
43 }
44
45 let created = !services.users.exists(user_id).await;
46
47 if created || changes_access(&body) {
49 refuse_server_user(services, user_id)?;
50 }
51
52 if created {
53 services
54 .users
55 .create(user_id, body.password.as_deref(), None)
56 .await?;
57 } else if let Some(password) = body.password.as_deref() {
58 services
59 .users
60 .set_password(user_id, Some(password))
61 .await?;
62
63 if body.logout_devices {
64 services
65 .users
66 .all_device_ids(user_id)
67 .map(ToOwned::to_owned)
68 .for_each_concurrent(automatic_width(), async |device_id| {
69 services
70 .users
71 .remove_device(user_id, &device_id)
72 .await;
73 })
74 .await;
75 }
76 }
77
78 if let Some(displayname) = body.displayname.as_deref() {
79 let displayname = (!displayname.is_empty()).then_some(displayname);
80
81 services
82 .profile
83 .set_displayname(user_id, displayname, None)
84 .await?;
85 }
86
87 if let Some(avatar_url) = body.avatar_url.as_deref() {
88 let avatar_url = (!avatar_url.is_empty()).then(|| <&MxcUri>::from(avatar_url));
89
90 services
91 .profile
92 .set_avatar_url(user_id, avatar_url, None)
93 .await?;
94 }
95
96 match body.admin {
97 | Some(true) => services.admin.make_user_admin(user_id).await?,
98 | Some(false) => services.admin.revoke_admin(user_id).await?,
99 | None => {},
100 }
101
102 match body.deactivated {
103 | Some(true) =>
104 services
105 .deactivate
106 .full_deactivate(user_id, false)
107 .await?,
108 | Some(false)
109 if services
110 .users
111 .is_deactivated(user_id)
112 .await
113 .unwrap_or(false) =>
114 {
115 if body.password.is_none() {
118 services
119 .users
120 .set_password(user_id, Some(PASSWORD_SENTINEL))
121 .await?;
122 }
123 },
124 | _ => {},
125 }
126
127 match body.locked {
128 | Some(true) => services.users.set_locked(user_id, sender_user),
129 | Some(false) => services.users.clear_locked(user_id),
130 | None => {},
131 }
132
133 if let Some(threepids) = body.threepids.as_deref() {
134 replace_emails(services, user_id, threepids).await?;
135 }
136
137 let details = user_details(services, user_id).await;
138
139 Ok(create_or_modify::Response::new(details))
140}
141
142fn changes_access(body: &create_or_modify::Request) -> bool {
143 body.password.is_some()
144 || body.deactivated.is_some()
145 || body.locked.is_some()
146 || body.admin.is_some()
147 || body.threepids.is_some()
148}
149
150async fn replace_emails(
153 services: crate::State,
154 user_id: &UserId,
155 threepids: &[create_or_modify::ThirdPartyIdentifier],
156) -> Result {
157 let desired: BTreeSet<String> = threepids
158 .iter()
159 .filter(|tpid| tpid.medium == Medium::Email)
160 .map(|tpid| canonicalize_email(&tpid.address))
161 .collect::<Result<_>>()?;
162
163 let current: BTreeSet<String> = services
164 .threepid
165 .get_bindings(user_id)
166 .ready_filter_map(|tpid| (tpid.medium == Medium::Email).then_some(tpid.address))
167 .collect()
168 .await;
169
170 current
171 .difference(&desired)
172 .stream()
173 .for_each_concurrent(automatic_width(), |address| {
174 services.threepid.del_binding(user_id, address)
175 })
176 .await;
177
178 let now = MilliSecondsSinceUnixEpoch::now();
179
180 desired
181 .difference(¤t)
182 .stream()
183 .for_each_concurrent(automatic_width(), |address| {
184 services
185 .threepid
186 .put_binding(user_id, address, Medium::Email, now, now)
187 })
188 .await;
189
190 Ok(())
191}