diff --git a/Cargo.lock b/Cargo.lock index d97b5e72..50828a44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3428,7 +3428,7 @@ dependencies = [ [[package]] name = "ruma" version = "0.12.6" -source = "git+https://github.com/matrix-construct/ruma?rev=3d3acddfcf96891f1203ca3c36d8f41932ede50f#3d3acddfcf96891f1203ca3c36d8f41932ede50f" +source = "git+https://github.com/matrix-construct/ruma?rev=8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6#8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6" dependencies = [ "assign", "js_int", @@ -3447,7 +3447,7 @@ dependencies = [ [[package]] name = "ruma-appservice-api" version = "0.12.2" -source = "git+https://github.com/matrix-construct/ruma?rev=3d3acddfcf96891f1203ca3c36d8f41932ede50f#3d3acddfcf96891f1203ca3c36d8f41932ede50f" +source = "git+https://github.com/matrix-construct/ruma?rev=8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6#8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6" dependencies = [ "js_int", "ruma-common", @@ -3459,7 +3459,7 @@ dependencies = [ [[package]] name = "ruma-client-api" version = "0.20.4" -source = "git+https://github.com/matrix-construct/ruma?rev=3d3acddfcf96891f1203ca3c36d8f41932ede50f#3d3acddfcf96891f1203ca3c36d8f41932ede50f" +source = "git+https://github.com/matrix-construct/ruma?rev=8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6#8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6" dependencies = [ "as_variant", "assign", @@ -3482,7 +3482,7 @@ dependencies = [ [[package]] name = "ruma-common" version = "0.15.4" -source = "git+https://github.com/matrix-construct/ruma?rev=3d3acddfcf96891f1203ca3c36d8f41932ede50f#3d3acddfcf96891f1203ca3c36d8f41932ede50f" +source = "git+https://github.com/matrix-construct/ruma?rev=8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6#8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6" dependencies = [ "as_variant", "base64", @@ -3515,7 +3515,7 @@ dependencies = [ [[package]] name = "ruma-events" version = "0.30.5" -source = "git+https://github.com/matrix-construct/ruma?rev=3d3acddfcf96891f1203ca3c36d8f41932ede50f#3d3acddfcf96891f1203ca3c36d8f41932ede50f" +source = "git+https://github.com/matrix-construct/ruma?rev=8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6#8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6" dependencies = [ "as_variant", "indexmap", @@ -3541,7 +3541,7 @@ dependencies = [ [[package]] name = "ruma-federation-api" version = "0.11.2" -source = "git+https://github.com/matrix-construct/ruma?rev=3d3acddfcf96891f1203ca3c36d8f41932ede50f#3d3acddfcf96891f1203ca3c36d8f41932ede50f" +source = "git+https://github.com/matrix-construct/ruma?rev=8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6#8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6" dependencies = [ "bytes", "headers", @@ -3563,7 +3563,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" version = "0.10.1" -source = "git+https://github.com/matrix-construct/ruma?rev=3d3acddfcf96891f1203ca3c36d8f41932ede50f#3d3acddfcf96891f1203ca3c36d8f41932ede50f" +source = "git+https://github.com/matrix-construct/ruma?rev=8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6#8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6" dependencies = [ "js_int", "thiserror 2.0.14", @@ -3572,7 +3572,7 @@ dependencies = [ [[package]] name = "ruma-macros" version = "0.15.2" -source = "git+https://github.com/matrix-construct/ruma?rev=3d3acddfcf96891f1203ca3c36d8f41932ede50f#3d3acddfcf96891f1203ca3c36d8f41932ede50f" +source = "git+https://github.com/matrix-construct/ruma?rev=8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6#8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6" dependencies = [ "cfg-if", "proc-macro-crate", @@ -3587,7 +3587,7 @@ dependencies = [ [[package]] name = "ruma-push-gateway-api" version = "0.11.0" -source = "git+https://github.com/matrix-construct/ruma?rev=3d3acddfcf96891f1203ca3c36d8f41932ede50f#3d3acddfcf96891f1203ca3c36d8f41932ede50f" +source = "git+https://github.com/matrix-construct/ruma?rev=8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6#8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6" dependencies = [ "js_int", "ruma-common", @@ -3599,16 +3599,16 @@ dependencies = [ [[package]] name = "ruma-signatures" version = "0.17.1" -source = "git+https://github.com/matrix-construct/ruma?rev=3d3acddfcf96891f1203ca3c36d8f41932ede50f#3d3acddfcf96891f1203ca3c36d8f41932ede50f" +source = "git+https://github.com/matrix-construct/ruma?rev=8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6#8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6" dependencies = [ "base64", "ed25519-dalek", + "memchr", "pkcs8", "rand 0.8.5", "ruma-common", "serde_json", "sha2", - "subslice", "thiserror 2.0.14", ] @@ -4301,15 +4301,6 @@ dependencies = [ "quote", ] -[[package]] -name = "subslice" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a8e4809a3bb02de01f1f7faf1ba01a83af9e8eabcd4d31dd6e413d14d56aae" -dependencies = [ - "memchr", -] - [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index 8e91e82c..f8671c8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -317,18 +317,19 @@ default-features = false [workspace.dependencies.ruma] git = "https://github.com/matrix-construct/ruma" -rev = "3d3acddfcf96891f1203ca3c36d8f41932ede50f" +rev = "8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6" features = [ "__compat", - "rand", "appservice-api-c", "client-api", + "client-api-s", + "compat-upload-signatures", "federation-api", + "identifiers-validation", "markdown", "push-gateway-api-c", + "rand", "ring-compat", - "compat-upload-signatures", - "identifiers-validation", "unstable-msc2448", "unstable-msc2666", "unstable-msc2867", @@ -343,6 +344,7 @@ features = [ "unstable-msc4095", "unstable-msc4121", "unstable-msc4125", + "unstable-msc4133", "unstable-msc4186", "unstable-msc4203", # sending to-device events to appservices "unstable-msc4311", diff --git a/src/api/client/profile.rs b/src/api/client/profile.rs index 066d2972..a7987825 100644 --- a/src/api/client/profile.rs +++ b/src/api/client/profile.rs @@ -279,13 +279,20 @@ pub(crate) async fn get_profile_route( ); } - 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, - }); + let canonical_fields = [ + ("avatar_url", response.avatar_url.map(Into::into)), + ("blurhash", response.blurhash), + ("displayname", response.displayname), + ("tz", response.tz), + ]; + + let response = canonical_fields + .into_iter() + .filter_map(|(key, val)| val.map(|val| (key, val))) + .map(|(key, val)| (key.to_owned(), val.into())) + .chain(response.custom_profile_fields.into_iter()); + + return Ok(response.collect::()); } } @@ -313,13 +320,20 @@ pub(crate) async fn get_profile_route( ) .await; - Ok(get_profile::v3::Response { - avatar_url, - blurhash, - displayname, - tz, - custom_profile_fields, - }) + let canonical_fields = [ + ("avatar_url", avatar_url.map(Into::into)), + ("blurhash", blurhash), + ("displayname", displayname), + ("tz", tz), + ]; + + let response = canonical_fields + .into_iter() + .filter_map(|(key, val)| val.map(|val| (key, val))) + .map(|(key, val)| (key.to_owned(), val.into())) + .chain(custom_profile_fields.into_iter()); + + Ok(response.collect::()) } pub async fn update_displayname( diff --git a/src/api/client/sync/v3.rs b/src/api/client/sync/v3.rs index 9ab81919..137fdf9d 100644 --- a/src/api/client/sync/v3.rs +++ b/src/api/client/sync/v3.rs @@ -14,7 +14,7 @@ use ruma::{ api::client::{ filter::FilterDefinition, sync::sync_events::{ - self, DeviceLists, StrippedState, UnreadNotificationsCount, + self, DeviceLists, UnreadNotificationsCount, v3::{ Ephemeral, Filter, GlobalAccountData, InviteState, InvitedRoom, JoinedRoom, KnockState, KnockedRoom, LeftRoom, Presence, RoomAccountData, RoomSummary, Rooms, @@ -295,12 +295,7 @@ async fn build_sync_events( } let invited_room = InvitedRoom { - invite_state: InviteState { - events: invite_state - .into_iter() - .map(Raw::cast::) - .collect(), - }, + invite_state: InviteState { events: invite_state }, }; invited_rooms.insert(room_id, invited_room); @@ -325,12 +320,7 @@ async fn build_sync_events( } let knocked_room = KnockedRoom { - knock_state: KnockState { - events: knock_state - .into_iter() - .map(Raw::cast::) - .collect(), - }, + knock_state: KnockState { events: knock_state }, }; knocked_rooms.insert(room_id, knocked_room); diff --git a/src/api/client/sync/v5.rs b/src/api/client/sync/v5.rs index e4b67cee..9422495a 100644 --- a/src/api/client/sync/v5.rs +++ b/src/api/client/sync/v5.rs @@ -14,8 +14,7 @@ use futures::{ use ruma::{ DeviceId, OwnedEventId, OwnedRoomId, RoomId, UInt, UserId, api::client::sync::sync_events::{ - self, DeviceLists, StrippedState, UnreadNotificationsCount, - v5::request::ExtensionRoomConfig, + self, DeviceLists, UnreadNotificationsCount, v5::request::ExtensionRoomConfig, }, directory::RoomTypeFilter, events::{ @@ -650,11 +649,7 @@ where name: room_name.or(hero_name), initial: Some(roomsince == &0), is_dm: None, - invite_state: invite_state.map(|s| { - s.into_iter() - .map(Raw::cast::) - .collect() - }), + invite_state, unread_notifications: UnreadNotificationsCount { highlight_count: Some( services diff --git a/src/api/client/unstable.rs b/src/api/client/unstable.rs index 5c17c763..30d3c818 100644 --- a/src/api/client/unstable.rs +++ b/src/api/client/unstable.rs @@ -1,5 +1,3 @@ -use std::collections::BTreeMap; - use axum::extract::State; use axum_client_ip::InsecureClientIp; use futures::StreamExt; @@ -10,8 +8,8 @@ use ruma::{ error::ErrorKind, membership::mutual_rooms, profile::{ - delete_profile_key, delete_timezone_key, get_profile_key, get_timezone_key, - set_profile_key, set_timezone_key, + ProfileFieldName, ProfileFieldValue, delete_profile_field, delete_timezone_key, + get_profile_field, get_timezone_key, set_profile_field, set_timezone_key, }, }, federation, @@ -123,48 +121,21 @@ pub(crate) async fn set_timezone_key_route( /// 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( +pub(crate) async fn set_profile_field_route( State(services): State, - body: Ruma, -) -> Result { + body: Ruma, +) -> Result { let sender_user = body.sender_user(); 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) { + if body.value.field_name().as_str().len() > 128 { return Err!(Request(BadJson("Key names cannot be longer than 128 bytes"))); } - if body.key == "displayname" { + if body.value.field_name() == ProfileFieldName::DisplayName { let all_joined_rooms: Vec = services .rooms .state_cache @@ -176,12 +147,12 @@ pub(crate) async fn set_profile_key_route( update_displayname( &services, &body.user_id, - Some(profile_key_value.to_string()), + Some(body.value.value().to_string()), &all_joined_rooms, ) .await; - } else if body.key == "avatar_url" { - let mxc = ruma::OwnedMxcUri::from(profile_key_value.to_string()); + } else if body.value.field_name() == ProfileFieldName::AvatarUrl { + let mxc = ruma::OwnedMxcUri::from(body.value.value().to_string()); let all_joined_rooms: Vec = services .rooms @@ -193,9 +164,11 @@ pub(crate) async fn set_profile_key_route( 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())); + services.users.set_profile_key( + &body.user_id, + body.value.field_name().as_str(), + Some(body.value.value().into_owned()), + ); } if services.config.allow_local_presence { @@ -206,7 +179,7 @@ pub(crate) async fn set_profile_key_route( .await?; } - Ok(set_profile_key::unstable::Response {}) + Ok(set_profile_field::v3::Response {}) } /// # `DELETE /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}` @@ -214,24 +187,17 @@ pub(crate) async fn set_profile_key_route( /// 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( +pub(crate) async fn delete_profile_field_route( State(services): State, - body: Ruma, -) -> Result { + body: Ruma, +) -> Result { let sender_user = body.sender_user(); 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" { + if body.field == ProfileFieldName::DisplayName { let all_joined_rooms: Vec = services .rooms .state_cache @@ -241,7 +207,7 @@ pub(crate) async fn delete_profile_key_route( .await; update_displayname(&services, &body.user_id, None, &all_joined_rooms).await; - } else if body.key == "avatar_url" { + } else if body.field == ProfileFieldName::AvatarUrl { let all_joined_rooms: Vec = services .rooms .state_cache @@ -254,7 +220,7 @@ pub(crate) async fn delete_profile_key_route( } else { services .users - .set_profile_key(&body.user_id, &body.key, None); + .set_profile_key(&body.user_id, body.field.as_str(), None); } if services.config.allow_local_presence { @@ -265,7 +231,7 @@ pub(crate) async fn delete_profile_key_route( .await?; } - Ok(delete_profile_key::unstable::Response {}) + Ok(delete_profile_field::v3::Response {}) } /// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/us.cloke.msc4175.tz` @@ -335,12 +301,10 @@ pub(crate) async fn get_timezone_key_route( /// /// - 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( +pub(crate) async fn get_profile_field_route( State(services): State, - body: Ruma, -) -> Result { - let mut profile_key_value: BTreeMap = BTreeMap::new(); - + body: Ruma, +) -> Result { if !services.globals.user_is_local(&body.user_id) { // Create and update our local copy of the user if let Ok(response) = services @@ -377,23 +341,29 @@ pub(crate) async fn get_profile_key_route( .users .set_timezone(&body.user_id, response.tz.clone()); - match response.custom_profile_fields.get(&body.key) { + let profile_key_value: Option = match response + .custom_profile_fields + .get(body.field.as_str()) + { | Some(value) => { - profile_key_value.insert(body.key.clone(), value.clone()); - services - .users - .set_profile_key(&body.user_id, &body.key, Some(value.clone())); + services.users.set_profile_key( + &body.user_id, + body.field.as_str(), + Some(value.clone()), + ); + + Some(ProfileFieldValue::new(body.field.as_str(), value.clone())?) }, | _ => { return Err!(Request(NotFound("The requested profile key does not exist."))); }, - } + }; - if profile_key_value.is_empty() { + if profile_key_value.is_none() { return Err!(Request(NotFound("The requested profile key does not exist."))); } - return Ok(get_profile_key::unstable::Response { value: profile_key_value }); + return Ok(get_profile_field::v3::Response { value: profile_key_value }); } } @@ -403,22 +373,20 @@ pub(crate) async fn get_profile_key_route( return Err!(Request(NotFound("Profile was not found."))); } - match services + let profile_key_value: Option = match services .users - .profile_key(&body.user_id, &body.key) + .profile_key(&body.user_id, body.field.as_str()) .await { - | Ok(value) => { - profile_key_value.insert(body.key.clone(), value); - }, + | Ok(value) => Some(ProfileFieldValue::new(body.field.as_str(), value)?), | _ => { return Err!(Request(NotFound("The requested profile key does not exist."))); }, - } + }; - if profile_key_value.is_empty() { + if profile_key_value.is_none() { return Err!(Request(NotFound("The requested profile key does not exist."))); } - Ok(get_profile_key::unstable::Response { value: profile_key_value }) + Ok(get_profile_field::v3::Response { value: profile_key_value }) } diff --git a/src/api/router.rs b/src/api/router.rs index 00caf4ac..786bc01d 100644 --- a/src/api/router.rs +++ b/src/api/router.rs @@ -23,9 +23,9 @@ pub fn build(router: Router, server: &Server) -> Router { let config = &server.config; let mut router = router .ruma_route(&client::get_timezone_key_route) - .ruma_route(&client::get_profile_key_route) - .ruma_route(&client::set_profile_key_route) - .ruma_route(&client::delete_profile_key_route) + .ruma_route(&client::get_profile_field_route) + .ruma_route(&client::set_profile_field_route) + .ruma_route(&client::delete_profile_field_route) .ruma_route(&client::set_timezone_key_route) .ruma_route(&client::delete_timezone_key_route) .ruma_route(&client::appservice_ping) diff --git a/src/api/router/auth.rs b/src/api/router/auth.rs index 42eb2a85..5ac8ff8e 100644 --- a/src/api/router/auth.rs +++ b/src/api/router/auth.rs @@ -22,7 +22,8 @@ use ruma::{ directory::get_public_rooms, error::ErrorKind, profile::{ - get_avatar_url, get_display_name, get_profile, get_profile_key, get_timezone_key, + get_avatar_url, get_display_name, get_profile, get_profile_field, + get_timezone_key, }, voip::get_turn_server_info, }, @@ -172,7 +173,7 @@ fn check_auth_still_required(services: &Services, metadata: &Metadata, token: &T match metadata { | &get_profile::v3::Request::METADATA - | &get_profile_key::unstable::Request::METADATA + | &get_profile_field::v3::Request::METADATA | &get_display_name::v3::Request::METADATA | &get_avatar_url::v3::Request::METADATA | &get_timezone_key::unstable::Request::METADATA