refactor: Ruma upstreaming, half-baked edition

Co-authored-by: Jade Ellis <jade@ellis.link>
This commit is contained in:
Ginger
2026-03-29 12:25:42 -04:00
parent b80b9a7950
commit 268ed38b43
142 changed files with 2737 additions and 2289 deletions
+1
View File
@@ -93,6 +93,7 @@ log.workspace = true
rand.workspace = true
reqwest.workspace = true
ruma.workspace = true
ruminuwuity.workspace = true
serde_html_form.workspace = true
serde_json.workspace = true
serde.workspace = true
+2 -2
View File
@@ -2,9 +2,10 @@
use conduwuit::{Err, Result, info, utils::ReadyExt, warn};
use futures::{FutureExt, StreamExt};
use ruma::{
OwnedRoomAliasId, continuwuity_admin_api::rooms,
OwnedRoomAliasId,
events::room::message::RoomMessageEventContent,
};
use ruminuwuity::admin::continuwuity::rooms;
use crate::{Ruma, client::leave_room};
@@ -36,7 +37,6 @@ pub(crate) async fn ban_room(
.rooms
.state_cache
.room_members(&body.room_id)
.map(ToOwned::to_owned)
.ready_filter(|user| services.globals.user_is_local(user))
.boxed();
let mut evicted = Vec::new();
+3 -2
View File
@@ -1,7 +1,8 @@
use axum::extract::State;
use conduwuit::{Err, Result};
use futures::StreamExt;
use ruma::{OwnedRoomId, continuwuity_admin_api::rooms};
use ruma::OwnedRoomId;
use ruminuwuity::admin::continuwuity::rooms;
use crate::Ruma;
@@ -22,7 +23,7 @@ pub(crate) async fn list_rooms(
.metadata
.iter_ids()
.filter_map(|room_id| async move {
if !services.rooms.metadata.is_banned(room_id).await {
if !services.rooms.metadata.is_banned(&room_id).await {
Some(room_id.to_owned())
} else {
None
+2 -2
View File
@@ -87,7 +87,7 @@ pub(crate) async fn get_register_available_route(
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
}
Ok(get_username_availability::v3::Response { available: true })
Ok(get_username_availability::v3::Response::new(true))
}
/// # `POST /_matrix/client/r0/account/password`
@@ -194,7 +194,7 @@ pub(crate) async fn change_password_route(
.await;
}
Ok(change_password::v3::Response {})
Ok(change_password::v3::Response::new())
}
/// # `POST /_matrix/client/v3/account/password/email/requestToken`
+3 -9
View File
@@ -218,14 +218,7 @@ pub(crate) async fn register_route(
.await?;
// Generate new device id if the user didn't specify one
let no_device = body.inhibit_login
|| body
.appservice_info
.as_ref()
.is_some_and(|aps| aps.registration.device_management);
let (token, device) = if !no_device {
// Don't create a device for inhibited logins
let (token, device) = if !body.inhibit_login {
let device_id = if is_guest { None } else { body.device_id.clone() }
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
@@ -243,11 +236,12 @@ pub(crate) async fn register_route(
Some(client.to_string()),
)
.await?;
debug_info!(%user_id, %device_id, "User account was created");
(Some(new_token), Some(device_id))
} else {
// Don't create a device for inhibited logins
(None, None)
};
debug_info!(%user_id, %device_id, "User account was created");
// If the user registered with an email, associate it with their account.
if let Some(identity) = identity
+3 -3
View File
@@ -40,7 +40,7 @@ pub(crate) async fn set_global_account_data_route(
)
.await?;
Ok(set_global_account_data::v3::Response {})
Ok(set_global_account_data::v3::Response::new())
}
/// # `PUT /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}`
@@ -65,7 +65,7 @@ pub(crate) async fn set_room_account_data_route(
)
.await?;
Ok(set_room_account_data::v3::Response {})
Ok(set_room_account_data::v3::Response::new())
}
/// # `GET /_matrix/client/r0/user/{userId}/account_data/{type}`
@@ -119,7 +119,7 @@ async fn set_account_data(
event_type_s: &str,
data: &RawJsonValue,
) -> Result {
if event_type_s == RoomAccountDataEventType::FullyRead.to_cow_str() {
if event_type_s == "m.fully_read" {
return Err!(Request(BadJson(
"This endpoint cannot be used for marking a room as fully read (setting \
m.fully_read)"
+1 -1
View File
@@ -1,7 +1,7 @@
use axum::extract::State;
use conduwuit::{Err, Result};
use futures::future::{join, join3};
use ruma::api::client::admin::{get_suspended, set_suspended};
use ruminuwuity::admin::{get_suspended, set_suspended};
use crate::Ruma;
+1 -1
View File
@@ -47,5 +47,5 @@ pub(crate) async fn appservice_ping(
.await?
.expect("We already validated if an appservice URL exists above");
Ok(request_ping::v1::Response { duration: timer.elapsed() })
Ok(request_ping::v1::Response::new(timer.elapsed()))
}
+9 -9
View File
@@ -28,7 +28,7 @@ pub(crate) async fn create_backup_version_route(
.key_backups
.create_backup(body.sender_user(), &body.algorithm)?;
Ok(create_backup_version::v3::Response { version })
Ok(create_backup_version::v3::Response::new(version))
}
/// # `PUT /_matrix/client/r0/room_keys/version/{version}`
@@ -44,7 +44,7 @@ pub(crate) async fn update_backup_version_route(
.update_backup(body.sender_user(), &body.version, &body.algorithm)
.await?;
Ok(update_backup_version::v3::Response {})
Ok(update_backup_version::v3::Response::new())
}
/// # `GET /_matrix/client/r0/room_keys/version`
@@ -105,7 +105,7 @@ pub(crate) async fn delete_backup_version_route(
.delete_backup(body.sender_user(), &body.version)
.await;
Ok(delete_backup_version::v3::Response {})
Ok(delete_backup_version::v3::Response::new())
}
/// # `PUT /_matrix/client/r0/room_keys/keys`
@@ -292,7 +292,7 @@ pub(crate) async fn get_backup_keys_route(
.get_all(body.sender_user(), &body.version)
.await;
Ok(get_backup_keys::v3::Response { rooms })
Ok(get_backup_keys::v3::Response::new(rooms))
}
/// # `GET /_matrix/client/r0/room_keys/keys/{roomId}`
@@ -307,7 +307,7 @@ pub(crate) async fn get_backup_keys_for_room_route(
.get_room(body.sender_user(), &body.version, &body.room_id)
.await;
Ok(get_backup_keys_for_room::v3::Response { sessions })
Ok(get_backup_keys_for_room::v3::Response::new(sessions))
}
/// # `GET /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
@@ -325,7 +325,7 @@ pub(crate) async fn get_backup_keys_for_session_route(
err!(Request(NotFound(debug_error!("Backup key not found for this user's session."))))
})?;
Ok(get_backup_keys_for_session::v3::Response { key_data })
Ok(get_backup_keys_for_session::v3::Response::new(key_data))
}
/// # `DELETE /_matrix/client/r0/room_keys/keys`
@@ -342,7 +342,7 @@ pub(crate) async fn delete_backup_keys_route(
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
Ok(delete_backup_keys::v3::Response { count, etag })
Ok(delete_backup_keys::v3::Response::new(etag, count))
}
/// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}`
@@ -359,7 +359,7 @@ pub(crate) async fn delete_backup_keys_for_room_route(
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
Ok(delete_backup_keys_for_room::v3::Response { count, etag })
Ok(delete_backup_keys_for_room::v3::Response::new(etag, count))
}
/// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
@@ -376,7 +376,7 @@ pub(crate) async fn delete_backup_keys_for_session_route(
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
Ok(delete_backup_keys_for_session::v3::Response { count, etag })
Ok(delete_backup_keys_for_session::v3::Response::new(etag, count))
}
async fn get_count_etag(
+1 -1
View File
@@ -56,5 +56,5 @@ pub(crate) async fn get_capabilities_route(
capabilities.set("uk.timedout.msc4323", json!({"suspend": true, "lock": false}))?;
}
Ok(get_capabilities::v3::Response { capabilities })
Ok(get_capabilities::v3::Response::new(capabilities))
}
+3 -6
View File
@@ -33,7 +33,7 @@ pub(crate) async fn put_dehydrated_device_route(
.set_dehydrated_device(sender_user, body.body)
.await?;
Ok(put_dehydrated_device::Response { device_id })
Ok(put_dehydrated_device::Response::new(device_id))
}
/// # `DELETE /_matrix/client/../dehydrated_device`
@@ -51,7 +51,7 @@ pub(crate) async fn delete_dehydrated_device_route(
services.users.remove_device(sender_user, &device_id).await;
Ok(delete_dehydrated_device::Response { device_id })
Ok(delete_dehydrated_device::Response::new(device_id))
}
/// # `GET /_matrix/client/../dehydrated_device`
@@ -67,10 +67,7 @@ pub(crate) async fn get_dehydrated_device_route(
let device = services.users.get_dehydrated_device(sender_user).await?;
Ok(get_dehydrated_device::Response {
device_id: device.device_id,
device_data: device.device_data,
})
Ok(get_dehydrated_device::Response::new(device.device_id, device.device_data))
}
/// # `GET /_matrix/client/../dehydrated_device/{device_id}/events`
+8 -13
View File
@@ -25,7 +25,7 @@ pub(crate) async fn get_devices_route(
.collect()
.await;
Ok(get_devices::v3::Response { devices })
Ok(get_devices::v3::Response::new(devices))
}
/// # `GET /_matrix/client/r0/devices/{deviceId}`
@@ -41,7 +41,7 @@ pub(crate) async fn get_device_route(
.await
.map_err(|_| err!(Request(NotFound("Device not found."))))?;
Ok(get_device::v3::Response { device })
Ok(get_device::v3::Response::new(device))
}
/// # `PUT /_matrix/client/r0/devices/{deviceId}`
@@ -73,19 +73,15 @@ pub(crate) async fn update_device_route(
.update_device_metadata(sender_user, &body.device_id, &device)
.await?;
Ok(update_device::v3::Response {})
Ok(update_device::v3::Response::new())
},
| Err(_) => {
let Some(appservice) = appservice else {
return Err!(Request(NotFound("Device not found.")));
};
if !appservice.registration.device_management {
return Err!(Request(NotFound("Device not found.")));
}
debug!(
"Creating new device for {sender_user} from appservice {} as MSC4190 is enabled \
and device ID does not exist",
"Creating new device for {sender_user} from appservice {} as device ID does not exist",
appservice.registration.id
);
@@ -102,7 +98,7 @@ pub(crate) async fn update_device_route(
)
.await?;
return Ok(update_device::v3::Response {});
return Ok(update_device::v3::Response::new());
},
}
}
@@ -124,17 +120,16 @@ pub(crate) async fn delete_device_route(
let sender_user = body.sender_user();
let appservice = body.appservice_info.as_ref();
if appservice.is_some_and(|appservice| appservice.registration.device_management) {
if appservice.is_some() {
debug!(
"Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \
enabled"
"Skipping UIAA for {sender_user} as this is from an appservice"
);
services
.users
.remove_device(sender_user, &body.device_id)
.await;
return Ok(delete_device::v3::Response {});
return Ok(delete_device::v3::Response::new());
}
// Prompt the user to confirm with their password using UIAA
+4 -8
View File
@@ -17,8 +17,7 @@
future::{join, join4, join5},
};
use ruma::{
OwnedRoomId, RoomId, ServerName, UInt, UserId,
api::{
OwnedRoomId, RoomId, ServerName, UInt, UserId, api::{
client::{
directory::{
get_public_rooms, get_public_rooms_filtered, get_room_visibility,
@@ -27,17 +26,14 @@
room,
},
federation,
},
directory::{Filter, PublicRoomJoinRule, PublicRoomsChunk, RoomNetwork, RoomTypeFilter},
events::{
}, directory::{Filter, PublicRoomsChunk, RoomNetwork, RoomTypeFilter}, events::{
StateEventType,
room::{
create::RoomCreateEventContent,
join_rules::{JoinRule, RoomJoinRulesEventContent},
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
},
uint,
}, room::JoinRuleKind, uint
};
use tokio::join;
@@ -425,7 +421,7 @@ async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> Public
.state_accessor
.room_state_get_content(&room_id, &StateEventType::RoomJoinRules, "")
.map_ok(|c: RoomJoinRulesEventContent| match c.join_rule {
| JoinRule::Public => PublicRoomJoinRule::Public,
| JoinRule::Public => JoinRuleKind::Public,
| JoinRule::Knock => "knock".into(),
| JoinRule::KnockRestricted(_) => "knock_restricted".into(),
| _ => "invite".into(),
+1 -1
View File
@@ -13,7 +13,7 @@
};
use reqwest::Url;
use ruma::{
Mxc, UserId,
UserId,
api::client::{
authenticated_media::{
get_content, get_content_as_filename, get_content_thumbnail, get_media_config,
+12 -13
View File
@@ -24,30 +24,29 @@ pub(crate) async fn ban_user_route(
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
let current_member_content = services
let mut content = services
.rooms
.state_accessor
.get_member(&body.room_id, &body.user_id)
.await
.unwrap_or_else(|_| RoomMemberEventContent::new(MembershipState::Ban));
content.membership = MembershipState::Ban;
content.reason = body.reason.clone();
content.displayname = None;
content.avatar_url = None;
content.is_direct = None;
content.join_authorized_via_users_server = None;
content.third_party_invite = None;
// TODO(upstream): MSC4293
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Ban,
reason: body.reason.clone(),
displayname: None, // display name may be offensive
avatar_url: None, // avatar may be offensive
is_direct: None,
join_authorized_via_users_server: None,
third_party_invite: None,
redact_events: body.redact_events,
..current_member_content
}),
PduBuilder::state(body.user_id.to_string(), &content),
sender_user,
Some(&body.room_id),
&state_lock,
+43 -38
View File
@@ -8,12 +8,10 @@
use futures::FutureExt;
use ruma::{
RoomId, UserId,
api::{client::membership::invite_user, federation::membership::create_invite},
events::{
invite_permission_config::FilterLevel,
room::member::{MembershipState, RoomMemberEventContent},
},
api::{client::membership::invite_user, federation::membership::{RawStrippedState, create_invite}},
events::room::member::{MembershipState, RoomMemberEventContent},
};
use ruminuwuity::invite_permission_config::FilterLevel;
use service::Services;
use super::banned_room_check;
@@ -59,7 +57,7 @@ pub(crate) async fn invite_user_route(
if !matches!(sender_filter_level, FilterLevel::Allow) {
// drop invites if the sender has the recipient filtered
return Ok(invite_user::v3::Response {});
return Ok(invite_user::v3::Response::new());
}
if let Ok(target_user_membership) = services
@@ -101,7 +99,7 @@ pub(crate) async fn invite_user_route(
.boxed()
.await?;
Ok(invite_user::v3::Response {})
Ok(invite_user::v3::Response::new())
},
| _ => {
Err!(Request(NotFound("User not found.")))
@@ -141,12 +139,11 @@ pub(crate) async fn invite_helper(
let (pdu, pdu_json, invite_room_state) = {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let content = RoomMemberEventContent {
avatar_url: services.users.avatar_url(recipient_user).await.ok(),
is_direct: Some(is_direct),
reason,
..RoomMemberEventContent::new(MembershipState::Invite)
};
let mut content = RoomMemberEventContent::new(MembershipState::Invite);
content.displayname = services.users.displayname(recipient_user).await.ok();
content.avatar_url = services.users.avatar_url(recipient_user).await.ok();
content.is_direct = Some(is_direct);
content.reason = reason;
let (pdu, pdu_json) = services
.rooms
@@ -159,7 +156,15 @@ pub(crate) async fn invite_helper(
)
.await?;
let invite_room_state = services.rooms.state.summary_stripped(&pdu, room_id).await;
#[allow(deprecated)]
let invite_room_state = services
.rooms
.state
.summary_stripped(&pdu, room_id)
.await
.into_iter()
.map(|event| RawStrippedState::Stripped(event))
.collect();
drop(state_lock);
@@ -168,24 +173,26 @@ pub(crate) async fn invite_helper(
let room_version_id = services.rooms.state.get_room_version(room_id).await?;
let mut request = create_invite::v2::Request::new(
room_id.to_owned(),
(*pdu.event_id).to_owned(),
room_version_id.clone(),
services
.sending
.convert_to_outgoing_federation_event(pdu_json.clone())
.await,
invite_room_state,
);
request.via = services
.rooms
.state_cache
.servers_route_via(room_id)
.await
.ok();
let response = services
.sending
.send_federation_request(recipient_user.server_name(), create_invite::v2::Request {
room_id: room_id.to_owned(),
event_id: (*pdu.event_id).to_owned(),
room_version: room_version_id.clone(),
event: services
.sending
.convert_to_outgoing_federation_event(pdu_json.clone())
.await,
invite_room_state,
via: services
.rooms
.state_cache
.servers_route_via(room_id)
.await
.ok(),
})
.send_federation_request(recipient_user.server_name(), request)
.await?;
// We do not add the event_id field to the pdu here because of signature and
@@ -229,14 +236,12 @@ pub(crate) async fn invite_helper(
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let content = RoomMemberEventContent {
displayname: services.users.displayname(recipient_user).await.ok(),
avatar_url: services.users.avatar_url(recipient_user).await.ok(),
blurhash: services.users.blurhash(recipient_user).await.ok(),
is_direct: Some(is_direct),
reason,
..RoomMemberEventContent::new(MembershipState::Invite)
};
let mut content = RoomMemberEventContent::new(MembershipState::Invite);
content.displayname = services.users.displayname(recipient_user).await.ok();
content.avatar_url = services.users.avatar_url(recipient_user).await.ok();
content.blurhash = services.users.blurhash(recipient_user).await.ok();
content.is_direct = Some(is_direct);
content.reason = reason;
services
.rooms
+27 -36
View File
@@ -89,7 +89,6 @@ pub(crate) async fn join_room_by_id_route(
.rooms
.state_cache
.servers_invite_via(&body.room_id)
.map(ToOwned::to_owned)
.collect()
.await;
@@ -168,7 +167,6 @@ pub(crate) async fn join_room_by_id_or_alias_route(
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
);
@@ -212,8 +210,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
let addl_via_servers = services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned);
.servers_invite_via(&room_id);
let addl_state_servers = services
.rooms
@@ -226,7 +223,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
.iter()
.map(|event| event.get_field("sender"))
.filter_map(FlatOk::flat_ok)
.map(|user: &UserId| user.server_name().to_owned())
.map(|user: OwnedUserId| user.server_name().to_owned())
.stream()
.chain(addl_via_servers)
.collect()
@@ -252,7 +249,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
.boxed()
.await?;
Ok(join_room_by_id_or_alias::v3::Response { room_id: join_room_response.room_id })
Ok(join_room_by_id_or_alias::v3::Response::new(join_room_response.room_id))
}
pub async fn join_room_by_id_helper(
@@ -283,7 +280,7 @@ pub async fn join_room_by_id_helper(
.await
{
debug_warn!("{sender_user} is already joined in {room_id}");
return Ok(join_room_by_id::v3::Response { room_id: room_id.into() });
return Ok(join_room_by_id::v3::Response::new(room_id.to_owned()));
}
if let Err(e) = services
@@ -423,16 +420,17 @@ async fn join_room_by_id_helper_remote(
.expect("Timestamp is valid js_int value"),
),
);
let mut join_content = RoomMemberEventContent::new(MembershipState::Join);
join_content.displayname = services.users.displayname(sender_user).await.ok();
join_content.avatar_url = services.users.avatar_url(sender_user).await.ok();
join_content.blurhash = services.users.blurhash(sender_user).await.ok();
join_content.reason = reason;
join_content.join_authorized_via_users_server = join_authorized_via_users_server.clone();
join_event_stub.insert(
"content".to_owned(),
to_canonical_value(RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason,
join_authorized_via_users_server: join_authorized_via_users_server.clone(),
..RoomMemberEventContent::new(MembershipState::Join)
})
to_canonical_value(join_content)
.expect("event is valid, we just created it"),
);
@@ -462,15 +460,10 @@ async fn join_room_by_id_helper_remote(
let mut join_event = join_event_stub;
info!("Asking {remote_server} for send_join in room {room_id}");
let send_join_request = federation::membership::create_join_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
omit_members: false,
pdu: services
let send_join_request = federation::membership::create_join_event::v2::Request::new(room_id.to_owned(), event_id.clone(), services
.sending
.convert_to_outgoing_federation_event(join_event.clone())
.await,
};
.await);
let send_join_response = match services
.sending
@@ -638,7 +631,7 @@ async fn join_room_by_id_helper_remote(
};
let auth_check = state_res::event_auth::auth_check(
&state_res::RoomVersion::new(&room_version_id)?,
&room_version_id.rules().unwrap(),
&parsed_join_pdu,
None, // TODO: third party invite
|k, s| state_fetch(k.clone(), s.into()),
@@ -759,14 +752,12 @@ async fn join_room_by_id_helper_local(
}
}
let content = RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason: reason.clone(),
join_authorized_via_users_server: auth_user,
..RoomMemberEventContent::new(MembershipState::Join)
};
let mut content = RoomMemberEventContent::new(MembershipState::Join);
content.displayname = services.users.displayname(sender_user).await.ok();
content.avatar_url = services.users.avatar_url(sender_user).await.ok();
content.blurhash = services.users.blurhash(sender_user).await.ok();
content.reason = reason.clone();
content.join_authorized_via_users_server = auth_user;
// Try normal join first
let Err(error) = services
@@ -822,15 +813,15 @@ async fn make_join_request(
"Asking {remote_server} for make_join (attempt {make_join_counter}/{})",
servers.len()
);
let mut request = federation::membership::prepare_join_event::v1::Request::new(room_id.to_owned(), sender_user.to_owned());
request.ver = services.server.supported_room_versions().collect();
let make_join_response = services
.sending
.send_federation_request(
remote_server,
federation::membership::prepare_join_event::v1::Request {
room_id: room_id.to_owned(),
user_id: sender_user.to_owned(),
ver: services.server.supported_room_versions().collect(),
},
request
)
.await;
+9 -10
View File
@@ -18,9 +18,9 @@ pub(crate) async fn kick_user_route(
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
let Ok(event) = services
let Ok(mut event) = services
.rooms
.state_accessor
.get_member(&body.room_id, &body.user_id)
@@ -41,18 +41,17 @@ pub(crate) async fn kick_user_route(
)));
}
event.membership = MembershipState::Leave;
event.reason = body.reason.clone();
event.is_direct = None;
event.join_authorized_via_users_server = None;
event.third_party_invite = None;
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Leave,
reason: body.reason.clone(),
is_direct: None,
join_authorized_via_users_server: None,
third_party_invite: None,
..event
}),
PduBuilder::state(body.user_id.to_string(), &event),
sender_user,
Some(&body.room_id),
&state_lock,
+51 -52
View File
@@ -15,20 +15,16 @@
};
use futures::{FutureExt, StreamExt};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName, RoomId,
RoomVersionId, UserId,
api::{
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, RoomVersionId, UserId, api::{
client::knock::knock_room,
federation::{self},
},
canonical_json::to_canonical_value,
events::{
}, canonical_json::to_canonical_value, events::{
StateEventType,
room::{
join_rules::{AllowRule, JoinRule},
member::{MembershipState, RoomMemberEventContent},
},
},
}
};
use service::{
Services,
@@ -73,7 +69,6 @@ pub(crate) async fn knock_room_route(
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
);
@@ -116,8 +111,7 @@ pub(crate) async fn knock_room_route(
let addl_via_servers = services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned);
.servers_invite_via(&room_id);
let addl_state_servers = services
.rooms
@@ -130,7 +124,7 @@ pub(crate) async fn knock_room_route(
.iter()
.map(|event| event.get_field("sender"))
.filter_map(FlatOk::flat_ok)
.map(|user: &UserId| user.server_name().to_owned())
.map(|user: OwnedUserId| user.server_name().to_owned())
.stream()
.chain(addl_via_servers)
.collect()
@@ -188,7 +182,7 @@ async fn knock_room_by_id_helper(
.await
{
debug_warn!("{sender_user} is already knocked in {room_id}");
return Ok(knock_room::v3::Response { room_id: room_id.into() });
return Ok(knock_room::v3::Response::new(room_id.into()));
}
if let Ok(membership) = services
@@ -353,13 +347,11 @@ async fn knock_room_helper_local(
return Err!(Request(Forbidden("This room does not support knocking.")));
}
let content = RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason: reason.clone(),
..RoomMemberEventContent::new(MembershipState::Knock)
};
let mut content = RoomMemberEventContent::new(MembershipState::Knock);
content.displayname = services.users.displayname(sender_user).await.ok();
content.avatar_url = services.users.avatar_url(sender_user).await.ok();
content.blurhash = services.users.blurhash(sender_user).await.ok();
content.reason = reason.clone();
// Try normal knock first
let Err(error) = services
@@ -424,13 +416,7 @@ async fn knock_room_helper_local(
);
knock_event_stub.insert(
"content".to_owned(),
to_canonical_value(RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason,
..RoomMemberEventContent::new(MembershipState::Knock)
})
to_canonical_value(content)
.expect("event is valid, we just created it"),
);
@@ -451,14 +437,14 @@ async fn knock_room_helper_local(
let knock_event = knock_event_stub;
info!("Asking {remote_server} for send_knock in room {room_id}");
let send_knock_request = federation::knock::send_knock::v1::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
pdu: services
let send_knock_request = federation::membership::create_knock_event::v1::Request::new(
room_id.to_owned(),
event_id.clone(),
services
.sending
.convert_to_outgoing_federation_event(knock_event.clone())
.await,
};
);
services
.sending
@@ -545,15 +531,16 @@ async fn knock_room_helper_remote(
.expect("Timestamp is valid js_int value"),
),
);
let mut knock_content = RoomMemberEventContent::new(MembershipState::Knock);
knock_content.displayname = services.users.displayname(sender_user).await.ok();
knock_content.avatar_url = services.users.avatar_url(sender_user).await.ok();
knock_content.blurhash = services.users.blurhash(sender_user).await.ok();
knock_content.reason = reason;
knock_event_stub.insert(
"content".to_owned(),
to_canonical_value(RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason,
..RoomMemberEventContent::new(MembershipState::Knock)
})
to_canonical_value(knock_content)
.expect("event is valid, we just created it"),
);
@@ -574,18 +561,18 @@ async fn knock_room_helper_remote(
let knock_event = knock_event_stub;
info!("Asking {remote_server} for send_knock in room {room_id}");
let send_knock_request = federation::knock::send_knock::v1::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
pdu: services
let request = federation::membership::create_knock_event::v1::Request::new(
room_id.to_owned(),
event_id.clone(),
services
.sending
.convert_to_outgoing_federation_event(knock_event.clone())
.await,
};
.await
);
let send_knock_response = services
.sending
.send_federation_request(&remote_server, send_knock_request)
.send_federation_request(&remote_server, request)
.await?;
info!("send_knock finished");
@@ -604,7 +591,20 @@ async fn knock_room_helper_remote(
let state = send_knock_response
.knock_room_state
.iter()
.map(|event| serde_json::from_str::<CanonicalJsonObject>(event.clone().into_json().get()))
.map(|event| {
#[allow(deprecated)]
let raw_value = match event {
federation::membership::RawStrippedState::Stripped(raw_state) => {
&raw_state.clone().into_json()
},
federation::membership::RawStrippedState::Pdu(raw_value) => {
raw_value
},
_ => panic!("unknown raw stripped state type"),
};
serde_json::from_str::<CanonicalJsonObject>(raw_value.get())
})
.filter_map(Result::ok);
let mut state_map: HashMap<u64, OwnedEventId> = HashMap::new();
@@ -709,7 +709,7 @@ async fn make_knock_request(
sender_user: &UserId,
room_id: &RoomId,
servers: &[OwnedServerName],
) -> Result<(federation::knock::create_knock_event_template::v1::Response, OwnedServerName)> {
) -> Result<(federation::membership::prepare_knock_event::v1::Response, OwnedServerName)> {
let mut make_knock_response_and_server =
Err!(BadServerResponse("No server available to assist in knocking."));
@@ -722,15 +722,14 @@ async fn make_knock_request(
info!("Asking {remote_server} for make_knock ({make_knock_counter})");
let mut request = federation::membership::prepare_knock_event::v1::Request::new(room_id.to_owned(), sender_user.to_owned());
request.ver = services.server.supported_room_versions().collect();
let make_knock_response = services
.sending
.send_federation_request(
remote_server,
federation::knock::create_knock_event_template::v1::Request {
room_id: room_id.to_owned(),
user_id: sender_user.to_owned(),
ver: services.server.supported_room_versions().collect(),
},
request,
)
.await;
+12 -22
View File
@@ -45,8 +45,7 @@ pub async fn leave_all_rooms(services: &Services, user_id: &UserId) {
let rooms_joined = services
.rooms
.state_cache
.rooms_joined(user_id)
.map(ToOwned::to_owned);
.rooms_joined(user_id);
let rooms_invited = services
.rooms
@@ -142,18 +141,17 @@ pub async fn leave_room(
.await;
match user_member_event_content {
| Ok(content) => {
| Ok(mut content) => {
content.membership = MembershipState::Leave;
content.reason = reason;
content.join_authorized_via_users_server = None;
content.is_direct = None;
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Leave,
reason,
join_authorized_via_users_server: None,
is_direct: None,
..content
}),
PduBuilder::state(user_id.to_string(), &content),
user_id,
Some(room_id),
&state_lock,
@@ -226,7 +224,6 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
.rooms
.state_cache
.servers_invite_via(room_id)
.map(ToOwned::to_owned)
.collect::<HashSet<OwnedServerName>>()
.await,
);
@@ -260,7 +257,7 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.filter_map(|sender| {
if !services.globals.user_is_local(sender) {
if !services.globals.user_is_local(&sender) {
Some(sender.server_name().to_owned())
} else {
None
@@ -289,10 +286,7 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
.sending
.send_federation_request(
remote_server.as_ref(),
federation::membership::prepare_leave_event::v1::Request {
room_id: room_id.to_owned(),
user_id: user_id.to_owned(),
},
federation::membership::prepare_leave_event::v1::Request::new(room_id.to_owned(), user_id.to_owned())
)
.await;
@@ -393,14 +387,10 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
.sending
.send_federation_request(
&remote_server,
federation::membership::create_leave_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
pdu: services
federation::membership::create_leave_event::v2::Request::new(room_id.to_owned(), event_id.clone(), services
.sending
.convert_to_outgoing_federation_event(leave_event.clone())
.await,
},
.await),
)
.await?;
+25 -51
View File
@@ -9,7 +9,7 @@
use futures::{FutureExt, StreamExt, future::join};
use ruma::{
api::client::membership::{
get_member_events::{self, v3::MembershipEventFilter},
get_member_events::{self},
joined_members::{self, v3::RoomMember},
},
events::{
@@ -43,8 +43,7 @@ pub(crate) async fn get_member_events_route(
return Err!(Request(Forbidden("You don't have permission to view this room.")));
}
Ok(get_member_events::v3::Response {
chunk: services
let chunk = services
.rooms
.state_accessor
.room_state_full(&body.room_id)
@@ -55,8 +54,9 @@ pub(crate) async fn get_member_events_route(
.map(Event::into_format)
.collect()
.boxed()
.await,
})
.await;
Ok(get_member_events::v3::Response::new(chunk))
}
/// # `POST /_matrix/client/r0/rooms/{roomId}/joined_members`
@@ -78,70 +78,44 @@ pub(crate) async fn joined_members_route(
return Err!(Request(Forbidden("You don't have permission to view this room.")));
}
Ok(joined_members::v3::Response {
joined: services
let joined = services
.rooms
.state_cache
.room_members(&body.room_id)
.map(ToOwned::to_owned)
.broad_then(|user_id| async move {
let mut member = RoomMember::new();
let (display_name, avatar_url) = join(
services.users.displayname(&user_id).ok(),
services.users.avatar_url(&user_id).ok(),
)
.await;
member.display_name = display_name;
member.avatar_url = avatar_url;
(user_id, RoomMember { display_name, avatar_url })
(user_id, member)
})
.collect()
.await,
})
.await;
Ok(joined_members::v3::Response::new(joined))
}
fn membership_filter<Pdu: Event>(
pdu: Pdu,
for_membership: Option<&MembershipEventFilter>,
not_membership: Option<&MembershipEventFilter>,
membership_state_filter: Option<&MembershipState>,
not_membership_state_filter: Option<&MembershipState>,
) -> Option<impl Event> {
let membership_state_filter = match for_membership {
| Some(MembershipEventFilter::Ban) => MembershipState::Ban,
| Some(MembershipEventFilter::Invite) => MembershipState::Invite,
| Some(MembershipEventFilter::Knock) => MembershipState::Knock,
| Some(MembershipEventFilter::Leave) => MembershipState::Leave,
| Some(_) | None => MembershipState::Join,
};
let not_membership_state_filter = match not_membership {
| Some(MembershipEventFilter::Ban) => MembershipState::Ban,
| Some(MembershipEventFilter::Invite) => MembershipState::Invite,
| Some(MembershipEventFilter::Join) => MembershipState::Join,
| Some(MembershipEventFilter::Knock) => MembershipState::Knock,
| Some(_) | None => MembershipState::Leave,
};
let evt_membership = pdu.get_content::<RoomMemberEventContent>().ok()?.membership;
if for_membership.is_some() && not_membership.is_some() {
if membership_state_filter != evt_membership
|| not_membership_state_filter == evt_membership
{
None
} else {
Some(pdu)
}
} else if for_membership.is_some() && not_membership.is_none() {
if membership_state_filter != evt_membership {
None
} else {
Some(pdu)
}
} else if not_membership.is_some() && for_membership.is_none() {
if not_membership_state_filter == evt_membership {
None
} else {
Some(pdu)
}
} else {
Some(pdu)
if let Some(membership_state_filter) = membership_state_filter
&& *membership_state_filter != evt_membership {
return None;
}
if let Some(not_membership_state_filter) = not_membership_state_filter
&& *not_membership_state_filter == evt_membership {
return None;
}
return Some(pdu);
}
+4 -5
View File
@@ -47,15 +47,14 @@ pub(crate) async fn joined_rooms_route(
State(services): State<crate::State>,
body: Ruma<joined_rooms::v3::Request>,
) -> Result<joined_rooms::v3::Response> {
Ok(joined_rooms::v3::Response {
joined_rooms: services
let joined_rooms = services
.rooms
.state_cache
.rooms_joined(body.sender_user())
.map(ToOwned::to_owned)
.collect()
.await,
})
.await;
Ok(joined_rooms::v3::Response::new(joined_rooms))
}
/// Checks if the room is banned in any way possible and the sender user is not
+9 -10
View File
@@ -18,9 +18,9 @@ pub(crate) async fn unban_user_route(
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
let current_member_content = services
let mut current_member_content = services
.rooms
.state_accessor
.get_member(&body.room_id, &body.user_id)
@@ -34,18 +34,17 @@ pub(crate) async fn unban_user_route(
)));
}
current_member_content.membership = MembershipState::Leave;
current_member_content.reason = body.reason.clone();
current_member_content.join_authorized_via_users_server = None;
current_member_content.third_party_invite = None;
current_member_content.is_direct = None;
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Leave,
reason: body.reason.clone(),
join_authorized_via_users_server: None,
third_party_invite: None,
is_direct: None,
..current_member_content
}),
PduBuilder::state(body.user_id.to_string(), &current_member_content),
sender_user,
Some(&body.room_id),
&state_lock,
+1 -1
View File
@@ -7,7 +7,7 @@
use ruma::{
EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
api::client::{
report_user,
reporting::report_user,
room::{report_content, report_room},
},
events::{Mentions, room::message::RoomMessageEventContent},
+4 -5
View File
@@ -26,13 +26,12 @@ pub(crate) async fn get_room_aliases_route(
return Err!(Request(Forbidden("You don't have permission to view this room.",)));
}
Ok(aliases::v3::Response {
aliases: services
let aliases = services
.rooms
.alias
.local_aliases_for_room(&body.room_id)
.map(ToOwned::to_owned)
.collect()
.await,
})
.await;
Ok(aliases::v3::Response::new(aliases))
}
+20 -93
View File
@@ -2,18 +2,15 @@
use axum::extract::State;
use conduwuit::{
Err, Result, RoomVersion, debug, debug_info, debug_warn, err, info,
Err, Result, debug, debug_info, debug_warn, err, info,
matrix::{StateKey, pdu::PduBuilder},
trace, warn,
};
use conduwuit_service::{Services, appservice::RegistrationInfo};
use futures::FutureExt;
use ruma::{
CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId,
api::client::room::{self, create_room},
events::{
CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId, api::client::room::{self, create_room}, events::{
TimelineEventType,
invite_permission_config::FilterLevel,
room::{
canonical_alias::RoomCanonicalAliasEventContent,
create::RoomCreateEventContent,
@@ -25,10 +22,9 @@
power_levels::RoomPowerLevelsEventContent,
topic::RoomTopicEventContent,
},
},
int,
serde::{JsonObject, Raw},
}, int, room_version_rules::RoomIdFormatVersion, serde::{JsonObject, Raw}
};
use ruminuwuity::invite_permission_config::FilterLevel;
use serde_json::{json, value::to_raw_value};
use crate::{Ruma, client::invite_helper};
@@ -81,15 +77,11 @@ pub(crate) async fn create_room_route(
},
| None => services.server.config.default_room_version.clone(),
};
let room_features = RoomVersion::new(&room_version)?;
let room_version_rules = room_version.rules().unwrap();
let room_id: Option<OwnedRoomId> = if !room_features.room_ids_as_hashes {
match &body.room_id {
| Some(custom_room_id) => Some(custom_room_id_check(&services, custom_room_id)?),
| None => Some(RoomId::new(services.globals.server_name())),
}
} else {
None
let room_id: Option<OwnedRoomId> = match room_version_rules.room_id_format {
RoomIdFormatVersion::V1 => Some(RoomId::new_v1(services.globals.server_name())),
_ => None,
};
// check if room ID doesn't already exist instead of erroring on auth check
@@ -167,7 +159,7 @@ pub(crate) async fn create_room_route(
use RoomVersionId::*;
let mut content = content
.deserialize_as::<CanonicalJsonObject>()
.deserialize_as_unchecked::<CanonicalJsonObject>()
.map_err(|e| {
err!(Request(BadJson(error!(
"Failed to deserialise content as canonical JSON: {e}"
@@ -201,8 +193,7 @@ pub(crate) async fn create_room_route(
let content = match room_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
RoomCreateEventContent::new_v1(sender_user.to_owned()),
| V11 => RoomCreateEventContent::new_v11(),
| _ => RoomCreateEventContent::new_v12(),
| _ => RoomCreateEventContent::new_v11(),
};
let mut content =
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
@@ -257,30 +248,23 @@ pub(crate) async fn create_room_route(
},
};
drop(state_lock);
if let Some(expected_room_id) = body.room_id.as_ref() {
if expected_room_id.as_str() != room_id.as_str() {
return Err!(Request(InvalidParam(
"Custom room ID {expected_room_id} does not match the generated room ID \
{room_id}.",
)));
}
}
debug!("Room created with ID {room_id}");
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
let state_lock = services.rooms.state.mutex.lock(room_id.as_str()).await;
// 2. Let the room creator join
let mut join_event = RoomMemberEventContent::new(MembershipState::Join);
join_event.displayname = services.users.displayname(sender_user).await.ok();
join_event.avatar_url = services.users.avatar_url(sender_user).await.ok();
join_event.blurhash = services.users.blurhash(sender_user).await.ok();
join_event.is_direct = Some(body.is_direct);
debug_info!("Joining {sender_user} to room {room_id}");
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(sender_user.to_string(), &RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
is_direct: Some(body.is_direct),
..RoomMemberEventContent::new(MembershipState::Join)
}),
PduBuilder::state(sender_user.to_string(), &join_event),
sender_user,
Some(&room_id),
&state_lock,
@@ -306,7 +290,7 @@ pub(crate) async fn create_room_route(
let mut creators: Vec<OwnedUserId> = vec![sender_user.to_owned()];
// Do we care about additional_creators?
if room_features.explicitly_privilege_room_creators {
if room_version_rules.explicitly_privilege_room_creators {
// Have they been specified?
if let Some(additional_creators) = create_content.get("additional_creators") {
// Are they a real array?
@@ -667,60 +651,3 @@ async fn room_alias_check(
Ok(full_room_alias)
}
/// if a room is being created with a custom room ID, run our checks against it
fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
// apply forbidden room alias checks to custom room IDs too
if services
.globals
.forbidden_alias_names()
.is_match(custom_room_id)
{
return Err!(Request(Unknown("Custom room ID is forbidden.")));
}
if custom_room_id.contains(':') {
return Err!(Request(InvalidParam(
"Custom room ID contained `:` which is not allowed. Please note that this expects a \
localpart, not the full room ID.",
)));
} else if custom_room_id.contains(char::is_whitespace) {
return Err!(Request(InvalidParam(
"Custom room ID contained spaces which is not valid."
)));
}
let server_name = services.globals.server_name();
let mut room_id = custom_room_id.to_owned();
if custom_room_id.contains(':') {
if !custom_room_id.starts_with('!') {
return Err!(Request(InvalidParam(
"Custom room ID contains an unexpected `:` which is not allowed.",
)));
}
} else if custom_room_id.starts_with('!') {
return Err!(Request(InvalidParam(
"Room ID is prefixed with !, but is not fully qualified. You likely did not want \
this.",
)));
} else {
room_id = format!("!{custom_room_id}:{server_name}");
}
OwnedRoomId::parse(room_id)
.map_err(Into::into)
.and_then(|full_room_id| {
if full_room_id
.server_name()
.expect("failed to extract server name from room ID")
!= server_name
{
Err!(Request(InvalidParam("Custom room ID must be on this server.",)))
} else {
Ok(full_room_id)
}
})
.inspect(|full_room_id| {
debug_info!(%full_room_id, "Full custom room ID");
})
.inspect_err(|e| warn!(?e, %custom_room_id, "Failed to create room with custom room ID",))
}
+4 -3
View File
@@ -44,15 +44,16 @@ impl<Err, Req, Fut, Fun, $($tx,)*> RumaHandler<($($tx,)* Ruma<Req>,)> for Fun
$( $tx: FromRequestParts<State> + Send + Sync + 'static, )*
{
fn add_routes(&'static self, router: Router<State>) -> Router<State> {
Req::METADATA
.history
use ruma::api::path_builder::PathBuilder;
Req::PATH_BUILDER
.all_paths()
.fold(router, |router, path| self.add_route(router, path))
}
fn add_route(&'static self, router: Router<State>, path: &str) -> Router<State> {
let action = |$($tx,)* req| self($($tx,)* req).map_ok(RumaResponse);
let method = method_to_filter(&Req::METADATA.method);
let method = method_to_filter(&Req::METHOD);
router.route(path, on(method, action))
}
}