implement generic K-V support for MSC4133, GET/PUT/DELETE
no PATCH still yet Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
parent
80b72637e2
commit
d75aebc373
9 changed files with 340 additions and 20 deletions
|
@ -236,7 +236,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
|
|||
Ok(room_id) => {
|
||||
banned_room_check(&services, sender_user, Some(&room_id), room_id.server_name(), client).await?;
|
||||
|
||||
let mut servers = body.server_name.clone();
|
||||
let mut servers = body.via.clone();
|
||||
servers.extend(
|
||||
services
|
||||
.rooms
|
||||
|
@ -269,13 +269,13 @@ pub(crate) async fn join_room_by_id_or_alias_route(
|
|||
let response = services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(&room_alias, Some(&body.server_name.clone()))
|
||||
.resolve_alias(&room_alias, Some(&body.via.clone()))
|
||||
.await?;
|
||||
let (room_id, mut pre_servers) = response;
|
||||
|
||||
banned_room_check(&services, sender_user, Some(&room_id), Some(room_alias.server_name()), client).await?;
|
||||
|
||||
let mut servers = body.server_name;
|
||||
let mut servers = body.via;
|
||||
if let Some(pre_servers) = &mut pre_servers {
|
||||
servers.append(pre_servers);
|
||||
}
|
||||
|
|
|
@ -247,11 +247,18 @@ pub(crate) async fn get_profile_route(
|
|||
.set_timezone(&body.user_id, response.tz.clone())
|
||||
.await?;
|
||||
|
||||
for (profile_key, profile_key_value) in &response.custom_profile_fields {
|
||||
services
|
||||
.users
|
||||
.set_profile_key(&body.user_id, profile_key, Some(profile_key_value.clone()))?;
|
||||
}
|
||||
|
||||
return Ok(get_profile::v3::Response {
|
||||
displayname: response.displayname,
|
||||
avatar_url: response.avatar_url,
|
||||
blurhash: response.blurhash,
|
||||
tz: response.tz,
|
||||
custom_profile_fields: response.custom_profile_fields,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -267,6 +274,11 @@ pub(crate) async fn get_profile_route(
|
|||
blurhash: services.users.blurhash(&body.user_id)?,
|
||||
displayname: services.users.displayname(&body.user_id)?,
|
||||
tz: services.users.timezone(&body.user_id)?,
|
||||
custom_profile_fields: services
|
||||
.users
|
||||
.all_profile_keys(&body.user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduit::{warn, Err};
|
||||
|
@ -6,7 +8,10 @@ use ruma::{
|
|||
client::{
|
||||
error::ErrorKind,
|
||||
membership::mutual_rooms,
|
||||
profile::{delete_timezone_key, get_timezone_key, set_timezone_key},
|
||||
profile::{
|
||||
delete_profile_key, delete_timezone_key, get_profile_key, get_timezone_key, set_profile_key,
|
||||
set_timezone_key,
|
||||
},
|
||||
room::get_summary,
|
||||
},
|
||||
federation,
|
||||
|
@ -16,6 +21,7 @@ use ruma::{
|
|||
OwnedRoomId,
|
||||
};
|
||||
|
||||
use super::{update_avatar_url, update_displayname};
|
||||
use crate::{Error, Result, Ruma, RumaResponse};
|
||||
|
||||
/// # `GET /_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms`
|
||||
|
@ -226,6 +232,138 @@ pub(crate) async fn set_timezone_key_route(
|
|||
Ok(set_timezone_key::unstable::Response {})
|
||||
}
|
||||
|
||||
/// # `PUT /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}`
|
||||
///
|
||||
/// Updates the profile key-value field of a user, as per MSC4133.
|
||||
///
|
||||
/// This also handles the avatar_url and displayname being updated.
|
||||
pub(crate) async fn set_profile_key_route(
|
||||
State(services): State<crate::State>, body: Ruma<set_profile_key::unstable::Request>,
|
||||
) -> Result<set_profile_key::unstable::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
if *sender_user != body.user_id && body.appservice_info.is_none() {
|
||||
return Err!(Request(Forbidden("You cannot update the profile of another user")));
|
||||
}
|
||||
|
||||
if body.kv_pair.is_empty() {
|
||||
return Err!(Request(BadJson(
|
||||
"The key-value pair JSON body is empty. Use DELETE to delete a key"
|
||||
)));
|
||||
}
|
||||
|
||||
if body.kv_pair.len() > 1 {
|
||||
// TODO: support PATCH or "recursively" adding keys in some sort
|
||||
return Err!(Request(BadJson("This endpoint can only take one key-value pair at a time")));
|
||||
}
|
||||
|
||||
let Some(profile_key_value) = body.kv_pair.get(&body.key) else {
|
||||
return Err!(Request(BadJson(
|
||||
"The key does not match the URL field key, or JSON body is empty (use DELETE)"
|
||||
)));
|
||||
};
|
||||
|
||||
if body
|
||||
.kv_pair
|
||||
.keys()
|
||||
.any(|key| key.starts_with("u.") && !profile_key_value.is_string())
|
||||
{
|
||||
return Err!(Request(BadJson("u.* profile key fields must be strings")));
|
||||
}
|
||||
|
||||
if body.kv_pair.keys().any(|key| key.len() > 128) {
|
||||
return Err!(Request(BadJson("Key names cannot be longer than 128 bytes")));
|
||||
}
|
||||
|
||||
if body.key == "displayname" {
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&body.user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
update_displayname(&services, &body.user_id, Some(profile_key_value.to_string()), all_joined_rooms).await?;
|
||||
} else if body.key == "avatar_url" {
|
||||
let mxc = ruma::OwnedMxcUri::from(profile_key_value.to_string());
|
||||
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&body.user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
update_avatar_url(&services, &body.user_id, Some(mxc), None, all_joined_rooms).await?;
|
||||
} else {
|
||||
services
|
||||
.users
|
||||
.set_profile_key(&body.user_id, &body.key, Some(profile_key_value.clone()))?;
|
||||
}
|
||||
|
||||
if services.globals.allow_local_presence() {
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.ping_presence(&body.user_id, &PresenceState::Online)?;
|
||||
}
|
||||
|
||||
Ok(set_profile_key::unstable::Response {})
|
||||
}
|
||||
|
||||
/// # `DELETE /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}`
|
||||
///
|
||||
/// Deletes the profile key-value field of a user, as per MSC4133.
|
||||
///
|
||||
/// This also handles the avatar_url and displayname being updated.
|
||||
pub(crate) async fn delete_profile_key_route(
|
||||
State(services): State<crate::State>, body: Ruma<delete_profile_key::unstable::Request>,
|
||||
) -> Result<delete_profile_key::unstable::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
if *sender_user != body.user_id && body.appservice_info.is_none() {
|
||||
return Err!(Request(Forbidden("You cannot update the profile of another user")));
|
||||
}
|
||||
|
||||
if body.kv_pair.len() > 1 {
|
||||
// TODO: support PATCH or "recursively" adding keys in some sort
|
||||
return Err!(Request(BadJson("This endpoint can only take one key-value pair at a time")));
|
||||
}
|
||||
|
||||
if body.key == "displayname" {
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&body.user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
update_displayname(&services, &body.user_id, None, all_joined_rooms).await?;
|
||||
} else if body.key == "avatar_url" {
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&body.user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
update_avatar_url(&services, &body.user_id, None, None, all_joined_rooms).await?;
|
||||
} else {
|
||||
services
|
||||
.users
|
||||
.set_profile_key(&body.user_id, &body.key, None)?;
|
||||
}
|
||||
|
||||
if services.globals.allow_local_presence() {
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.ping_presence(&body.user_id, &PresenceState::Online)?;
|
||||
}
|
||||
|
||||
Ok(delete_profile_key::unstable::Response {})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
|
||||
///
|
||||
/// Returns the `timezone` of the user as per MSC4133 and MSC4175.
|
||||
|
@ -285,3 +423,80 @@ pub(crate) async fn get_timezone_key_route(
|
|||
tz: services.users.timezone(&body.user_id)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/{userId}/{field}}`
|
||||
///
|
||||
/// Gets the profile key-value field of a user, as per MSC4133.
|
||||
///
|
||||
/// - If user is on another server and we do not have a local copy already fetch
|
||||
/// `timezone` over federation
|
||||
pub(crate) async fn get_profile_key_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_profile_key::unstable::Request>,
|
||||
) -> Result<get_profile_key::unstable::Response> {
|
||||
let mut profile_key_value: BTreeMap<String, serde_json::Value> = BTreeMap::new();
|
||||
|
||||
if !services.globals.user_is_local(&body.user_id) {
|
||||
// Create and update our local copy of the user
|
||||
if let Ok(response) = services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
body.user_id.server_name(),
|
||||
federation::query::get_profile_information::v1::Request {
|
||||
user_id: body.user_id.clone(),
|
||||
field: None, // we want the full user's profile to update locally as well
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
if !services.users.exists(&body.user_id)? {
|
||||
services.users.create(&body.user_id, None)?;
|
||||
}
|
||||
|
||||
services
|
||||
.users
|
||||
.set_displayname(&body.user_id, response.displayname.clone())
|
||||
.await?;
|
||||
services
|
||||
.users
|
||||
.set_avatar_url(&body.user_id, response.avatar_url.clone())
|
||||
.await?;
|
||||
services
|
||||
.users
|
||||
.set_blurhash(&body.user_id, response.blurhash.clone())
|
||||
.await?;
|
||||
services
|
||||
.users
|
||||
.set_timezone(&body.user_id, response.tz.clone())
|
||||
.await?;
|
||||
|
||||
if let Some(value) = response.custom_profile_fields.get(&body.key) {
|
||||
profile_key_value.insert(body.key.clone(), value.clone());
|
||||
services
|
||||
.users
|
||||
.set_profile_key(&body.user_id, &body.key, Some(value.clone()))?;
|
||||
} else {
|
||||
return Err!(Request(NotFound("The requested profile key does not exist.")));
|
||||
}
|
||||
|
||||
return Ok(get_profile_key::unstable::Response {
|
||||
value: profile_key_value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if !services.users.exists(&body.user_id)? {
|
||||
// Return 404 if this user doesn't exist and we couldn't fetch it over
|
||||
// federation
|
||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
|
||||
}
|
||||
|
||||
if let Some(value) = services.users.profile_key(&body.user_id, &body.key)? {
|
||||
profile_key_value.insert(body.key.clone(), value);
|
||||
} else {
|
||||
return Err!(Request(NotFound("The requested profile key does not exist.")));
|
||||
}
|
||||
|
||||
Ok(get_profile_key::unstable::Response {
|
||||
value: profile_key_value,
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue