1use std::{collections::BTreeMap, sync::Arc};
2
3use futures::{Stream, StreamExt, future::join};
4use ruma::{
5 MxcUri, OwnedMxcUri, OwnedRoomId, RoomId, UserId,
6 api::federation::query::get_profile_information,
7 events::room::member::{MembershipState, RoomMemberEventContent},
8 profile::{ProfileFieldName, ProfileFieldValue},
9};
10use serde::Deserialize;
11use serde_json::Value;
12use tuwunel_core::{
13 Err, Result, err, extract_variant, implement,
14 matrix::PduBuilder,
15 utils::{
16 TryReadyExt,
17 future::TryExtExt,
18 stream::{IterStream, TryIgnore, automatic_width},
19 },
20 warn,
21};
22use tuwunel_database::{Deserialized, Ignore, Interfix, Json, Map};
23
24pub struct Service {
25 services: Arc<crate::services::OnceServices>,
26 useridprofilekey_value: Arc<Map>,
27}
28
29impl crate::Service for Service {
30 fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
31 Ok(Arc::new(Self {
32 services: args.services.clone(),
33 useridprofilekey_value: args.db["useridprofilekey_value"].clone(),
34 }))
35 }
36
37 fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
38}
39
40#[derive(Copy, Clone, Debug, Eq, PartialEq)]
44pub enum Propagation {
45 All,
47
48 Unchanged,
52
53 None,
55}
56
57#[implement(Service)]
58pub async fn update_all_rooms(
59 &self,
60 user_id: &UserId,
61 profile_values: &[(ProfileFieldName, Option<Value>)],
62 propagation: Propagation,
63) {
64 if matches!(propagation, Propagation::None) {
65 return;
66 }
67
68 if !profile_values.iter().any(|(name, _)| {
69 matches!(name, ProfileFieldName::DisplayName | ProfileFieldName::AvatarUrl)
70 }) {
71 return;
72 }
73
74 if self.services.users.is_suspended(user_id).await {
77 return;
78 }
79
80 let (current_displayname, current_avatar_url) =
81 if matches!(propagation, Propagation::Unchanged) {
82 join(self.displayname(user_id).ok(), self.avatar_url(user_id).ok()).await
83 } else {
84 (None, None)
85 };
86
87 let rooms: Vec<OwnedRoomId> = self
88 .services
89 .state_cache
90 .rooms_joined(user_id)
91 .map(Into::into)
92 .collect()
93 .await;
94
95 rooms
96 .iter()
97 .stream()
98 .for_each_concurrent(automatic_width(), async |room_id| {
99 if let Err(e) = self
100 .update_room(
101 user_id,
102 room_id,
103 profile_values,
104 propagation,
105 current_displayname.as_deref(),
106 current_avatar_url.as_deref(),
107 )
108 .await
109 {
110 warn!(
111 %user_id,
112 %room_id,
113 %e,
114 "Failed to update room profile",
115 );
116 }
117 })
118 .await;
119}
120
121#[implement(Service)]
122async fn update_room(
123 &self,
124 user_id: &UserId,
125 room_id: &RoomId,
126 profile_values: &[(ProfileFieldName, Option<Value>)],
127 propagation: Propagation,
128 current_displayname: Option<&str>,
129 current_avatar_url: Option<&MxcUri>,
130) -> Result {
131 let unchanged = match propagation {
132 | Propagation::All => false,
133 | Propagation::Unchanged => true,
134 | Propagation::None => return Ok(()),
135 };
136
137 let mut content = self
138 .services
139 .state_accessor
140 .get_member(room_id, user_id)
141 .await?;
142
143 if !matches!(content.membership, MembershipState::Join) {
144 return Ok(());
145 }
146
147 let mut changed = false;
148
149 for (name, value) in profile_values {
150 match name {
151 | ProfileFieldName::DisplayName => {
152 if unchanged && content.displayname.as_deref() != current_displayname {
153 continue;
154 }
155
156 let displayname = value.clone().map(|value| {
157 extract_variant!(value, Value::String).expect("invalid profile value type")
158 });
159
160 content.displayname = displayname;
161
162 changed = true;
163 },
164 | ProfileFieldName::AvatarUrl => {
165 if unchanged && content.avatar_url.as_deref() != current_avatar_url {
166 continue;
167 }
168
169 let avatar_url = value.clone().map(|value| {
170 serde_json::from_value(value).expect("invalid profile value type")
171 });
172
173 content.avatar_url = avatar_url;
174
175 changed = true;
176 },
177 | _ => {},
178 }
179 }
180
181 if !changed {
182 return Ok(());
183 }
184
185 content.reason = None;
186
187 let state_lock = self.services.state.mutex.lock(room_id).await;
188
189 self.services
190 .timeline
191 .build_and_append_pdu(
192 PduBuilder::state(user_id.as_str(), &content),
193 user_id,
194 room_id,
195 &state_lock,
196 )
197 .await?;
198
199 Ok(())
200}
201
202#[implement(Service)]
205pub async fn set_displayname(
206 &self,
207 user_id: &UserId,
208 displayname: Option<&str>,
209 propagation: Option<Propagation>,
210) -> Result {
211 self.set_profile_keys(
212 user_id,
213 &[(
214 ProfileFieldName::DisplayName,
215 displayname.map(|displayname| {
216 serde_json::to_value(displayname).expect("displayname serialization cannot fail")
217 }),
218 )],
219 propagation,
220 )
221 .await
222}
223
224#[implement(Service)]
226pub async fn displayname(&self, user_id: &UserId) -> Result<String> {
227 self.profile_key(user_id, &ProfileFieldName::DisplayName)
228 .await
229}
230
231#[implement(Service)]
233pub async fn set_avatar_url(
234 &self,
235 user_id: &UserId,
236 avatar_url: Option<&MxcUri>,
237 propagation: Option<Propagation>,
238) -> Result {
239 self.set_profile_keys(
240 user_id,
241 &[(
242 ProfileFieldName::AvatarUrl,
243 avatar_url.map(|avatar_url| {
244 serde_json::to_value(avatar_url).expect("avatar url serialization cannot fail")
245 }),
246 )],
247 propagation,
248 )
249 .await
250}
251
252#[implement(Service)]
254pub async fn avatar_url(&self, user_id: &UserId) -> Result<OwnedMxcUri> {
255 self.profile_key(user_id, &ProfileFieldName::AvatarUrl)
256 .await
257}
258
259#[implement(Service)]
261pub async fn set_timezone(
262 &self,
263 user_id: &UserId,
264 timezone: Option<&str>,
265 propagation: Option<Propagation>,
266) -> Result {
267 self.set_profile_keys(
268 user_id,
269 &[(
270 ProfileFieldName::TimeZone,
271 timezone.map(|timezone| {
272 serde_json::to_value(timezone).expect("timezone serialization cannot fail")
273 }),
274 )],
275 propagation,
276 )
277 .await
278}
279
280#[implement(Service)]
282pub async fn timezone(&self, user_id: &UserId) -> Result<String> {
283 self.profile_key(user_id, &ProfileFieldName::TimeZone)
284 .await
285}
286
287#[implement(Service)]
289pub fn all_profile_keys(&self, user_id: &UserId) -> impl Stream<Item = ProfileFieldValue> + Send {
290 let prefix = (user_id, Interfix);
291 self.useridprofilekey_value
292 .stream_prefix(&prefix)
293 .ignore_err()
294 .map(move |((_, key), Json(val)): ((Ignore, _), _)| {
295 ProfileFieldValue::new(key, val).map_err(|_| {
296 err!(Database(
297 error!(%user_id, %key, "Invalid json in database profile value while iterating")
298 ))
299 })
300 })
301 .ignore_err()
302}
303
304#[implement(Service)]
305pub async fn clear_profile_keys(&self, user_id: &UserId) {
306 let prefix = (user_id, Interfix);
307
308 self.useridprofilekey_value
309 .keys_prefix_raw(&prefix)
310 .ready_try_for_each(|key| {
311 self.useridprofilekey_value.remove(key);
312 Ok(())
313 })
314 .await
315 .ok();
316}
317
318#[implement(Service)]
320pub async fn set_profile_keys(
321 &self,
322 user_id: &UserId,
323 profile_values: &[(ProfileFieldName, Option<Value>)],
324 propagation: Option<Propagation>,
325) -> Result {
326 if self.services.globals.user_is_local(user_id) {
327 for (name, value) in profile_values {
328 check_profile_key(name.as_str())?;
329
330 if let Some(value) = value {
331 self.enforce_profile_size(user_id, name.as_str(), value)
332 .await?;
333 }
334 }
335 }
336
337 let propagation = propagation.unwrap_or(
338 if self
339 .services
340 .config
341 .preserve_room_profile_overrides
342 {
343 Propagation::Unchanged
344 } else {
345 Propagation::All
346 },
347 );
348
349 if !matches!(propagation, Propagation::None) && self.services.globals.user_is_local(user_id) {
350 self.update_all_rooms(user_id, profile_values, propagation)
351 .await;
352 }
353
354 for (name, value) in profile_values {
355 let key = (user_id, name.as_str());
356
357 if let Some(value) = value {
358 self.useridprofilekey_value.put(key, Json(value));
359 } else {
360 self.useridprofilekey_value.del(key);
361 }
362 }
363
364 Ok(())
365}
366
367#[implement(Service)]
369pub async fn profile_key<T>(&self, user_id: &UserId, profile_key: &ProfileFieldName) -> Result<T>
370where
371 T: for<'de> Deserialize<'de> + Send,
372{
373 let key = (user_id, profile_key);
374 let Json(value) = self
375 .useridprofilekey_value
376 .qry(&key)
377 .await
378 .map_err(|_| err!(Request(NotFound("The requested profile key does not exist."))))?
379 .deserialized()
380 .map_err(|_| err!(Database("Cannot deserialize database profile value")))?;
381
382 Ok(value)
383}
384
385#[implement(Service)]
386pub async fn fill_profile_data(&self, user_id: &UserId, content: &mut RoomMemberEventContent) {
387 let displayname = self.displayname(user_id).ok();
388 let avatar_url = self.avatar_url(user_id).ok();
389
390 let (displayname, avatar_url) = join(displayname, avatar_url).await;
391
392 content.displayname = displayname;
393 content.avatar_url = avatar_url;
394}
395
396#[implement(Service)]
397pub async fn fetch_remote_profile(&self, user_id: &UserId) -> Result {
398 assert!(
399 !self.services.globals.user_is_local(user_id),
400 "fetch remote profile called with a local user"
401 );
402
403 if let Ok(response) = self
404 .services
405 .federation
406 .execute(user_id.server_name(), get_profile_information::v1::Request {
407 user_id: user_id.to_owned(),
408 field: None,
409 })
410 .await
411 {
412 if !self.services.users.exists(user_id).await {
413 self.services
414 .users
415 .create(user_id, None, None)
416 .await?;
417 }
418
419 for (key, value) in response.iter() {
420 self.set_profile_keys(
421 user_id,
422 &[(key.as_str().into(), Some(value.clone()))],
423 Some(Propagation::None),
424 )
425 .await?;
426 }
427 }
428
429 Ok(())
430}
431
432pub(super) const MAX_PROFILE_SIZE: usize = 65_536;
435
436#[implement(Service)]
440async fn enforce_profile_size(&self, user_id: &UserId, key: &str, value: &Value) -> Result {
441 let mut profile: BTreeMap<_, _> = self
442 .all_profile_keys(user_id)
443 .map(|profile_value| {
444 (
445 profile_value.field_name().as_str().to_owned(),
446 profile_value.value().into_owned(),
447 )
448 })
449 .collect()
450 .await;
451 profile.insert(key.to_owned(), value.clone());
452
453 let profile_size = serde_json::to_vec(&profile).map_or(0, |buf| buf.len());
454
455 if profile_size > MAX_PROFILE_SIZE {
456 return Err!(Request(ProfileTooLarge(
457 "Profile would exceed the maximum size of 64 KiB."
458 )));
459 }
460
461 Ok(())
462}
463
464const MAX_KEY_LENGTH: usize = 255;
466
467fn check_profile_key(name: &str) -> Result {
471 if name.len() > MAX_KEY_LENGTH {
472 return Err!(Request(KeyTooLarge("Profile key names cannot be longer than 255 bytes.")));
473 }
474
475 let ok = name
476 .bytes()
477 .next()
478 .is_some_and(|b| b.is_ascii_lowercase())
479 && name.bytes().all(|b| {
480 b.is_ascii_lowercase() || b.is_ascii_digit() || matches!(b, b'_' | b'.' | b'-')
481 });
482
483 if !ok {
484 return Err!(Request(BadJson(
485 "Profile key names must follow the Common Namespaced Identifier Grammar."
486 )));
487 }
488
489 Ok(())
490}