refactor: Ruma upstraming, bake a little more

This commit is contained in:
Jade Ellis
2026-04-07 14:40:10 +01:00
committed by Ginger
parent 204bc1367e
commit a4e64383b7
115 changed files with 1907 additions and 1504 deletions
Generated
+7
View File
@@ -999,6 +999,7 @@ dependencies = [
name = "conduwuit_admin" name = "conduwuit_admin"
version = "0.5.8" version = "0.5.8"
dependencies = [ dependencies = [
"assign",
"clap", "clap",
"conduwuit_api", "conduwuit_api",
"conduwuit_core", "conduwuit_core",
@@ -1022,6 +1023,7 @@ dependencies = [
name = "conduwuit_api" name = "conduwuit_api"
version = "0.5.8" version = "0.5.8"
dependencies = [ dependencies = [
"assign",
"async-trait", "async-trait",
"axum", "axum",
"axum-client-ip", "axum-client-ip",
@@ -1068,6 +1070,7 @@ version = "0.5.8"
dependencies = [ dependencies = [
"argon2", "argon2",
"arrayvec", "arrayvec",
"assign",
"axum", "axum",
"axum-extra", "axum-extra",
"bytes", "bytes",
@@ -1161,6 +1164,7 @@ dependencies = [
name = "conduwuit_router" name = "conduwuit_router"
version = "0.5.8" version = "0.5.8"
dependencies = [ dependencies = [
"assign",
"axum", "axum",
"axum-client-ip", "axum-client-ip",
"axum-server", "axum-server",
@@ -1198,6 +1202,7 @@ name = "conduwuit_service"
version = "0.5.8" version = "0.5.8"
dependencies = [ dependencies = [
"askama", "askama",
"assign",
"async-trait", "async-trait",
"base64 0.22.1", "base64 0.22.1",
"blurhash", "blurhash",
@@ -1247,6 +1252,7 @@ name = "conduwuit_web"
version = "0.5.8" version = "0.5.8"
dependencies = [ dependencies = [
"askama", "askama",
"assign",
"async-trait", "async-trait",
"axum", "axum",
"axum-extra", "axum-extra",
@@ -4846,6 +4852,7 @@ dependencies = [
name = "ruminuwuity" name = "ruminuwuity"
version = "0.5.7" version = "0.5.7"
dependencies = [ dependencies = [
"assign",
"ruma", "ruma",
"serde", "serde",
"serde_json", "serde_json",
+3
View File
@@ -342,6 +342,9 @@ version = "0.1.88"
[workspace.dependencies.lru-cache] [workspace.dependencies.lru-cache]
version = "0.1.2" version = "0.1.2"
[workspace.dependencies.assign]
version = "1.1.1"
# Used for matrix spec type definitions and helpers # Used for matrix spec type definitions and helpers
[workspace.dependencies.ruma] [workspace.dependencies.ruma]
# version = "0.14.1" # version = "0.14.1"
+1
View File
@@ -84,6 +84,7 @@ ctor.workspace = true
futures.workspace = true futures.workspace = true
lettre.workspace = true lettre.workspace = true
log.workspace = true log.workspace = true
assign.workspace = true
ruma.workspace = true ruma.workspace = true
serde_json.workspace = true serde_json.workspace = true
serde-saphyr.workspace = true serde-saphyr.workspace = true
+1
View File
@@ -88,6 +88,7 @@ lettre.workspace = true
log.workspace = true log.workspace = true
rand.workspace = true rand.workspace = true
reqwest.workspace = true reqwest.workspace = true
assign.workspace = true
ruma.workspace = true ruma.workspace = true
ruminuwuity.workspace = true ruminuwuity.workspace = true
serde_html_form.workspace = true serde_html_form.workspace = true
+1 -4
View File
@@ -1,10 +1,7 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{Err, Result, info, utils::ReadyExt, warn}; use conduwuit::{Err, Result, info, utils::ReadyExt, warn};
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use ruma::{ use ruma::{OwnedRoomAliasId, events::room::message::RoomMessageEventContent};
OwnedRoomAliasId,
events::room::message::RoomMessageEventContent,
};
use ruminuwuity::admin::continuwuity::rooms; use ruminuwuity::admin::continuwuity::rooms;
use crate::{Ruma, client::leave_room}; use crate::{Ruma, client::leave_room};
+3 -4
View File
@@ -81,7 +81,8 @@ pub(crate) async fn update_device_route(
}; };
debug!( debug!(
"Creating new device for {sender_user} from appservice {} as device ID does not exist", "Creating new device for {sender_user} from appservice {} as device ID does not \
exist",
appservice.registration.id appservice.registration.id
); );
@@ -121,9 +122,7 @@ pub(crate) async fn delete_device_route(
let appservice = body.appservice_info.as_ref(); let appservice = body.appservice_info.as_ref();
if appservice.is_some() { if appservice.is_some() {
debug!( debug!("Skipping UIAA for {sender_user} as this is from an appservice");
"Skipping UIAA for {sender_user} as this is from an appservice"
);
services services
.users .users
.remove_device(sender_user, &body.device_id) .remove_device(sender_user, &body.device_id)
+8 -3
View File
@@ -17,7 +17,8 @@ use futures::{
future::{join, join4, join5}, future::{join, join4, join5},
}; };
use ruma::{ use ruma::{
OwnedRoomId, RoomId, ServerName, UInt, UserId, api::{ OwnedRoomId, RoomId, ServerName, UInt, UserId,
api::{
client::{ client::{
directory::{ directory::{
get_public_rooms, get_public_rooms_filtered, get_room_visibility, get_public_rooms, get_public_rooms_filtered, get_room_visibility,
@@ -26,14 +27,18 @@ use ruma::{
room, room,
}, },
federation, federation,
}, directory::{Filter, PublicRoomsChunk, RoomNetwork, RoomTypeFilter}, events::{ },
directory::{Filter, PublicRoomsChunk, RoomNetwork, RoomTypeFilter},
events::{
StateEventType, StateEventType,
room::{ room::{
create::RoomCreateEventContent, create::RoomCreateEventContent,
join_rules::{JoinRule, RoomJoinRulesEventContent}, join_rules::{JoinRule, RoomJoinRulesEventContent},
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent}, power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
}, },
}, room::JoinRuleKind, uint },
room::JoinRuleKind,
uint,
}; };
use tokio::join; use tokio::join;
+4 -1
View File
@@ -8,7 +8,10 @@ use conduwuit::{
use futures::FutureExt; use futures::FutureExt;
use ruma::{ use ruma::{
RoomId, UserId, RoomId, UserId,
api::{client::membership::invite_user, federation::membership::{RawStrippedState, create_invite}}, api::{
client::membership::invite_user,
federation::membership::{RawStrippedState, create_invite},
},
events::room::member::{MembershipState, RoomMemberEventContent}, events::room::member::{MembershipState, RoomMemberEventContent},
}; };
use ruminuwuity::invite_permission_config::FilterLevel; use ruminuwuity::invite_permission_config::FilterLevel;
+13 -13
View File
@@ -208,10 +208,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
) )
.await?; .await?;
let addl_via_servers = services let addl_via_servers = services.rooms.state_cache.servers_invite_via(&room_id);
.rooms
.state_cache
.servers_invite_via(&room_id);
let addl_state_servers = services let addl_state_servers = services
.rooms .rooms
@@ -432,8 +429,7 @@ async fn join_room_by_id_helper_remote(
join_event_stub.insert( join_event_stub.insert(
"content".to_owned(), "content".to_owned(),
to_canonical_value(join_content) to_canonical_value(join_content).expect("event is valid, we just created it"),
.expect("event is valid, we just created it"),
); );
// We keep the "event_id" in the pdu only in v1 or // We keep the "event_id" in the pdu only in v1 or
@@ -462,10 +458,14 @@ async fn join_room_by_id_helper_remote(
let mut join_event = join_event_stub; let mut join_event = join_event_stub;
info!("Asking {remote_server} for send_join in room {room_id}"); info!("Asking {remote_server} for send_join in room {room_id}");
let send_join_request = federation::membership::create_join_event::v2::Request::new(room_id.to_owned(), event_id.clone(), services let send_join_request = federation::membership::create_join_event::v2::Request::new(
room_id.to_owned(),
event_id.clone(),
services
.sending .sending
.convert_to_outgoing_federation_event(join_event.clone()) .convert_to_outgoing_federation_event(join_event.clone())
.await); .await,
);
let send_join_response = match services let send_join_response = match services
.sending .sending
@@ -816,15 +816,15 @@ async fn make_join_request(
servers.len() servers.len()
); );
let mut request = federation::membership::prepare_join_event::v1::Request::new(room_id.to_owned(), sender_user.to_owned()); 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(); request.ver = services.server.supported_room_versions().collect();
let make_join_response = services let make_join_response = services
.sending .sending
.send_federation_request( .send_federation_request(remote_server, request)
remote_server,
request
)
.await; .await;
trace!("make_join response: {:?}", make_join_response); trace!("make_join response: {:?}", make_join_response);
+20 -24
View File
@@ -15,16 +15,20 @@ use conduwuit::{
}; };
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use ruma::{ use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, RoomVersionId, UserId, api::{ CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName,
OwnedUserId, RoomId, RoomVersionId, UserId,
api::{
client::knock::knock_room, client::knock::knock_room,
federation::{self}, federation::{self},
}, canonical_json::to_canonical_value, events::{ },
canonical_json::to_canonical_value,
events::{
StateEventType, StateEventType,
room::{ room::{
join_rules::{AllowRule, JoinRule}, join_rules::{AllowRule, JoinRule},
member::{MembershipState, RoomMemberEventContent}, member::{MembershipState, RoomMemberEventContent},
}, },
} },
}; };
use service::{ use service::{
Services, Services,
@@ -108,10 +112,7 @@ pub(crate) async fn knock_room_route(
) )
.await?; .await?;
let addl_via_servers = services let addl_via_servers = services.rooms.state_cache.servers_invite_via(&room_id);
.rooms
.state_cache
.servers_invite_via(&room_id);
let addl_state_servers = services let addl_state_servers = services
.rooms .rooms
@@ -416,8 +417,7 @@ async fn knock_room_helper_local(
); );
knock_event_stub.insert( knock_event_stub.insert(
"content".to_owned(), "content".to_owned(),
to_canonical_value(content) to_canonical_value(content).expect("event is valid, we just created it"),
.expect("event is valid, we just created it"),
); );
// In order to create a compatible ref hash (EventID) the `hashes` field needs // In order to create a compatible ref hash (EventID) the `hashes` field needs
@@ -540,8 +540,7 @@ async fn knock_room_helper_remote(
knock_event_stub.insert( knock_event_stub.insert(
"content".to_owned(), "content".to_owned(),
to_canonical_value(knock_content) to_canonical_value(knock_content).expect("event is valid, we just created it"),
.expect("event is valid, we just created it"),
); );
// In order to create a compatible ref hash (EventID) the `hashes` field needs // In order to create a compatible ref hash (EventID) the `hashes` field needs
@@ -567,7 +566,7 @@ async fn knock_room_helper_remote(
services services
.sending .sending
.convert_to_outgoing_federation_event(knock_event.clone()) .convert_to_outgoing_federation_event(knock_event.clone())
.await .await,
); );
let send_knock_response = services let send_knock_response = services
@@ -594,13 +593,10 @@ async fn knock_room_helper_remote(
.map(|event| { .map(|event| {
#[allow(deprecated)] #[allow(deprecated)]
let raw_value = match event { let raw_value = match event {
federation::membership::RawStrippedState::Stripped(raw_state) => { | federation::membership::RawStrippedState::Stripped(raw_state) =>
&raw_state.clone().into_json() &raw_state.clone().into_json(),
}, | federation::membership::RawStrippedState::Pdu(raw_value) => raw_value,
federation::membership::RawStrippedState::Pdu(raw_value) => { | _ => panic!("unknown raw stripped state type"),
raw_value
},
_ => panic!("unknown raw stripped state type"),
}; };
serde_json::from_str::<CanonicalJsonObject>(raw_value.get()) serde_json::from_str::<CanonicalJsonObject>(raw_value.get())
@@ -722,15 +718,15 @@ async fn make_knock_request(
info!("Asking {remote_server} for make_knock ({make_knock_counter})"); 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()); 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(); request.ver = services.server.supported_room_versions().collect();
let make_knock_response = services let make_knock_response = services
.sending .sending
.send_federation_request( .send_federation_request(remote_server, request)
remote_server,
request,
)
.await; .await;
trace!("make_knock response: {make_knock_response:?}"); trace!("make_knock response: {make_knock_response:?}");
+11 -7
View File
@@ -42,10 +42,7 @@ pub(crate) async fn leave_room_route(
// Make a user leave all their joined rooms, rescinds knocks, forgets all rooms, // Make a user leave all their joined rooms, rescinds knocks, forgets all rooms,
// and ignores errors // and ignores errors
pub async fn leave_all_rooms(services: &Services, user_id: &UserId) { pub async fn leave_all_rooms(services: &Services, user_id: &UserId) {
let rooms_joined = services let rooms_joined = services.rooms.state_cache.rooms_joined(user_id);
.rooms
.state_cache
.rooms_joined(user_id);
let rooms_invited = services let rooms_invited = services
.rooms .rooms
@@ -286,7 +283,10 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
.sending .sending
.send_federation_request( .send_federation_request(
remote_server.as_ref(), remote_server.as_ref(),
federation::membership::prepare_leave_event::v1::Request::new(room_id.to_owned(), user_id.to_owned()) federation::membership::prepare_leave_event::v1::Request::new(
room_id.to_owned(),
user_id.to_owned(),
),
) )
.await; .await;
@@ -387,10 +387,14 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
.sending .sending
.send_federation_request( .send_federation_request(
&remote_server, &remote_server,
federation::membership::create_leave_event::v2::Request::new(room_id.to_owned(), event_id.clone(), services federation::membership::create_leave_event::v2::Request::new(
room_id.to_owned(),
event_id.clone(),
services
.sending .sending
.convert_to_outgoing_federation_event(leave_event.clone()) .convert_to_outgoing_federation_event(leave_event.clone())
.await), .await,
),
) )
.await?; .await?;
+31 -29
View File
@@ -44,17 +44,17 @@ pub(crate) async fn get_member_events_route(
} }
let chunk = services let chunk = services
.rooms .rooms
.state_accessor .state_accessor
.room_state_full(&body.room_id) .room_state_full(&body.room_id)
.ready_filter_map(Result::ok) .ready_filter_map(Result::ok)
.ready_filter(|((ty, _), _)| *ty == StateEventType::RoomMember) .ready_filter(|((ty, _), _)| *ty == StateEventType::RoomMember)
.map(at!(1)) .map(at!(1))
.ready_filter_map(|pdu| membership_filter(pdu, membership, not_membership)) .ready_filter_map(|pdu| membership_filter(pdu, membership, not_membership))
.map(Event::into_format) .map(Event::into_format)
.collect() .collect()
.boxed() .boxed()
.await; .await;
Ok(get_member_events::v3::Response::new(chunk)) Ok(get_member_events::v3::Response::new(chunk))
} }
@@ -79,23 +79,23 @@ pub(crate) async fn joined_members_route(
} }
let joined = services let joined = services
.rooms .rooms
.state_cache .state_cache
.room_members(&body.room_id) .room_members(&body.room_id)
.broad_then(|user_id| async move { .broad_then(|user_id| async move {
let mut member = RoomMember::new(); let mut member = RoomMember::new();
let (display_name, avatar_url) = join( let (display_name, avatar_url) = join(
services.users.displayname(&user_id).ok(), services.users.displayname(&user_id).ok(),
services.users.avatar_url(&user_id).ok(), services.users.avatar_url(&user_id).ok(),
) )
.await;
member.display_name = display_name;
member.avatar_url = avatar_url;
(user_id, member)
})
.collect()
.await; .await;
member.display_name = display_name;
member.avatar_url = avatar_url;
(user_id, member)
})
.collect()
.await;
Ok(joined_members::v3::Response::new(joined)) Ok(joined_members::v3::Response::new(joined))
} }
@@ -108,12 +108,14 @@ fn membership_filter<Pdu: Event>(
let evt_membership = pdu.get_content::<RoomMemberEventContent>().ok()?.membership; let evt_membership = pdu.get_content::<RoomMemberEventContent>().ok()?.membership;
if let Some(membership_state_filter) = membership_state_filter if let Some(membership_state_filter) = membership_state_filter
&& *membership_state_filter != evt_membership { && *membership_state_filter != evt_membership
{
return None; return None;
} }
if let Some(not_membership_state_filter) = not_membership_state_filter if let Some(not_membership_state_filter) = not_membership_state_filter
&& *not_membership_state_filter == evt_membership { && *not_membership_state_filter == evt_membership
{
return None; return None;
} }
+5 -5
View File
@@ -48,11 +48,11 @@ pub(crate) async fn joined_rooms_route(
body: Ruma<joined_rooms::v3::Request>, body: Ruma<joined_rooms::v3::Request>,
) -> Result<joined_rooms::v3::Response> { ) -> Result<joined_rooms::v3::Response> {
let joined_rooms = services let joined_rooms = services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(body.sender_user()) .rooms_joined(body.sender_user())
.collect() .collect()
.await; .await;
Ok(joined_rooms::v3::Response::new(joined_rooms)) Ok(joined_rooms::v3::Response::new(joined_rooms))
} }
+5 -5
View File
@@ -27,11 +27,11 @@ pub(crate) async fn get_room_aliases_route(
} }
let aliases = services let aliases = services
.rooms .rooms
.alias .alias
.local_aliases_for_room(&body.room_id) .local_aliases_for_room(&body.room_id)
.collect() .collect()
.await; .await;
Ok(aliases::v3::Response::new(aliases)) Ok(aliases::v3::Response::new(aliases))
} }
+9 -4
View File
@@ -9,7 +9,9 @@ use conduwuit::{
use conduwuit_service::{Services, appservice::RegistrationInfo}; use conduwuit_service::{Services, appservice::RegistrationInfo};
use futures::FutureExt; use futures::FutureExt;
use ruma::{ 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, TimelineEventType,
room::{ room::{
canonical_alias::RoomCanonicalAliasEventContent, canonical_alias::RoomCanonicalAliasEventContent,
@@ -22,7 +24,10 @@ use ruma::{
power_levels::RoomPowerLevelsEventContent, power_levels::RoomPowerLevelsEventContent,
topic::RoomTopicEventContent, topic::RoomTopicEventContent,
}, },
}, int, room_version_rules::RoomIdFormatVersion, serde::{JsonObject, Raw} },
int,
room_version_rules::RoomIdFormatVersion,
serde::{JsonObject, Raw},
}; };
use ruminuwuity::invite_permission_config::FilterLevel; use ruminuwuity::invite_permission_config::FilterLevel;
use serde_json::{json, value::to_raw_value}; use serde_json::{json, value::to_raw_value};
@@ -80,8 +85,8 @@ pub(crate) async fn create_room_route(
let room_version_rules = room_version.rules().unwrap(); let room_version_rules = room_version.rules().unwrap();
let room_id: Option<OwnedRoomId> = match room_version_rules.room_id_format { let room_id: Option<OwnedRoomId> = match room_version_rules.room_id_format {
RoomIdFormatVersion::V1 => Some(RoomId::new_v1(services.globals.server_name())), | RoomIdFormatVersion::V1 => Some(RoomId::new_v1(services.globals.server_name())),
_ => None, | _ => None,
}; };
// check if room ID doesn't already exist instead of erroring on auth check // check if room ID doesn't already exist instead of erroring on auth check
+1 -1
View File
@@ -45,7 +45,7 @@ macro_rules! ruma_handler {
{ {
fn add_routes(&'static self, router: Router<State>) -> Router<State> { fn add_routes(&'static self, router: Router<State>) -> Router<State> {
use ruma::api::path_builder::PathBuilder; use ruma::api::path_builder::PathBuilder;
Req::PATH_BUILDER Req::PATH_BUILDER
.all_paths() .all_paths()
.fold(router, |router, path| self.add_route(router, path)) .fold(router, |router, path| self.add_route(router, path))
+1
View File
@@ -90,6 +90,7 @@ rand_core = { version = "0.6.4", features = ["getrandom"] }
regex.workspace = true regex.workspace = true
reqwest.workspace = true reqwest.workspace = true
sha2.workspace = true sha2.workspace = true
assign.workspace = true
ruma.workspace = true ruma.workspace = true
sanitize-filename.workspace = true sanitize-filename.workspace = true
serde_json.workspace = true serde_json.workspace = true
+5 -5
View File
@@ -46,7 +46,7 @@ macro_rules! err {
(Request(Forbidden($level:ident!($($args:tt)+)))) => {{ (Request(Forbidden($level:ident!($($args:tt)+)))) => {{
let mut buf = String::new(); let mut buf = String::new();
$crate::error::Error::Request( $crate::error::Error::Request(
$crate::ruma::api::client::error::ErrorKind::forbidden(), $crate::ruma::api::error::ErrorKind::Forbidden,
$crate::err_log!(buf, $level, $($args)+), $crate::err_log!(buf, $level, $($args)+),
$crate::http::StatusCode::BAD_REQUEST $crate::http::StatusCode::BAD_REQUEST
) )
@@ -54,7 +54,7 @@ macro_rules! err {
(Request(Forbidden($($args:tt)+))) => { (Request(Forbidden($($args:tt)+))) => {
$crate::error::Error::Request( $crate::error::Error::Request(
$crate::ruma::api::client::error::ErrorKind::forbidden(), $crate::ruma::api::error::ErrorKind::Forbidden,
$crate::format_maybe!($($args)+), $crate::format_maybe!($($args)+),
$crate::http::StatusCode::BAD_REQUEST $crate::http::StatusCode::BAD_REQUEST
) )
@@ -63,7 +63,7 @@ macro_rules! err {
(Request($variant:ident($level:ident!($($args:tt)+)))) => {{ (Request($variant:ident($level:ident!($($args:tt)+)))) => {{
let mut buf = String::new(); let mut buf = String::new();
$crate::error::Error::Request( $crate::error::Error::Request(
$crate::ruma::api::client::error::ErrorKind::$variant, $crate::ruma::api::error::ErrorKind::$variant,
$crate::err_log!(buf, $level, $($args)+), $crate::err_log!(buf, $level, $($args)+),
$crate::http::StatusCode::BAD_REQUEST $crate::http::StatusCode::BAD_REQUEST
) )
@@ -71,7 +71,7 @@ macro_rules! err {
(Request($variant:ident($($args:tt)+))) => { (Request($variant:ident($($args:tt)+))) => {
$crate::error::Error::Request( $crate::error::Error::Request(
$crate::ruma::api::client::error::ErrorKind::$variant, $crate::ruma::api::error::ErrorKind::$variant,
$crate::format_maybe!($($args)+), $crate::format_maybe!($($args)+),
$crate::http::StatusCode::BAD_REQUEST $crate::http::StatusCode::BAD_REQUEST
) )
@@ -79,7 +79,7 @@ macro_rules! err {
(Request($variant:ident($($args:tt)+), $status_code:ident)) => { (Request($variant:ident($($args:tt)+), $status_code:ident)) => {
$crate::error::Error::Request( $crate::error::Error::Request(
$crate::ruma::api::client::error::ErrorKind::$variant, $crate::ruma::api::error::ErrorKind::$variant,
$crate::format_maybe!($($args)+), $crate::format_maybe!($($args)+),
$crate::http::StatusCode::$status_code, $crate::http::StatusCode::$status_code,
) )
+9 -7
View File
@@ -87,7 +87,7 @@ pub enum Error {
#[error("Arithmetic operation failed: {0}")] #[error("Arithmetic operation failed: {0}")]
Arithmetic(Cow<'static, str>), Arithmetic(Cow<'static, str>),
#[error("{0:?}: {1}")] #[error("{0:?}: {1}")]
BadRequest(ruma::api::client::error::ErrorKind, &'static str), //TODO: remove BadRequest(ruma::api::error::ErrorKind, &'static str), //TODO: remove
#[error("{0}")] #[error("{0}")]
BadServerResponse(Cow<'static, str>), BadServerResponse(Cow<'static, str>),
#[error(transparent)] #[error(transparent)]
@@ -103,7 +103,7 @@ pub enum Error {
#[error("Feature '{0}' is not available on this server.")] #[error("Feature '{0}' is not available on this server.")]
FeatureDisabled(Cow<'static, str>), FeatureDisabled(Cow<'static, str>),
#[error("Remote server {0} responded with: {1}")] #[error("Remote server {0} responded with: {1}")]
Federation(ruma::OwnedServerName, ruma::api::client::error::Error), Federation(ruma::OwnedServerName, ruma::api::error::Error),
#[error("{0} in {1}")] #[error("{0} in {1}")]
InconsistentRoomState(&'static str, ruma::OwnedRoomId), InconsistentRoomState(&'static str, ruma::OwnedRoomId),
#[error(transparent)] #[error(transparent)]
@@ -117,11 +117,13 @@ pub enum Error {
#[error("from {0}: {1}")] #[error("from {0}: {1}")]
Redaction(ruma::OwnedServerName, ruma::canonical_json::RedactionError), Redaction(ruma::OwnedServerName, ruma::canonical_json::RedactionError),
#[error("{0:?}: {1}")] #[error("{0:?}: {1}")]
Request(ruma::api::client::error::ErrorKind, Cow<'static, str>, http::StatusCode), Request(ruma::api::error::ErrorKind, Cow<'static, str>, http::StatusCode),
#[error(transparent)] #[error(transparent)]
Ruma(#[from] ruma::api::client::error::Error), Ruma(#[from] ruma::api::error::Error),
#[error(transparent)] #[error(transparent)]
Signatures(#[from] ruma::signatures::Error), SignatureJson(#[from] ruma::signatures::JsonError),
#[error(transparent)]
SignatureVerification(#[from] ruma::signatures::VerificationError),
#[error(transparent)] #[error(transparent)]
StateRes(#[from] crate::state_res::Error), StateRes(#[from] crate::state_res::Error),
#[error("uiaa")] #[error("uiaa")]
@@ -162,8 +164,8 @@ impl Error {
/// Returns the Matrix error code / error kind /// Returns the Matrix error code / error kind
#[inline] #[inline]
pub fn kind(&self) -> ruma::api::client::error::ErrorKind { pub fn kind(&self) -> ruma::api::error::ErrorKind {
use ruma::api::client::error::ErrorKind::{Unknown, Unrecognized}; use ruma::api::error::ErrorKind::{Unknown, Unrecognized};
match self { match self {
| Self::Federation(_, error) | Self::Ruma(error) => | Self::Federation(_, error) | Self::Ruma(error) =>
+10 -14
View File
@@ -3,10 +3,8 @@ use http::StatusCode;
use http_body_util::Full; use http_body_util::Full;
use ruma::api::{ use ruma::api::{
OutgoingResponse, OutgoingResponse,
client::{ client::uiaa::UiaaResponse,
error::{ErrorBody, ErrorKind, StandardErrorBody}, error::{ErrorBody, ErrorKind, StandardErrorBody},
uiaa::UiaaResponse,
},
}; };
use super::Error; use super::Error;
@@ -53,7 +51,7 @@ impl From<Error> for UiaaResponse {
let body = ErrorBody::Standard(StandardErrorBody::new(error.kind(), error.message())); let body = ErrorBody::Standard(StandardErrorBody::new(error.kind(), error.message()));
Self::MatrixError(ruma::api::client::error::Error::new(error.status_code(), body)) Self::MatrixError(ruma::api::error::Error::new(error.status_code(), body))
} }
} }
@@ -70,7 +68,7 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
match kind { match kind {
// 429 // 429
| LimitExceeded { .. } => StatusCode::TOO_MANY_REQUESTS, | LimitExceeded(_) => StatusCode::TOO_MANY_REQUESTS,
// 413 // 413
| TooLarge => StatusCode::PAYLOAD_TOO_LARGE, | TooLarge => StatusCode::PAYLOAD_TOO_LARGE,
@@ -79,28 +77,26 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
| Unrecognized => StatusCode::METHOD_NOT_ALLOWED, | Unrecognized => StatusCode::METHOD_NOT_ALLOWED,
// 404 // 404
| NotFound => | NotFound => StatusCode::NOT_FOUND,
StatusCode::NOT_FOUND,
// 403 // 403
| GuestAccessForbidden | GuestAccessForbidden
| ThreepidAuthFailed | ThreepidAuthFailed
| UserDeactivated | UserDeactivated
| ThreepidDenied | ThreepidDenied
| WrongRoomKeysVersion { .. } | WrongRoomKeysVersion(_)
| UserSuspended | UserSuspended
| Forbidden { .. } => StatusCode::FORBIDDEN, | Forbidden => StatusCode::FORBIDDEN,
// 401 // 401
| UnknownToken { .. } | MissingToken | Unauthorized | UserLocked => | UnknownToken(_) | MissingToken | Unauthorized | UserLocked => StatusCode::UNAUTHORIZED,
StatusCode::UNAUTHORIZED,
// 400 // 400
| _ => StatusCode::BAD_REQUEST, | _ => StatusCode::BAD_REQUEST,
} }
} }
pub(super) fn ruma_error_message(error: &ruma::api::client::error::Error) -> String { pub(super) fn ruma_error_message(error: &ruma::api::error::Error) -> String {
if let ErrorBody::Standard(StandardErrorBody { message, .. }) = &error.body { if let ErrorBody::Standard(StandardErrorBody { message, .. }) = &error.body {
return message.clone(); return message.clone();
} }
@@ -108,7 +104,7 @@ pub(super) fn ruma_error_message(error: &ruma::api::client::error::Error) -> Str
format!("{error}") format!("{error}")
} }
pub(super) fn ruma_error_kind(e: &ruma::api::client::error::Error) -> &ErrorKind { pub(super) fn ruma_error_kind(e: &ruma::api::error::Error) -> &ErrorKind {
e.error_kind().unwrap_or(&ErrorKind::Unknown) e.error_kind().unwrap_or(&ErrorKind::Unknown)
} }
+1 -1
View File
@@ -25,7 +25,7 @@ pub fn gen_event_id(
room_version_id: &RoomVersionId, room_version_id: &RoomVersionId,
) -> Result<OwnedEventId> { ) -> Result<OwnedEventId> {
let Some(rules) = room_version_id.rules() else { let Some(rules) = room_version_id.rules() else {
return Err!("Cannot generate event ID for unknown room version {room_version_id}") return Err!("Cannot generate event ID for unknown room version {room_version_id}");
}; };
let reference_hash = ruma::signatures::reference_hash(value, &rules)?; let reference_hash = ruma::signatures::reference_hash(value, &rules)?;
let event_id: OwnedEventId = format!("${reference_hash}").try_into()?; let event_id: OwnedEventId = format!("${reference_hash}").try_into()?;
+1 -1
View File
@@ -9,4 +9,4 @@ pub mod versions;
pub use event::{Event, TypeExt as EventTypeExt}; pub use event::{Event, TypeExt as EventTypeExt};
pub use pdu::{Pdu, PduBuilder, PduCount, PduEvent, PduId, RawPduId, ShortId}; pub use pdu::{Pdu, PduBuilder, PduCount, PduEvent, PduId, RawPduId, ShortId};
pub use state_key::StateKey; pub use state_key::StateKey;
pub use state_res::{StateMap, TypeStateKey}; pub use state_res::{StateMap, TypeStateKey};
+2 -3
View File
@@ -6,7 +6,7 @@ use crate::{Err, Error, Result, err, implement};
#[implement(super::Pdu)] #[implement(super::Pdu)]
pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) -> Result { pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) -> Result {
let Some(rules) = room_version_id.rules() else { let Some(rules) = room_version_id.rules() else {
return Err!("Cannot redact event for unknown room version {room_version_id}") return Err!("Cannot redact event for unknown room version {room_version_id}");
}; };
self.unsigned = None; self.unsigned = None;
@@ -14,8 +14,7 @@ pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) ->
let mut content = serde_json::from_str(self.content.get()) let mut content = serde_json::from_str(self.content.get())
.map_err(|e| err!(Request(BadJson("Failed to deserialize content into type: {e}"))))?; .map_err(|e| err!(Request(BadJson("Failed to deserialize content into type: {e}"))))?;
redact_content_in_place(&mut content, &rules.redaction, self.kind.to_string()) redact_content_in_place(&mut content, &rules.redaction, self.kind.to_string());
.map_err(|e| Error::Redaction(self.sender.server_name().to_owned(), e))?;
let reason = serde_json::to_value(reason).expect("Failed to preserialize reason"); let reason = serde_json::to_value(reason).expect("Failed to preserialize reason");
+41 -21
View File
@@ -5,12 +5,16 @@ use futures::{
future::{OptionFuture, join, join3}, future::{OptionFuture, join, join3},
}; };
use ruma::{ use ruma::{
Int, OwnedUserId, RoomVersionId, UserId, events::room::{ Int, OwnedUserId, RoomVersionId, UserId,
events::room::{
create::RoomCreateEventContent, create::RoomCreateEventContent,
join_rules::{JoinRule, RoomJoinRulesEventContent}, join_rules::{JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, ThirdPartyInvite}, member::{MembershipState, ThirdPartyInvite},
power_levels::RoomPowerLevelsEventContent, power_levels::RoomPowerLevelsEventContent,
}, int, room_version_rules::{RoomIdFormatVersion, RoomVersionRules}, serde::Raw, },
int,
room_version_rules::{RoomIdFormatVersion, RoomVersionRules},
serde::Raw,
}; };
use serde::{ use serde::{
Deserialize, Deserialize,
@@ -117,11 +121,11 @@ pub fn auth_types_for_event(
// TODO: restore this once 3pid support isn't broken // TODO: restore this once 3pid support isn't broken
// if membership == MembershipState::Invite { // if membership == MembershipState::Invite {
// if let Some(Ok(t_id)) = content.third_party_invite.map(|t| t.deserialize()) { // if let Some(Ok(t_id)) = content.third_party_invite.map(|t|
// let key = // t.deserialize()) { let key =
// (StateEventType::RoomThirdPartyInvite, t_id.signed.token.into()); // (StateEventType::RoomThirdPartyInvite,
// if !auth_types.contains(&key) { // t_id.signed.token.into()); if !auth_types.contains(&
// auth_types.push(key); // key) { auth_types.push(key);
// } // }
// } // }
// } // }
@@ -214,13 +218,17 @@ where
return Ok(false); return Ok(false);
} }
if room_version.room_id_format == RoomIdFormatVersion::V2 && incoming_event.room_id().is_some() { if room_version.room_id_format == RoomIdFormatVersion::V2
&& incoming_event.room_id().is_some()
{
warn!("room create event incorrectly claims to have a room ID when it should not"); warn!("room create event incorrectly claims to have a room ID when it should not");
return Ok(false); return Ok(false);
} }
if !room_version.authorization.use_room_create_sender if !room_version.authorization.use_room_create_sender
&& !room_version.authorization.explicitly_privilege_room_creators && !room_version
.authorization
.explicitly_privilege_room_creators
{ {
// If content has no creator field, reject // If content has no creator field, reject
if content.creator.is_none() { if content.creator.is_none() {
@@ -343,7 +351,7 @@ where
// Only in some room versions 6 and below // Only in some room versions 6 and below
if room_version.authorization.special_case_room_aliases { if room_version.authorization.special_case_room_aliases {
// 4. If type is m.room.aliases // 4. If type is m.room.aliases
if *incoming_event.event_type() == TimelineEventType::RoomAliases { if *incoming_event.event_type() == TimelineEventType::from("m.room.aliases") {
debug!("starting m.room.aliases check"); debug!("starting m.room.aliases check");
// If sender's domain doesn't matches state_key, reject // If sender's domain doesn't matches state_key, reject
@@ -493,7 +501,10 @@ where
if is_creator { int!(100) } else { int!(0) } if is_creator { int!(100) } else { int!(0) }
}, },
}; };
if room_version.authorization.explicitly_privilege_room_creators { if room_version
.authorization
.explicitly_privilege_room_creators
{
// If the user sent the create event, or is listed in additional_creators, just // If the user sent the create event, or is listed in additional_creators, just
// give them Int::MAX // give them Int::MAX
if sender == room_create_event.sender() if sender == room_create_event.sender()
@@ -555,7 +566,10 @@ where
if *incoming_event.event_type() == TimelineEventType::RoomPowerLevels { if *incoming_event.event_type() == TimelineEventType::RoomPowerLevels {
debug!("starting m.room.power_levels check"); debug!("starting m.room.power_levels check");
let mut creators = BTreeSet::new(); let mut creators = BTreeSet::new();
if room_version.authorization.explicitly_privilege_room_creators { if room_version
.authorization
.explicitly_privilege_room_creators
{
creators.insert(create_event.sender().to_owned()); creators.insert(create_event.sender().to_owned());
for creator in room_create_content.additional_creators.iter().flatten() { for creator in room_create_content.additional_creators.iter().flatten() {
creators.insert(creator.deserialize()?); creators.insert(creator.deserialize()?);
@@ -710,7 +724,10 @@ where
let mut creators = BTreeSet::new(); let mut creators = BTreeSet::new();
creators.insert(create_room.sender().to_owned()); creators.insert(create_room.sender().to_owned());
if room_version.authorization.explicitly_privilege_room_creators { if room_version
.authorization
.explicitly_privilege_room_creators
{
// Explicitly privilege room creators // Explicitly privilege room creators
// If the sender sent the create event, or in additional_creators, give them // If the sender sent the create event, or in additional_creators, give them
// Int::MAX. Same case for target. // Int::MAX. Same case for target.
@@ -878,7 +895,8 @@ where
trace!(sender=%sender, "sender is invited or already joined to room, allowing join"); trace!(sender=%sender, "sender is invited or already joined to room, allowing join");
true true
}, },
| JoinRule::KnockRestricted(_) if !room_version.authorization.knock_restricted_join_rule => | JoinRule::KnockRestricted(_)
if !room_version.authorization.knock_restricted_join_rule =>
{ {
warn!( warn!(
"Join rule is knock_restricted but room version does not support it" "Join rule is knock_restricted but room version does not support it"
@@ -1508,15 +1526,17 @@ fn verify_third_party_invite(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use ruma::{events::{ use ruma::{
StateEventType, TimelineEventType, events::{
room::{ StateEventType, TimelineEventType,
join_rules::{ room::{
AllowRule, JoinRule, Restricted, RoomJoinRulesEventContent, join_rules::{AllowRule, JoinRule, Restricted, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent},
}, },
member::{MembershipState, RoomMemberEventContent},
}, },
}, room::RoomMembership, room_version_rules::RoomVersionRules}; room::RoomMembership,
room_version_rules::RoomVersionRules,
};
use serde_json::value::to_raw_value as to_raw_json_value; use serde_json::value::to_raw_value as to_raw_json_value;
use crate::{ use crate::{
+48 -31
View File
@@ -25,13 +25,14 @@ use ruma::{
StateEventType, TimelineEventType, StateEventType, TimelineEventType,
room::member::{MembershipState, RoomMemberEventContent}, room::member::{MembershipState, RoomMemberEventContent},
}, },
int, room_version_rules::{RoomIdFormatVersion, RoomVersionRules, StateResolutionVersion}, int,
room_version_rules::{RoomIdFormatVersion, RoomVersionRules, StateResolutionVersion},
}; };
use serde_json::from_str as from_json_str; use serde_json::from_str as from_json_str;
pub(crate) use self::error::Error; pub(crate) use self::error::Error;
use self::power_levels::PowerLevelsContentFields;
pub use self::event_auth::{auth_check, auth_types_for_event}; pub use self::event_auth::{auth_check, auth_types_for_event};
use self::power_levels::PowerLevelsContentFields;
use crate::{ use crate::{
debug, debug_error, err, debug, debug_error, err,
matrix::{Event, StateKey}, matrix::{Event, StateKey},
@@ -106,19 +107,21 @@ where
debug!(count = conflicting.len(), "conflicting events"); debug!(count = conflicting.len(), "conflicting events");
trace!(map = ?conflicting, "conflicting events"); trace!(map = ?conflicting, "conflicting events");
let (conflicted_state_subgraph, initial_state) = let (conflicted_state_subgraph, initial_state) = if let StateResolutionVersion::V2(v2_rules) =
if let StateResolutionVersion::V2(v2_rules) = stateres_version && v2_rules.consider_conflicted_state_subgraph { stateres_version
let csg = calculate_conflicted_subgraph(&conflicting, event_fetch) && v2_rules.consider_conflicted_state_subgraph
.await {
.ok_or_else(|| { let csg = calculate_conflicted_subgraph(&conflicting, event_fetch)
Error::InvalidPdu("Failed to calculate conflicted subgraph".to_owned()) .await
})?; .ok_or_else(|| {
debug!(count = csg.len(), "conflicted subgraph"); Error::InvalidPdu("Failed to calculate conflicted subgraph".to_owned())
trace!(set = ?csg, "conflicted subgraph"); })?;
(csg, HashMap::new()) debug!(count = csg.len(), "conflicted subgraph");
} else { trace!(set = ?csg, "conflicted subgraph");
(HashSet::new(), unconflicted.clone()) (csg, HashMap::new())
}; } else {
(HashSet::new(), unconflicted.clone())
};
// `all_conflicted` contains unique items // `all_conflicted` contains unique items
// synapse says `full_set = {eid for eid in full_conflicted_set if eid in // synapse says `full_set = {eid for eid in full_conflicted_set if eid in
@@ -974,10 +977,14 @@ mod tests {
use maplit::{hashmap, hashset}; use maplit::{hashmap, hashset};
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use ruma::{ use ruma::{
MilliSecondsSinceUnixEpoch, OwnedEventId, RoomVersionId, events::{ MilliSecondsSinceUnixEpoch, OwnedEventId, RoomVersionId,
events::{
StateEventType, TimelineEventType, StateEventType, TimelineEventType,
room::join_rules::{JoinRule, RoomJoinRulesEventContent}, room::join_rules::{JoinRule, RoomJoinRulesEventContent},
}, int, room_version_rules::RoomVersionRules, uint },
int,
room_version_rules::RoomVersionRules,
uint,
}; };
use serde_json::{json, value::to_raw_value as to_raw_json_value}; use serde_json::{json, value::to_raw_value as to_raw_json_value};
@@ -1423,13 +1430,18 @@ mod tests {
}) })
.collect(); .collect();
let resolved = let resolved = match super::resolve(
match super::resolve(&RoomVersionRules::V2, &state_sets, &auth_chain, &fetcher, &exists) &RoomVersionRules::V2,
.await &state_sets,
{ &auth_chain,
| Ok(state) => state, &fetcher,
| Err(e) => panic!("{e}"), &exists,
}; )
.await
{
| Ok(state) => state,
| Err(e) => panic!("{e}"),
};
assert_eq!(expected, resolved); assert_eq!(expected, resolved);
} }
@@ -1536,13 +1548,18 @@ mod tests {
let fetcher = |id: OwnedEventId| ready(ev_map.get(&id).cloned()); let fetcher = |id: OwnedEventId| ready(ev_map.get(&id).cloned());
let exists = |id: OwnedEventId| ready(ev_map.get(&id).is_some()); let exists = |id: OwnedEventId| ready(ev_map.get(&id).is_some());
let resolved = let resolved = match super::resolve(
match super::resolve(&RoomVersionRules::V6, &state_sets, &auth_chain, &fetcher, &exists) &RoomVersionRules::V6,
.await &state_sets,
{ &auth_chain,
| Ok(state) => state, &fetcher,
| Err(e) => panic!("{e}"), &exists,
}; )
.await
{
| Ok(state) => state,
| Err(e) => panic!("{e}"),
};
debug!( debug!(
resolved = ?resolved resolved = ?resolved
+14 -5
View File
@@ -1,13 +1,16 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use ruma::{ use ruma::{
Int, OwnedUserId, UserId, events::{TimelineEventType, room::power_levels::RoomPowerLevelsEventContent}, power_levels::{NotificationPowerLevels, default_power_level}, room_version_rules::{AuthorizationRules, RoomVersionRules}, serde::deserialize_v1_powerlevel Int, OwnedUserId, UserId,
events::{TimelineEventType, room::power_levels::RoomPowerLevelsEventContent},
power_levels::{NotificationPowerLevels, default_power_level},
room_version_rules::{AuthorizationRules, RoomVersionRules},
serde::deserialize_v1_powerlevel,
}; };
use super::serde_backports::*;
use serde::Deserialize; use serde::Deserialize;
use serde_json::{Error, from_str as from_json_str}; use serde_json::{Error, from_str as from_json_str};
use super::Result; use super::{Result, serde_backports::*};
use crate::error; use crate::error;
#[derive(Deserialize)] #[derive(Deserialize)]
@@ -44,7 +47,10 @@ struct IntRoomPowerLevelsEventContent {
} }
impl IntRoomPowerLevelsEventContent { impl IntRoomPowerLevelsEventContent {
fn to_room_power_levels_content(self, auth_rules: &AuthorizationRules) -> RoomPowerLevelsEventContent { fn to_room_power_levels_content(
self,
auth_rules: &AuthorizationRules,
) -> RoomPowerLevelsEventContent {
let IntRoomPowerLevelsEventContent { let IntRoomPowerLevelsEventContent {
ban, ban,
events, events,
@@ -105,7 +111,10 @@ pub(crate) fn deserialize_power_levels(
} }
} }
fn deserialize_integer_power_levels(content: &str, auth_rules: &AuthorizationRules) -> Option<RoomPowerLevelsEventContent> { fn deserialize_integer_power_levels(
content: &str,
auth_rules: &AuthorizationRules,
) -> Option<RoomPowerLevelsEventContent> {
match from_json_str::<IntRoomPowerLevelsEventContent>(content) { match from_json_str::<IntRoomPowerLevelsEventContent>(content) {
| Ok(content) => Some(content.to_room_power_levels_content(auth_rules)), | Ok(content) => Some(content.to_room_power_levels_content(auth_rules)),
| Err(_) => { | Err(_) => {
+83 -83
View File
@@ -1,120 +1,120 @@
//! These functions are copied from an old version of Ruma. power_levels.rs uses them to lazily deserialize power level events. //! These functions are copied from an old version of Ruma. power_levels.rs uses
//! Upstream Ruma uses a much more elegant approach in its state resolution code, which we may want //! them to lazily deserialize power level events. Upstream Ruma uses a much
//! more elegant approach in its state resolution code, which we may want
//! to look into at some point. //! to look into at some point.
use std::marker::PhantomData; use std::{fmt, marker::PhantomData};
use std::fmt;
use serde::{Deserialize, Deserializer, de::{MapAccess, Visitor}};
use ruma::{Int, serde::deserialize_v1_powerlevel}; use ruma::{Int, serde::deserialize_v1_powerlevel};
use serde::{
Deserialize, Deserializer,
de::{MapAccess, Visitor},
};
/// Take a Map with values of either an integer number or a string and deserialize /// Take a Map with values of either an integer number or a string and
/// those to integer numbers in a Vec of sorted pairs. /// deserialize those to integer numbers in a Vec of sorted pairs.
/// ///
/// To be used like this: /// To be used like this:
/// `#[serde(deserialize_with = "vec_deserialize_v1_powerlevel_values")]` /// `#[serde(deserialize_with = "vec_deserialize_v1_powerlevel_values")]`
pub fn vec_deserialize_v1_powerlevel_values<'de, D, T>(de: D) -> Result<Vec<(T, Int)>, D::Error> pub fn vec_deserialize_v1_powerlevel_values<'de, D, T>(de: D) -> Result<Vec<(T, Int)>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
T: Deserialize<'de> + Ord, T: Deserialize<'de> + Ord,
{ {
#[repr(transparent)] #[repr(transparent)]
struct IntWrap(Int); struct IntWrap(Int);
impl<'de> Deserialize<'de> for IntWrap { impl<'de> Deserialize<'de> for IntWrap {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
deserialize_v1_powerlevel(deserializer).map(IntWrap) deserialize_v1_powerlevel(deserializer).map(IntWrap)
} }
} }
struct IntMapVisitor<T> { struct IntMapVisitor<T> {
_phantom: PhantomData<T>, _phantom: PhantomData<T>,
} }
impl<T> IntMapVisitor<T> { impl<T> IntMapVisitor<T> {
fn new() -> Self { fn new() -> Self { Self { _phantom: PhantomData } }
Self { _phantom: PhantomData } }
}
}
impl<'de, T> Visitor<'de> for IntMapVisitor<T> impl<'de, T> Visitor<'de> for IntMapVisitor<T>
where where
T: Deserialize<'de> + Ord, T: Deserialize<'de> + Ord,
{ {
type Value = Vec<(T, Int)>; type Value = Vec<(T, Int)>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a map with integers or strings as values") formatter.write_str("a map with integers or strings as values")
} }
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> { fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
let mut res = Vec::new(); let mut res = Vec::new();
if let Some(hint) = map.size_hint() { if let Some(hint) = map.size_hint() {
res.reserve(hint); res.reserve(hint);
} }
while let Some((k, IntWrap(v))) = map.next_entry()? { while let Some((k, IntWrap(v))) = map.next_entry()? {
res.push((k, v)); res.push((k, v));
} }
res.sort_unstable(); res.sort_unstable();
res.dedup_by(|a, b| a.0 == b.0); res.dedup_by(|a, b| a.0 == b.0);
Ok(res) Ok(res)
} }
} }
de.deserialize_map(IntMapVisitor::new()) de.deserialize_map(IntMapVisitor::new())
} }
/// Take a Map with integer values and deserialize those to a Vec of sorted pairs /// Take a Map with integer values and deserialize those to a Vec of sorted
/// pairs
/// ///
/// To be used like this: /// To be used like this:
/// `#[serde(deserialize_with = "vec_deserialize_int_powerlevel_values")]` /// `#[serde(deserialize_with = "vec_deserialize_int_powerlevel_values")]`
pub fn vec_deserialize_int_powerlevel_values<'de, D, T>(de: D) -> Result<Vec<(T, Int)>, D::Error> pub fn vec_deserialize_int_powerlevel_values<'de, D, T>(de: D) -> Result<Vec<(T, Int)>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
T: Deserialize<'de> + Ord, T: Deserialize<'de> + Ord,
{ {
struct IntMapVisitor<T> { struct IntMapVisitor<T> {
_phantom: PhantomData<T>, _phantom: PhantomData<T>,
} }
impl<T> IntMapVisitor<T> { impl<T> IntMapVisitor<T> {
fn new() -> Self { fn new() -> Self { Self { _phantom: PhantomData } }
Self { _phantom: PhantomData } }
}
}
impl<'de, T> Visitor<'de> for IntMapVisitor<T> impl<'de, T> Visitor<'de> for IntMapVisitor<T>
where where
T: Deserialize<'de> + Ord, T: Deserialize<'de> + Ord,
{ {
type Value = Vec<(T, Int)>; type Value = Vec<(T, Int)>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a map with integers as values") formatter.write_str("a map with integers as values")
} }
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> { fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
let mut res = Vec::new(); let mut res = Vec::new();
if let Some(hint) = map.size_hint() { if let Some(hint) = map.size_hint() {
res.reserve(hint); res.reserve(hint);
} }
while let Some(item) = map.next_entry()? { while let Some(item) = map.next_entry()? {
res.push(item); res.push(item);
} }
res.sort_unstable(); res.sort_unstable();
res.dedup_by(|a, b| a.0 == b.0); res.dedup_by(|a, b| a.0 == b.0);
Ok(res) Ok(res)
} }
} }
de.deserialize_map(IntMapVisitor::new()) de.deserialize_map(IntMapVisitor::new())
} }
+15 -5
View File
@@ -6,13 +6,18 @@ use std::{
use futures::future::ready; use futures::future::ready;
use ruma::{ use ruma::{
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, RoomVersionId, ServerSignatures, UserId, event_id, events::{ EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, RoomVersionId, ServerSignatures,
UserId, event_id,
events::{
TimelineEventType, TimelineEventType,
room::{ room::{
join_rules::{JoinRule, RoomJoinRulesEventContent}, join_rules::{JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent}, member::{MembershipState, RoomMemberEventContent},
}, },
}, int, room_id, room_version_rules::RoomVersionRules, uint, user_id },
int, room_id,
room_version_rules::RoomVersionRules,
uint, user_id,
}; };
use serde_json::{ use serde_json::{
json, json,
@@ -130,9 +135,14 @@ pub(crate) async fn do_check(
let event_map = &event_map; let event_map = &event_map;
let fetch = |id: OwnedEventId| ready(event_map.get(&id).cloned()); let fetch = |id: OwnedEventId| ready(event_map.get(&id).cloned());
let exists = |id: OwnedEventId| ready(event_map.get(&id).is_some()); let exists = |id: OwnedEventId| ready(event_map.get(&id).is_some());
let resolved = let resolved = super::resolve(
super::resolve(&RoomVersionRules::V6, state_sets, &auth_chain_sets, &fetch, &exists) &RoomVersionRules::V6,
.await; state_sets,
&auth_chain_sets,
&fetch,
&exists,
)
.await;
match resolved { match resolved {
| Ok(state) => state, | Ok(state) => state,
+38 -38
View File
@@ -1,44 +1,44 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
pub fn versions() -> Vec<String> { pub fn versions() -> Vec<String> {
vec![ vec![
"r0.0.1".to_owned(), "r0.0.1".to_owned(),
"r0.1.0".to_owned(), "r0.1.0".to_owned(),
"r0.2.0".to_owned(), "r0.2.0".to_owned(),
"r0.3.0".to_owned(), "r0.3.0".to_owned(),
"r0.4.0".to_owned(), "r0.4.0".to_owned(),
"r0.5.0".to_owned(), "r0.5.0".to_owned(),
"r0.6.0".to_owned(), "r0.6.0".to_owned(),
"r0.6.1".to_owned(), "r0.6.1".to_owned(),
"v1.1".to_owned(), "v1.1".to_owned(),
"v1.2".to_owned(), "v1.2".to_owned(),
"v1.3".to_owned(), "v1.3".to_owned(),
"v1.4".to_owned(), "v1.4".to_owned(),
"v1.5".to_owned(), "v1.5".to_owned(),
"v1.8".to_owned(), "v1.8".to_owned(),
"v1.11".to_owned(), "v1.11".to_owned(),
"v1.12".to_owned(), "v1.12".to_owned(),
"v1.13".to_owned(), "v1.13".to_owned(),
"v1.14".to_owned(), "v1.14".to_owned(),
] ]
} }
pub fn unstable_features() -> BTreeMap<String, bool> { pub fn unstable_features() -> BTreeMap<String, bool> {
BTreeMap::from_iter([ BTreeMap::from_iter([
("org.matrix.e2e_cross_signing".to_owned(), true), ("org.matrix.e2e_cross_signing".to_owned(), true),
("org.matrix.msc2285.stable".to_owned(), true), /* private read receipts (https://github.com/matrix-org/matrix-spec-proposals/pull/2285) */ ("org.matrix.msc2285.stable".to_owned(), true), /* private read receipts (https://github.com/matrix-org/matrix-spec-proposals/pull/2285) */
("uk.half-shot.msc2666.query_mutual_rooms".to_owned(), true), /* query mutual rooms (https://github.com/matrix-org/matrix-spec-proposals/pull/2666) */ ("uk.half-shot.msc2666.query_mutual_rooms".to_owned(), true), /* query mutual rooms (https://github.com/matrix-org/matrix-spec-proposals/pull/2666) */
("org.matrix.msc2836".to_owned(), true), /* threading/threads (https://github.com/matrix-org/matrix-spec-proposals/pull/2836) */ ("org.matrix.msc2836".to_owned(), true), /* threading/threads (https://github.com/matrix-org/matrix-spec-proposals/pull/2836) */
("org.matrix.msc2946".to_owned(), true), /* spaces/hierarchy summaries (https://github.com/matrix-org/matrix-spec-proposals/pull/2946) */ ("org.matrix.msc2946".to_owned(), true), /* spaces/hierarchy summaries (https://github.com/matrix-org/matrix-spec-proposals/pull/2946) */
("org.matrix.msc3026.busy_presence".to_owned(), true), /* busy presence status (https://github.com/matrix-org/matrix-spec-proposals/pull/3026) */ ("org.matrix.msc3026.busy_presence".to_owned(), true), /* busy presence status (https://github.com/matrix-org/matrix-spec-proposals/pull/3026) */
("org.matrix.msc3827".to_owned(), true), /* filtering of /publicRooms by room type (https://github.com/matrix-org/matrix-spec-proposals/pull/3827) */ ("org.matrix.msc3827".to_owned(), true), /* filtering of /publicRooms by room type (https://github.com/matrix-org/matrix-spec-proposals/pull/3827) */
("org.matrix.msc3952_intentional_mentions".to_owned(), true), /* intentional mentions (https://github.com/matrix-org/matrix-spec-proposals/pull/3952) */ ("org.matrix.msc3952_intentional_mentions".to_owned(), true), /* intentional mentions (https://github.com/matrix-org/matrix-spec-proposals/pull/3952) */
("org.matrix.msc3916.stable".to_owned(), true), /* authenticated media (https://github.com/matrix-org/matrix-spec-proposals/pull/3916) */ ("org.matrix.msc3916.stable".to_owned(), true), /* authenticated media (https://github.com/matrix-org/matrix-spec-proposals/pull/3916) */
("org.matrix.msc4180".to_owned(), true), /* stable flag for 3916 (https://github.com/matrix-org/matrix-spec-proposals/pull/4180) */ ("org.matrix.msc4180".to_owned(), true), /* stable flag for 3916 (https://github.com/matrix-org/matrix-spec-proposals/pull/4180) */
("uk.tcpip.msc4133".to_owned(), true), /* Extending User Profile API with Key:Value Pairs (https://github.com/matrix-org/matrix-spec-proposals/pull/4133) */ ("uk.tcpip.msc4133".to_owned(), true), /* Extending User Profile API with Key:Value Pairs (https://github.com/matrix-org/matrix-spec-proposals/pull/4133) */
("us.cloke.msc4175".to_owned(), true), /* Profile field for user time zone (https://github.com/matrix-org/matrix-spec-proposals/pull/4175) */ ("us.cloke.msc4175".to_owned(), true), /* Profile field for user time zone (https://github.com/matrix-org/matrix-spec-proposals/pull/4175) */
("org.matrix.simplified_msc3575".to_owned(), true), /* Simplified Sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/4186) */ ("org.matrix.simplified_msc3575".to_owned(), true), /* Simplified Sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/4186) */
("uk.timedout.msc4323".to_owned(), true), /* agnostic suspend (https://github.com/matrix-org/matrix-spec-proposals/pull/4323) */ ("uk.timedout.msc4323".to_owned(), true), /* agnostic suspend (https://github.com/matrix-org/matrix-spec-proposals/pull/4323) */
("org.matrix.msc4155".to_owned(), true), /* invite filtering (https://github.com/matrix-org/matrix-spec-proposals/pull/4155) */ ("org.matrix.msc4155".to_owned(), true), /* invite filtering (https://github.com/matrix-org/matrix-spec-proposals/pull/4155) */
]) ])
} }
+1 -3
View File
@@ -38,9 +38,7 @@ pub use info::{
version, version,
version::{name, version}, version::{name, version},
}; };
pub use matrix::{ pub use matrix::{Event, EventTypeExt, Pdu, PduCount, PduEvent, PduId, pdu, state_res};
Event, EventTypeExt, Pdu, PduCount, PduEvent, PduId, pdu, state_res,
};
pub use parking_lot::{Mutex as SyncMutex, RwLock as SyncRwLock}; pub use parking_lot::{Mutex as SyncMutex, RwLock as SyncRwLock};
pub use server::Server; pub use server::Server;
pub use utils::{implement, result, result::Result}; pub use utils::{implement, result, result::Result};
+6 -6
View File
@@ -1,6 +1,6 @@
use std::{fmt, marker::PhantomData, str::FromStr}; use std::{fmt, marker::PhantomData, str::FromStr};
use ruma::{CanonicalJsonError, CanonicalJsonObject, canonical_json::try_from_json_map}; use ruma::{CanonicalJsonError, CanonicalJsonObject, canonical_json::to_canonical_value};
use crate::Result; use crate::Result;
@@ -11,12 +11,12 @@ use crate::Result;
pub fn to_canonical_object<T: serde::Serialize>( pub fn to_canonical_object<T: serde::Serialize>(
value: T, value: T,
) -> Result<CanonicalJsonObject, CanonicalJsonError> { ) -> Result<CanonicalJsonObject, CanonicalJsonError> {
use CanonicalJsonError::SerDe; use ruma::CanonicalJsonValue;
use serde::ser::Error;
match serde_json::to_value(value).map_err(SerDe)? { match to_canonical_value(value)? {
| serde_json::Value::Object(map) => try_from_json_map(map), | CanonicalJsonValue::Object(map) => Ok(map),
| _ => Err(SerDe(serde_json::Error::custom("Value must be an object"))), | _ => Err(to_canonical_value(1.0_f32).unwrap_err()), /* Hack to return a
* CanonicalJsonError */
} }
} }
+6 -2
View File
@@ -4,7 +4,10 @@ use std::{borrow::Cow, fmt::Debug};
use conduwuit::{ use conduwuit::{
arrayvec::ArrayVec, arrayvec::ArrayVec,
ruma::{EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId, room_id, serde::Raw, user_id}, ruma::{
EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId, room_id, serde::Raw,
user_id,
},
}; };
use serde::Serialize; use serde::Serialize;
@@ -439,7 +442,8 @@ fn serde_tuple_option_some_some() {
aa.push(0xFF); aa.push(0xFF);
aa.extend_from_slice(user_id.as_bytes()); aa.extend_from_slice(user_id.as_bytes());
let bb: (Option<OwnedRoomId>, Option<OwnedUserId>) = (Some(room_id.to_owned()), Some(user_id.to_owned())); let bb: (Option<OwnedRoomId>, Option<OwnedUserId>) =
(Some(room_id.to_owned()), Some(user_id.to_owned()));
let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple"); let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple");
assert_eq!(aa, bbs); assert_eq!(aa, bbs);
+1
View File
@@ -111,6 +111,7 @@ http-body-util.workspace = true
hyper.workspace = true hyper.workspace = true
hyper-util.workspace = true hyper-util.workspace = true
log.workspace = true log.workspace = true
assign.workspace = true
ruma.workspace = true ruma.workspace = true
rustls.workspace = true rustls.workspace = true
rustls.optional = true rustls.optional = true
+1 -1
View File
@@ -4,7 +4,7 @@ use axum::{Router, response::IntoResponse};
use conduwuit::Error; use conduwuit::Error;
use conduwuit_service::{Services, state, state::Guard}; use conduwuit_service::{Services, state, state::Guard};
use http::{StatusCode, Uri}; use http::{StatusCode, Uri};
use ruma::api::client::error::ErrorKind; use ruma::api::error::ErrorKind;
pub(crate) fn build(services: &Arc<Services>) -> (Router, Guard) { pub(crate) fn build(services: &Arc<Services>) -> (Router, Guard) {
let router = Router::<state::State>::new(); let router = Router::<state::State>::new();
+1
View File
@@ -20,6 +20,7 @@ unstable-msc3202 = []
unstable-msc4203 = [] unstable-msc4203 = []
[dependencies] [dependencies]
assign.workspace = true
ruma.workspace = true ruma.workspace = true
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
+1 -1
View File
@@ -1 +1 @@
pub mod rooms; pub mod rooms;
+19 -13
View File
@@ -1,5 +1,9 @@
pub mod v1 { pub mod v1 {
use ruma::{OwnedRoomAliasId, OwnedRoomId, OwnedUserId, api::{auth_scheme::AccessToken, request, response}, metadata}; use ruma::{
OwnedRoomAliasId, OwnedRoomId, OwnedUserId,
api::{auth_scheme::AccessToken, request, response},
metadata,
};
metadata! { metadata! {
method: PUT, method: PUT,
@@ -16,10 +20,10 @@ pub mod v1 {
pub room_id: OwnedRoomId, pub room_id: OwnedRoomId,
/// Whether to ban (true) or unban (false) the room. /// Whether to ban (true) or unban (false) the room.
/// If true, and the room is not banned, all local users will be evacuated /// If true, and the room is not banned, all local users will be
/// and prevented from re-joining. /// evacuated and prevented from re-joining.
/// If false, and the room is unbanned, local users will be allowed to re-join. /// If false, and the room is unbanned, local users will be allowed to
/// No-ops are no-ops. /// re-join. No-ops are no-ops.
pub banned: bool, pub banned: bool,
} }
@@ -27,24 +31,26 @@ pub mod v1 {
pub struct Response { pub struct Response {
pub kicked_users: Vec<OwnedUserId>, pub kicked_users: Vec<OwnedUserId>,
pub failed_kicked_users: Vec<OwnedUserId>, pub failed_kicked_users: Vec<OwnedUserId>,
pub local_aliases: Vec<OwnedRoomAliasId> pub local_aliases: Vec<OwnedRoomAliasId>,
} }
impl Request { impl Request {
#[must_use] #[must_use]
pub fn new(room_id: OwnedRoomId, banned: bool) -> Self { pub fn new(room_id: OwnedRoomId, banned: bool) -> Self { Self { room_id, banned } }
Self { room_id, banned }
}
} }
impl Response { impl Response {
#[must_use] #[must_use]
pub fn new( pub fn new(
kicked_users: Vec<OwnedUserId>, kicked_users: Vec<OwnedUserId>,
failed_kicked_users: Vec<OwnedUserId>, failed_kicked_users: Vec<OwnedUserId>,
local_aliases: Vec<OwnedRoomAliasId>, local_aliases: Vec<OwnedRoomAliasId>,
) -> Self { ) -> Self {
Self { kicked_users, failed_kicked_users, local_aliases } Self {
kicked_users,
failed_kicked_users,
local_aliases,
}
} }
} }
} }
@@ -1,6 +1,8 @@
pub mod v1 { pub mod v1 {
use ruma::{ use ruma::{
OwnedRoomId, api::{auth_scheme::AccessToken, request, response}, metadata OwnedRoomId,
api::{auth_scheme::AccessToken, request, response},
metadata,
}; };
metadata! { metadata! {
@@ -22,16 +24,12 @@ pub mod v1 {
} }
impl Request { impl Request {
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self { Self::default() }
Self::default()
}
} }
impl Response { impl Response {
#[must_use] #[must_use]
pub fn new(rooms: Vec<OwnedRoomId>) -> Self { pub fn new(rooms: Vec<OwnedRoomId>) -> Self { Self { rooms } }
Self { rooms }
}
} }
} }
@@ -1,2 +1,2 @@
pub mod list;
pub mod ban; pub mod ban;
pub mod list;
+41 -42
View File
@@ -3,51 +3,50 @@
//! Check the suspension status of a target user //! Check the suspension status of a target user
pub mod v1 { pub mod v1 {
//! `/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{userID}` ([msc]) //! `/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{userID}`
//! //! ([msc])
//! [msc]: https://github.com/matrix-org/matrix-spec-proposals/pull/4323 //!
//! [msc]: https://github.com/matrix-org/matrix-spec-proposals/pull/4323
use ruma::{ use ruma::{
OwnedUserId, api::{auth_scheme::AccessToken, request, response}, metadata OwnedUserId,
}; api::{auth_scheme::AccessToken, request, response},
metadata,
};
metadata! { metadata! {
method: GET, method: GET,
rate_limited: false, rate_limited: false,
authentication: AccessToken, authentication: AccessToken,
history: { history: {
unstable => "/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{user_id}", unstable => "/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{user_id}",
} }
} }
/// Request type for the get & set user suspension status endpoint. /// Request type for the get & set user suspension status endpoint.
#[request(error = ruma::api::client::Error)] #[request(error = ruma::api::error::Error)]
pub struct Request { pub struct Request {
/// The user to look up. /// The user to look up.
#[ruma_api(path)] #[ruma_api(path)]
pub user_id: OwnedUserId, pub user_id: OwnedUserId,
} }
/// Response type for the suspension endpoints /// Response type for the suspension endpoints
#[response(error = ruma::api::client::Error)] #[response(error = ruma::api::error::Error)]
pub struct Response { pub struct Response {
/// Whether the user is currently suspended. /// Whether the user is currently suspended.
pub suspended: bool, pub suspended: bool,
} }
impl Request { impl Request {
/// Creates a new `Request` with the given user id. /// Creates a new `Request` with the given user id.
#[must_use] #[must_use]
pub fn new(user_id: OwnedUserId) -> Self { pub fn new(user_id: OwnedUserId) -> Self { Self { user_id } }
Self { user_id } }
}
}
impl Response { impl Response {
/// Creates a new `Response` with the given suspension status. /// Creates a new `Response` with the given suspension status.
#[must_use] #[must_use]
pub fn new(suspended: bool) -> Self { pub fn new(suspended: bool) -> Self { Self { suspended } }
Self { suspended } }
} }
}
}
+1 -1
View File
@@ -1,3 +1,3 @@
pub mod continuwuity; pub mod continuwuity;
pub mod get_suspended; pub mod get_suspended;
pub mod set_suspended; pub mod set_suspended;
+42 -43
View File
@@ -3,53 +3,52 @@
//! Set the suspension status of a target user //! Set the suspension status of a target user
pub mod v1 { pub mod v1 {
//! `/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{userID}` ([msc]) //! `/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{userID}`
//! //! ([msc])
//! [msc]: https://github.com/matrix-org/matrix-spec-proposals/pull/4323 //!
//! [msc]: https://github.com/matrix-org/matrix-spec-proposals/pull/4323
use ruma::{ use ruma::{
OwnedUserId, api::{auth_scheme::AccessToken, request, response}, metadata OwnedUserId,
}; api::{auth_scheme::AccessToken, request, response},
metadata,
};
metadata! { metadata! {
method: PUT, method: PUT,
rate_limited: false, rate_limited: false,
authentication: AccessToken, authentication: AccessToken,
history: { history: {
unstable => "/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{user_id}", unstable => "/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{user_id}",
} }
} }
/// Request type for the set user suspension status endpoint. /// Request type for the set user suspension status endpoint.
#[request(error = ruma::api::client::Error)] #[request(error = ruma::api::error::Error)]
pub struct Request { pub struct Request {
/// The user to look up. /// The user to look up.
#[ruma_api(path)] #[ruma_api(path)]
pub user_id: OwnedUserId, pub user_id: OwnedUserId,
pub suspended: bool, pub suspended: bool,
} }
/// Response type for the suspension endpoints /// Response type for the suspension endpoints
#[response(error = ruma::api::client::Error)] #[response(error = ruma::api::error::Error)]
pub struct Response { pub struct Response {
/// Whether the user is currently suspended. /// Whether the user is currently suspended.
pub suspended: bool, pub suspended: bool,
} }
impl Request { impl Request {
/// Creates a new `Request` with the given user id. /// Creates a new `Request` with the given user id.
#[must_use] #[must_use]
pub fn new(user_id: OwnedUserId, suspended: bool) -> Self { pub fn new(user_id: OwnedUserId, suspended: bool) -> Self { Self { user_id, suspended } }
Self { user_id, suspended } }
}
}
impl Response { impl Response {
/// Creates a new `Response` with the given suspension status. /// Creates a new `Response` with the given suspension status.
#[must_use] #[must_use]
pub fn new(suspended: bool) -> Self { pub fn new(suspended: bool) -> Self { Self { suspended } }
Self { suspended } }
} }
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
pub mod user_may_invite; pub mod user_may_invite;
pub mod user_may_join_room; pub mod user_may_join_room;
@@ -1,50 +1,51 @@
//! `POST /api/1/spam_check/user_may_invite` //! `POST /api/1/spam_check/user_may_invite`
//! //!
//! Checks that a user may invite the given user to the given room via Draupnir anti-spam //! Checks that a user may invite the given user to the given room via Draupnir
//! anti-spam
pub mod v1 { pub mod v1 {
use ruma::{ use ruma::{
OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata OwnedRoomId, OwnedUserId,
}; api::{auth_scheme::AppserviceToken, request, response},
metadata,
};
metadata! { metadata! {
method: POST, method: POST,
rate_limited: false, rate_limited: false,
authentication: AppserviceToken, authentication: AppserviceToken,
history: { history: {
1.0 => "/api/1/spam_check/user_may_invite", 1.0 => "/api/1/spam_check/user_may_invite",
} }
} }
/// Request type for the `user_may_invite` callback. /// Request type for the `user_may_invite` callback.
#[request] #[request]
pub struct Request { pub struct Request {
/// The room the invitee is being invited to /// The room the invitee is being invited to
pub room_id: OwnedRoomId, pub room_id: OwnedRoomId,
/// The user sending the invite /// The user sending the invite
pub inviter: OwnedUserId, pub inviter: OwnedUserId,
/// The user being invited /// The user being invited
pub invitee: OwnedUserId, pub invitee: OwnedUserId,
} }
/// Response type for the `user_may_invite` callback. /// Response type for the `user_may_invite` callback.
#[response] #[response]
#[derive(Default)] #[derive(Default)]
pub struct Response; pub struct Response;
impl Request { impl Request {
/// Creates a new empty `Request`. /// Creates a new empty `Request`.
#[must_use] #[must_use]
pub fn new(room_id: OwnedRoomId, inviter: OwnedUserId, invitee: OwnedUserId) -> Self { pub fn new(room_id: OwnedRoomId, inviter: OwnedUserId, invitee: OwnedUserId) -> Self {
Self { room_id, inviter, invitee } Self { room_id, inviter, invitee }
} }
} }
impl Response { impl Response {
/// Creates a new empty `Response`. /// Creates a new empty `Response`.
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self { Self::default() }
Self::default() }
}
}
} }
@@ -1,50 +1,51 @@
//! `POST /api/1/spam_check/user_may_join_room` //! `POST /api/1/spam_check/user_may_join_room`
//! //!
//! Endpoint that checks whether a user may join a given room via Draupnir anti-spam //! Endpoint that checks whether a user may join a given room via Draupnir
//! anti-spam
pub mod v1 { pub mod v1 {
use ruma::{ use ruma::{
OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata OwnedRoomId, OwnedUserId,
}; api::{auth_scheme::AppserviceToken, request, response},
metadata,
};
metadata! { metadata! {
method: POST, method: POST,
rate_limited: false, rate_limited: false,
authentication: AppserviceToken, authentication: AppserviceToken,
history: { history: {
1.0 => "/api/1/spam_check/user_may_join_room", 1.0 => "/api/1/spam_check/user_may_join_room",
} }
} }
/// Request type for the `user_may_join_room` callback. /// Request type for the `user_may_join_room` callback.
#[request] #[request]
pub struct Request { pub struct Request {
/// The user trying to join a room /// The user trying to join a room
pub user: OwnedUserId, pub user: OwnedUserId,
/// The room the user is trying to join /// The room the user is trying to join
pub room: OwnedRoomId, pub room: OwnedRoomId,
/// Whether the user was invited to this room /// Whether the user was invited to this room
pub is_invited: bool, pub is_invited: bool,
} }
/// Response type for the `user_may_join_room` callback. /// Response type for the `user_may_join_room` callback.
#[response] #[response]
#[derive(Default)] #[derive(Default)]
pub struct Response; pub struct Response;
impl Request { impl Request {
/// Creates a new empty `Request`. /// Creates a new empty `Request`.
#[must_use] #[must_use]
pub fn new(user: OwnedUserId, room: OwnedRoomId, is_invited: bool) -> Self { pub fn new(user: OwnedUserId, room: OwnedRoomId, is_invited: bool) -> Self {
Self { user, room, is_invited } Self { user, room, is_invited }
} }
} }
impl Response { impl Response {
/// Creates a new empty `Response`. /// Creates a new empty `Response`.
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self { Self::default() }
Self::default() }
}
}
} }
+226 -202
View File
@@ -1,247 +1,271 @@
//! Types for invite filtering ([MSC4155]). //! Types for invite filtering ([MSC4155]).
//! //!
//! MSC4155: https://github.com/matrix-org/matrix-spec-proposals/pull/4155 //! MSC4155: https://github.com/matrix-org/matrix-spec-proposals/pull/4155
use ruma::{ServerName, UserId}; use ruma::{ServerName, UserId, exports::ruma_macros::EventContent};
use ruma::exports::ruma_macros::EventContent;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use wildmatch::WildMatch; use wildmatch::WildMatch;
/// Represents a user's level of filtering on actions from another user or server. /// Represents a user's level of filtering on actions from another user or
/// "Ignore" and "block" are defined in [MSC4283]. /// server. "Ignore" and "block" are defined in [MSC4283].
/// ///
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283 /// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FilterLevel { pub enum FilterLevel {
Allow, Allow,
Ignore, Ignore,
Block, Block,
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize, EventContent)] #[derive(Clone, Debug, Default, Deserialize, Serialize, EventContent)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "m.invite_permission_config", kind = GlobalAccountData)] #[ruma_event(type = "m.invite_permission_config", kind = GlobalAccountData)]
pub struct InvitePermissionConfigEventContent { pub struct InvitePermissionConfigEventContent {
/// A global on/off toggle for all rules /// A global on/off toggle for all rules
#[serde(default = "ruma::serde::default_true")] #[serde(default = "ruma::serde::default_true")]
pub enabled: bool, pub enabled: bool,
/// A list of globs matching users which are allowed to send an invite.
/// Entries in this list supersede entries in the ignored and blocked lists.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allowed_users: Vec<String>,
/// A list of globs matching users whose invites should be ignored (as defined in [MSC4283]).
///
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ignored_users: Vec<String>,
/// A list of globs matching users whose invites should be blocked (as defined in [MSC4283]).
/// Invites from blocked users should be refused with the M_INVITE_BLOCKED status code.
///
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub blocked_users: Vec<String>,
/// A list of globs matching users which are allowed to send an invite.
/// Entries in this list supersede entries in the ignored and blocked lists.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allowed_users: Vec<String>,
/// A list of globs matching users whose invites should be ignored (as
/// defined in [MSC4283]).
///
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ignored_users: Vec<String>,
/// A list of globs matching users whose invites should be blocked (as
/// defined in [MSC4283]). Invites from blocked users should be refused
/// with the M_INVITE_BLOCKED status code.
///
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub blocked_users: Vec<String>,
/// A list of globs matching servers which are allowed to send an invite. /// A list of globs matching servers which are allowed to send an invite.
/// Entries in this list supersede entries in the ignored and blocked lists. /// Entries in this list supersede entries in the ignored and blocked lists.
#[serde(default, skip_serializing_if = "Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allowed_servers: Vec<String>, pub allowed_servers: Vec<String>,
/// A list of globs matching servers whose invites should be ignored (as defined in [MSC4283]). /// A list of globs matching servers whose invites should be ignored (as
/// /// defined in [MSC4283]).
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283 ///
#[serde(default, skip_serializing_if = "Vec::is_empty")] /// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
pub ignored_servers: Vec<String>, #[serde(default, skip_serializing_if = "Vec::is_empty")]
/// A list of globs matching servers whose invites should be blocked (as defined in [MSC4283]). pub ignored_servers: Vec<String>,
/// Invites from blocked servers should be refused with the M_INVITE_BLOCKED status code. /// A list of globs matching servers whose invites should be blocked (as
/// /// defined in [MSC4283]). Invites from blocked servers should be refused
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283 /// with the M_INVITE_BLOCKED status code.
#[serde(default, skip_serializing_if = "Vec::is_empty")] ///
pub blocked_servers: Vec<String>, /// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub blocked_servers: Vec<String>,
} }
impl InvitePermissionConfigEventContent { impl InvitePermissionConfigEventContent {
/// Creates a new `InvitePermissionConfigEventContent` from six lists of globs. /// Creates a new `InvitePermissionConfigEventContent` from six lists of
#[must_use] /// globs.
pub fn new( #[must_use]
enabled: bool, pub fn new(
allowed_users: Vec<String>, enabled: bool,
ignored_users: Vec<String>, allowed_users: Vec<String>,
blocked_users: Vec<String>, ignored_users: Vec<String>,
allowed_servers: Vec<String>, blocked_users: Vec<String>,
ignored_servers: Vec<String>, allowed_servers: Vec<String>,
blocked_servers: Vec<String>, ignored_servers: Vec<String>,
) -> Self { blocked_servers: Vec<String>,
Self { ) -> Self {
enabled, Self {
allowed_users, enabled,
ignored_users, allowed_users,
blocked_users, ignored_users,
allowed_servers, blocked_users,
ignored_servers, allowed_servers,
blocked_servers, ignored_servers,
} blocked_servers,
} }
}
/// Test the filters against a user id. This function will check both the /// Test the filters against a user id. This function will check both the
/// user rules _and_ the server rules. /// user rules _and_ the server rules.
#[must_use] #[must_use]
#[allow(clippy::if_same_then_else)] #[allow(clippy::if_same_then_else)]
pub fn user_filter_level(&self, user: &UserId) -> FilterLevel { pub fn user_filter_level(&self, user: &UserId) -> FilterLevel {
if !self.enabled { if !self.enabled {
FilterLevel::Allow FilterLevel::Allow
} else if Self::matches(&self.allowed_users, user.as_str()) { } else if Self::matches(&self.allowed_users, user.as_str()) {
FilterLevel::Allow FilterLevel::Allow
} else if Self::matches(&self.ignored_users, user.as_str()) { } else if Self::matches(&self.ignored_users, user.as_str()) {
FilterLevel::Ignore FilterLevel::Ignore
} else if Self::matches(&self.blocked_users, user.as_str()) { } else if Self::matches(&self.blocked_users, user.as_str()) {
FilterLevel::Block FilterLevel::Block
} else { } else {
self.server_filter_level(user.server_name()) self.server_filter_level(user.server_name())
} }
} }
/// Test the filters against a server name. Port numbers are ignored. /// Test the filters against a server name. Port numbers are ignored.
#[must_use] #[must_use]
pub fn server_filter_level(&self, server: &ServerName) -> FilterLevel { pub fn server_filter_level(&self, server: &ServerName) -> FilterLevel {
if !self.enabled { if !self.enabled {
FilterLevel::Allow FilterLevel::Allow
} else { } else {
let server = server.host(); let server = server.host();
if Self::matches(&self.allowed_servers, server) { if Self::matches(&self.allowed_servers, server) {
FilterLevel::Allow FilterLevel::Allow
} else if Self::matches(&self.ignored_servers, server) { } else if Self::matches(&self.ignored_servers, server) {
FilterLevel::Ignore FilterLevel::Ignore
} else if Self::matches(&self.blocked_servers, server) { } else if Self::matches(&self.blocked_servers, server) {
FilterLevel::Block FilterLevel::Block
} else { } else {
FilterLevel::Allow FilterLevel::Allow
} }
} }
} }
fn matches(a: &[String], s: &str) -> bool { fn matches(a: &[String], s: &str) -> bool {
a.iter().map(String::as_str).any(|a| WildMatch::new(a).matches(s)) a.iter()
} .map(String::as_str)
.any(|a| WildMatch::new(a).matches(s))
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use ruma::{ServerName, UserId, events::GlobalAccountDataEvent}; use ruma::{ServerName, UserId, events::GlobalAccountDataEvent};
use serde_json::{from_value as from_json_value, json}; use serde_json::{from_value as from_json_value, json};
use crate::invite_permission_config::{FilterLevel, InvitePermissionConfigEventContent}; use crate::invite_permission_config::{FilterLevel, InvitePermissionConfigEventContent};
fn user_id(id: &str) -> &UserId { fn user_id(id: &str) -> &UserId { <&UserId>::try_from(id).unwrap() }
<&UserId>::try_from(id).unwrap()
}
fn server_name(name: &str) -> &ServerName { fn server_name(name: &str) -> &ServerName { <&ServerName>::try_from(name).unwrap() }
<&ServerName>::try_from(name).unwrap()
}
#[test] #[test]
fn default_values() { fn default_values() {
let data = json!({ let data = json!({
"content": {}, "content": {},
"type": "org.matrix.msc4155.invite_permission_config" "type": "org.matrix.msc4155.invite_permission_config"
}); });
let event: GlobalAccountDataEvent<InvitePermissionConfigEventContent> = from_json_value(data).unwrap(); let event: GlobalAccountDataEvent<InvitePermissionConfigEventContent> =
assert!(event.content.enabled); from_json_value(data).unwrap();
assert!(event.content.allowed_users.is_empty()); assert!(event.content.enabled);
assert!(event.content.ignored_users.is_empty()); assert!(event.content.allowed_users.is_empty());
assert!(event.content.blocked_users.is_empty()); assert!(event.content.ignored_users.is_empty());
assert!(event.content.allowed_servers.is_empty()); assert!(event.content.blocked_users.is_empty());
assert!(event.content.ignored_servers.is_empty()); assert!(event.content.allowed_servers.is_empty());
assert!(event.content.blocked_servers.is_empty()); assert!(event.content.ignored_servers.is_empty());
assert_eq!(event.content.user_filter_level(user_id("@alice:example.com")), FilterLevel::Allow); assert!(event.content.blocked_servers.is_empty());
assert_eq!(event.content.server_filter_level(server_name("example.com")), FilterLevel::Allow); assert_eq!(
} event
.content
.user_filter_level(user_id("@alice:example.com")),
FilterLevel::Allow
);
assert_eq!(
event
.content
.server_filter_level(server_name("example.com")),
FilterLevel::Allow
);
}
#[test] #[test]
fn block_the_world() { fn block_the_world() {
let event = InvitePermissionConfigEventContent { let event = InvitePermissionConfigEventContent {
enabled: true, enabled: true,
blocked_servers: vec!["*".to_owned()], blocked_servers: vec!["*".to_owned()],
..Default::default() ..Default::default()
}; };
assert_eq!(event.user_filter_level(user_id("@alice:foo.com:8080")), FilterLevel::Block); assert_eq!(event.user_filter_level(user_id("@alice:foo.com:8080")), FilterLevel::Block);
assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Block); assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Block);
} }
#[test] #[test]
fn only_goodguys() { fn only_goodguys() {
let event = InvitePermissionConfigEventContent { let event = InvitePermissionConfigEventContent {
enabled: true, enabled: true,
allowed_servers: vec!["goodguys.org".to_owned()], allowed_servers: vec!["goodguys.org".to_owned()],
blocked_servers: vec!["*".to_owned()], blocked_servers: vec!["*".to_owned()],
..Default::default() ..Default::default()
}; };
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org:8080")), FilterLevel::Allow); assert_eq!(
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow); event.user_filter_level(user_id("@alice:goodguys.org:8080")),
assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Block); FilterLevel::Allow
} );
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Block);
}
#[test] #[test]
fn exclude_badguys() { fn exclude_badguys() {
let event = InvitePermissionConfigEventContent { let event = InvitePermissionConfigEventContent {
enabled: true, enabled: true,
blocked_servers: vec!["badguys.org".to_owned()], blocked_servers: vec!["badguys.org".to_owned()],
..Default::default() ..Default::default()
}; };
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow); assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Allow); assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org:8080")), FilterLevel::Block); assert_eq!(
assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org")), FilterLevel::Block); event.user_filter_level(user_id("@kevin:badguys.org:8080")),
} FilterLevel::Block
);
assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org")), FilterLevel::Block);
}
#[test] #[test]
fn only_goodguys_except_for_kevin() { fn only_goodguys_except_for_kevin() {
let event = InvitePermissionConfigEventContent { let event = InvitePermissionConfigEventContent {
enabled: true, enabled: true,
blocked_users: vec!["@kevin:goodguys.org".to_owned()], blocked_users: vec!["@kevin:goodguys.org".to_owned()],
allowed_servers: vec!["goodguys.org".to_owned()], allowed_servers: vec!["goodguys.org".to_owned()],
blocked_servers: vec!["*".to_owned()], blocked_servers: vec!["*".to_owned()],
..Default::default() ..Default::default()
}; };
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow); assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@kevin:goodguys.org")), FilterLevel::Block); assert_eq!(event.user_filter_level(user_id("@kevin:goodguys.org")), FilterLevel::Block);
assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org")), FilterLevel::Block); assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org")), FilterLevel::Block);
} }
#[test] #[test]
fn no_badguys_except_for_alice() { fn no_badguys_except_for_alice() {
let event = InvitePermissionConfigEventContent { let event = InvitePermissionConfigEventContent {
enabled: true, enabled: true,
allowed_users: vec!["@alice:badguys.org".to_owned()], allowed_users: vec!["@alice:badguys.org".to_owned()],
blocked_servers: vec!["badguys.org".to_owned()], blocked_servers: vec!["badguys.org".to_owned()],
..Default::default() ..Default::default()
}; };
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow); assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@alice:badguys.org")), FilterLevel::Allow); assert_eq!(event.user_filter_level(user_id("@alice:badguys.org")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Allow); assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org")), FilterLevel::Block); assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org")), FilterLevel::Block);
} }
#[test] #[test]
fn only_goodguys_and_ignore_reallybadguys() { fn only_goodguys_and_ignore_reallybadguys() {
let event = InvitePermissionConfigEventContent { let event = InvitePermissionConfigEventContent {
enabled: true, enabled: true,
allowed_servers: vec!["goodguys.org".to_owned()], allowed_servers: vec!["goodguys.org".to_owned()],
ignored_servers: vec!["reallybadguys.org".to_owned()], ignored_servers: vec!["reallybadguys.org".to_owned()],
blocked_servers: vec!["*".to_owned()], blocked_servers: vec!["*".to_owned()],
..Default::default() ..Default::default()
}; };
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org:8080")), FilterLevel::Allow); assert_eq!(
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow); event.user_filter_level(user_id("@alice:goodguys.org:8080")),
assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Block); FilterLevel::Allow
assert_eq!(event.user_filter_level(user_id("@kevin:reallybadguys.org")), FilterLevel::Ignore); );
} assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow);
} assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Block);
assert_eq!(
event.user_filter_level(user_id("@kevin:reallybadguys.org")),
FilterLevel::Ignore
);
}
}
@@ -7,53 +7,53 @@
//! - https://mau.dev/maunium/synapse/-/blob/52741d3/synapse/handlers/event_auth.py#L280-292 //! - https://mau.dev/maunium/synapse/-/blob/52741d3/synapse/handlers/event_auth.py#L280-292
pub mod v1 { pub mod v1 {
use ruma::{ use ruma::{
OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata OwnedRoomId, OwnedUserId,
}; api::{auth_scheme::AppserviceToken, request, response},
metadata,
};
metadata! { metadata! {
method: POST, method: POST,
rate_limited: false, rate_limited: false,
authentication: AppserviceToken, authentication: AppserviceToken,
history: { history: {
1.0 => "/_meowlnir/antispam/{management_room_id}/accept_make_join", 1.0 => "/_meowlnir/antispam/{management_room_id}/accept_make_join",
} }
} }
/// Request type for the `accept_make_join` callback. /// Request type for the `accept_make_join` callback.
#[request] #[request]
pub struct Request { pub struct Request {
/// The relevant management room /// The relevant management room
#[ruma_api(path)] #[ruma_api(path)]
pub management_room_id: OwnedRoomId, pub management_room_id: OwnedRoomId,
/// The user trying to join a room /// The user trying to join a room
pub user: OwnedUserId, pub user: OwnedUserId,
/// The room the user is trying to join /// The room the user is trying to join
pub room: OwnedRoomId, pub room: OwnedRoomId,
} }
/// Response type for the `accept_make_join` callback. /// Response type for the `accept_make_join` callback.
#[response] #[response]
#[derive(Default)] #[derive(Default)]
pub struct Response; pub struct Response;
impl Request { impl Request {
/// Creates a new empty `Request`. /// Creates a new empty `Request`.
#[must_use] #[must_use]
pub fn new( pub fn new(
management_room_id: OwnedRoomId, management_room_id: OwnedRoomId,
user: OwnedUserId, user: OwnedUserId,
room: OwnedRoomId, room: OwnedRoomId,
) -> Self { ) -> Self {
Self { management_room_id, user, room } Self { management_room_id, user, room }
} }
} }
impl Response { impl Response {
/// Creates a new empty `Response`. /// Creates a new empty `Response`.
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self { Self::default() }
Self::default() }
}
}
} }
+1 -1
View File
@@ -1,4 +1,4 @@
pub mod user_may_invite; pub mod user_may_invite;
pub mod user_may_join_room; pub mod user_may_join_room;
pub mod accept_make_join; pub mod accept_make_join;
@@ -1,58 +1,64 @@
//! `POST /_meowlnir/antispam/*/user_may_invite` //! `POST /_meowlnir/antispam/*/user_may_invite`
//! //!
//! Checks that a user may invite the given user to the given room via Meowlnir anti-spam //! Checks that a user may invite the given user to the given room via Meowlnir
//! anti-spam
pub mod v1 { pub mod v1 {
use ruma::{ use ruma::{
OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata OwnedRoomId, OwnedUserId,
}; api::{auth_scheme::AppserviceToken, request, response},
metadata,
};
metadata! { metadata! {
method: POST, method: POST,
rate_limited: false, rate_limited: false,
authentication: AppserviceToken, authentication: AppserviceToken,
history: { history: {
1.0 => "/_meowlnir/antispam/{management_room_id}/user_may_invite", 1.0 => "/_meowlnir/antispam/{management_room_id}/user_may_invite",
} }
} }
/// Request type for the `user_may_invite` callback. /// Request type for the `user_may_invite` callback.
#[request] #[request]
pub struct Request { pub struct Request {
/// The relevant management room /// The relevant management room
#[ruma_api(path)] #[ruma_api(path)]
pub management_room_id: OwnedRoomId, pub management_room_id: OwnedRoomId,
/// The user sending the invite /// The user sending the invite
pub inviter: OwnedUserId, pub inviter: OwnedUserId,
/// The user being invited /// The user being invited
pub invitee: OwnedUserId, pub invitee: OwnedUserId,
/// The room the invitee is being invited to /// The room the invitee is being invited to
pub room_id: OwnedRoomId, pub room_id: OwnedRoomId,
} }
/// Response type for the `user_may_invite` callback. /// Response type for the `user_may_invite` callback.
#[response] #[response]
#[derive(Default)] #[derive(Default)]
pub struct Response; pub struct Response;
impl Request { impl Request {
/// Creates a new empty `Request`. /// Creates a new empty `Request`.
#[must_use] #[must_use]
pub fn new( pub fn new(
management_room_id: OwnedRoomId, management_room_id: OwnedRoomId,
inviter: OwnedUserId, inviter: OwnedUserId,
invitee: OwnedUserId, invitee: OwnedUserId,
room_id: OwnedRoomId, room_id: OwnedRoomId,
) -> Self { ) -> Self {
Self { management_room_id, inviter, invitee, room_id } Self {
} management_room_id,
} inviter,
invitee,
room_id,
}
}
}
impl Response { impl Response {
/// Creates a new empty `Response`. /// Creates a new empty `Response`.
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self { Self::default() }
Self::default() }
}
}
} }
@@ -3,56 +3,61 @@
//! Endpoint to track invite joins via Meowlnir anti-spam //! Endpoint to track invite joins via Meowlnir anti-spam
pub mod v1 { pub mod v1 {
use ruma::{ use ruma::{
OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata OwnedRoomId, OwnedUserId,
}; api::{auth_scheme::AppserviceToken, request, response},
metadata,
};
metadata! { metadata! {
method: POST, method: POST,
rate_limited: false, rate_limited: false,
authentication: AppserviceToken, authentication: AppserviceToken,
history: { history: {
1.0 => "/_meowlnir/antispam/{management_room_id}/user_may_join_room", 1.0 => "/_meowlnir/antispam/{management_room_id}/user_may_join_room",
} }
} }
/// Request type for the `user_may_join_room` callback. /// Request type for the `user_may_join_room` callback.
#[request] #[request]
pub struct Request { pub struct Request {
/// The relevant management room /// The relevant management room
#[ruma_api(path)] #[ruma_api(path)]
pub management_room_id: OwnedRoomId, pub management_room_id: OwnedRoomId,
/// The user trying to join a room /// The user trying to join a room
pub user: OwnedUserId, pub user: OwnedUserId,
/// The room the user is trying to join /// The room the user is trying to join
pub room: OwnedRoomId, pub room: OwnedRoomId,
/// Whether the user was invited to this room /// Whether the user was invited to this room
pub is_invited: bool, pub is_invited: bool,
} }
/// Response type for the `user_may_join_room` callback. /// Response type for the `user_may_join_room` callback.
#[response] #[response]
#[derive(Default)] #[derive(Default)]
pub struct Response; pub struct Response;
impl Request { impl Request {
/// Creates a new empty `Request`. /// Creates a new empty `Request`.
#[must_use] #[must_use]
pub fn new( pub fn new(
management_room_id: OwnedRoomId, management_room_id: OwnedRoomId,
user: OwnedUserId, user: OwnedUserId,
room: OwnedRoomId, room: OwnedRoomId,
is_invited: bool, is_invited: bool,
) -> Self { ) -> Self {
Self { management_room_id, user, room, is_invited } Self {
} management_room_id,
} user,
room,
is_invited,
}
}
}
impl Response { impl Response {
/// Creates a new empty `Response`. /// Creates a new empty `Response`.
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self { Self::default() }
Self::default() }
}
}
} }
+1 -1
View File
@@ -2,6 +2,6 @@
pub mod admin; pub mod admin;
pub mod draupnir_antispam; pub mod draupnir_antispam;
pub mod invite_permission_config;
pub mod meowlnir_antispam; pub mod meowlnir_antispam;
pub mod policy; pub mod policy;
pub mod invite_permission_config;
+57 -66
View File
@@ -2,99 +2,90 @@
//! //!
//! [`org.matrix.msc4284.policy`]: https://github.com/matrix-org/matrix-spec-proposals/pull/4284 //! [`org.matrix.msc4284.policy`]: https://github.com/matrix-org/matrix-spec-proposals/pull/4284
use ruma::exports::ruma_macros::EventContent; use ruma::{events::EmptyStateKey, exports::ruma_macros::EventContent};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ruma::events::EmptyStateKey;
#[derive(Clone, Debug, Deserialize, Serialize, EventContent, Default)] #[derive(Clone, Debug, Deserialize, Serialize, EventContent, Default)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "org.matrix.msc4284.policy", kind = State, state_key_type = EmptyStateKey)] #[ruma_event(type = "org.matrix.msc4284.policy", kind = State, state_key_type = EmptyStateKey)]
pub struct RoomPolicyEventContent { pub struct RoomPolicyEventContent {
/// The server name of the room's policy server. /// The server name of the room's policy server.
/// ///
/// If the value is empty or unreachable, the policy server should be ignored. /// If the value is empty or unreachable, the policy server should be
pub via: Option<String>, /// ignored.
/// The public key this policy server will sign with. pub via: Option<String>,
pub public_key: Option<String> /// The public key this policy server will sign with.
pub public_key: Option<String>,
} }
impl RoomPolicyEventContent { impl RoomPolicyEventContent {
/// Create an empty `RoomPolicyEventContent`. /// Create an empty `RoomPolicyEventContent`.
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self { Self::default() }
Self::default()
}
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct PolicyServerResponseContent { pub struct PolicyServerResponseContent {
/// The policy server's verdict. Either `ok` or `spam`. /// The policy server's verdict. Either `ok` or `spam`.
pub recommendation: String, pub recommendation: String,
} }
impl PolicyServerResponseContent { impl PolicyServerResponseContent {
/// Create a new `PolicyServerResponseContent` with the given recommendation. /// Create a new `PolicyServerResponseContent` with the given
#[must_use] /// recommendation.
pub fn new(recommendation: String) -> Self { #[must_use]
Self { recommendation } pub fn new(recommendation: String) -> Self { Self { recommendation } }
}
} }
impl From<String> for PolicyServerResponseContent { impl From<String> for PolicyServerResponseContent {
fn from(recommendation: String) -> Self { fn from(recommendation: String) -> Self { Self::new(recommendation) }
Self::new(recommendation)
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; use ruma::events::OriginalStateEvent;
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
use super::RoomPolicyEventContent; use super::RoomPolicyEventContent;
use ruma::events::OriginalStateEvent;
#[test] #[test]
fn serialization() { fn serialization() {
let content = RoomPolicyEventContent { let content = RoomPolicyEventContent {
via: Some("example.com".to_owned()), via: Some("example.com".to_owned()),
public_key: Some("6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s".to_owned()) public_key: Some("6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s".to_owned()),
}; };
let actual = to_json_value(content).unwrap(); let actual = to_json_value(content).unwrap();
let expected = json!({ let expected = json!({
"via": "example.com", "via": "example.com",
"public_key": "6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s" "public_key": "6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s"
}); });
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
#[test] #[test]
fn deserialization() { fn deserialization() {
let json_data = json!({ let json_data = json!({
"content": { "content": {
"via": "example.com", "via": "example.com",
"public_key": "6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s" "public_key": "6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s"
}, },
"event_id": "123:example.com", "event_id": "123:example.com",
"origin_server_ts": 1, "origin_server_ts": 1,
"room_id": "!123456:example.com", "room_id": "!123456:example.com",
"sender": "@carl:example.com", "sender": "@carl:example.com",
"state_key": "", "state_key": "",
"type": "org.matrix.msc4284.policy" "type": "org.matrix.msc4284.policy"
}); });
let content = from_json_value::<OriginalStateEvent<RoomPolicyEventContent>>(json_data) let content = from_json_value::<OriginalStateEvent<RoomPolicyEventContent>>(json_data)
.unwrap() .unwrap()
.content; .content;
assert_eq!( assert_eq!(content.via, Some("example.com".to_owned()));
content.via, assert_eq!(
Some("example.com".to_owned()) content.public_key,
); Some("6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s".to_owned())
assert_eq!( );
content.public_key, }
Some("6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s".to_owned())
);
}
} }
+1 -1
View File
@@ -1,4 +1,4 @@
pub mod event;
pub mod policy_check; pub mod policy_check;
pub mod policy_sign; pub mod policy_sign;
pub mod report_content; pub mod report_content;
pub mod event;
+45 -46
View File
@@ -4,57 +4,56 @@
//! This is now a fallback behaviour that will be removed later. //! This is now a fallback behaviour that will be removed later.
pub mod unstable { pub mod unstable {
//! `/policy/unstable/org.matrix.msc4284` ([spec]) //! `/policy/unstable/org.matrix.msc4284` ([spec])
//! //!
//! [spec]: https://github.com/matrix-org/matrix-spec-proposals/pull/4284 //! [spec]: https://github.com/matrix-org/matrix-spec-proposals/pull/4284
use ruma::{ use ruma::{
OwnedEventId, api::{federation::authentication::ServerSignatures, request, response}, metadata OwnedEventId,
}; api::{federation::authentication::ServerSignatures, request, response},
use serde_json::value::RawValue as RawJsonValue; metadata,
};
use serde_json::value::RawValue as RawJsonValue;
metadata! { metadata! {
method: POST, method: POST,
rate_limited: false, rate_limited: false,
authentication: ServerSignatures, authentication: ServerSignatures,
history: { history: {
unstable => "/_matrix/policy/unstable/org.matrix.msc4284/event/{event_id}/check", unstable => "/_matrix/policy/unstable/org.matrix.msc4284/event/{event_id}/check",
} }
} }
/// Response type for the `check` endpoint. /// Response type for the `check` endpoint.
#[response] #[response]
pub struct Response { pub struct Response {
/// Either `ok` or `spam`, indicating the policy server's recommendation. /// Either `ok` or `spam`, indicating the policy server's
pub recommendation: String, /// recommendation.
} pub recommendation: String,
}
impl Response { impl Response {
/// Creates a new `Response` with the given recommendation. /// Creates a new `Response` with the given recommendation.
#[must_use] #[must_use]
pub fn new(recommendation: String) -> Self { pub fn new(recommendation: String) -> Self { Self { recommendation } }
Self { recommendation } }
}
}
/// Request type for the `check` endpoint. /// Request type for the `check` endpoint.
#[request] #[request]
pub struct Request { pub struct Request {
/// The event ID to check. /// The event ID to check.
#[ruma_api(path)] #[ruma_api(path)]
pub event_id: OwnedEventId, pub event_id: OwnedEventId,
/// The PDU body (optional) /// The PDU body (optional)
#[ruma_api(body)] #[ruma_api(body)]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub pdu: Option<Box<RawJsonValue>>, pub pdu: Option<Box<RawJsonValue>>,
} }
impl Request { impl Request {
/// Creates a new `Request` with the given event ID. /// Creates a new `Request` with the given event ID.
#[must_use] #[must_use]
pub fn new(event_id: OwnedEventId) -> Self { pub fn new(event_id: OwnedEventId) -> Self { Self { event_id, pdu: None } }
Self { event_id, pdu: None } }
}
}
} }
+45 -45
View File
@@ -3,53 +3,53 @@
//! Asks a policy server to sign our event //! Asks a policy server to sign our event
pub mod unstable { pub mod unstable {
//! `/policy/unstable/org.matrix.msc4284` ([spec]) //! `/policy/unstable/org.matrix.msc4284` ([spec])
//! //!
//! [spec]: https://github.com/matrix-org/matrix-spec-proposals/pull/4284 //! [spec]: https://github.com/matrix-org/matrix-spec-proposals/pull/4284
use ruma::{ use ruma::{
ServerSignatures, ServerSignatures,
api::{federation::authentication::ServerSignatures as ServerSignaturesAuth, request, response}, metadata api::{
}; federation::authentication::ServerSignatures as ServerSignaturesAuth, request,
use serde_json::value::RawValue as RawJsonValue; response,
},
metadata,
};
use serde_json::value::RawValue as RawJsonValue;
metadata! { metadata! {
method: POST, method: POST,
rate_limited: false, rate_limited: false,
authentication: ServerSignaturesAuth, authentication: ServerSignaturesAuth,
history: { history: {
unstable => "/_matrix/policy/unstable/org.matrix.msc4284/sign", unstable => "/_matrix/policy/unstable/org.matrix.msc4284/sign",
} }
} }
/// Response type for the `sign` endpoint. /// Response type for the `sign` endpoint.
#[response] #[response]
pub struct Response { pub struct Response {
/// The signatures returned from the policy server (if provided) /// The signatures returned from the policy server (if provided)
#[ruma_api(body)] #[ruma_api(body)]
pub signatures: Option<ServerSignatures> pub signatures: Option<ServerSignatures>,
} }
impl Response { impl Response {
/// Creates a new `Response` with the given recommendation. /// Creates a new `Response` with the given recommendation.
#[must_use] #[must_use]
pub fn new(signatures: Option<ServerSignatures>) -> Self { pub fn new(signatures: Option<ServerSignatures>) -> Self { Self { signatures } }
Self { signatures } }
}
}
/// Request type for the `sign` endpoint. /// Request type for the `sign` endpoint.
#[request] #[request]
pub struct Request { pub struct Request {
/// The PDU body (in canonical JSON) /// The PDU body (in canonical JSON)
#[ruma_api(body)] #[ruma_api(body)]
pub pdu: Box<RawJsonValue>, pub pdu: Box<RawJsonValue>,
} }
impl Request { impl Request {
/// Creates a new `Request` with the given event JSON /// Creates a new `Request` with the given event JSON
#[must_use] #[must_use]
pub fn new(pdu: Box<RawJsonValue>) -> Self { pub fn new(pdu: Box<RawJsonValue>) -> Self { Self { pdu } }
Self { pdu } }
} }
}
}
+44 -44
View File
@@ -3,56 +3,56 @@
//! Send a request to report an event originating from another server. //! Send a request to report an event originating from another server.
pub mod msc3843 { pub mod msc3843 {
//! `MSC3843` ([MSC]) //! `MSC3843` ([MSC])
//! //!
//! [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/3843 //! [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/3843
use ruma::{ use ruma::{
OwnedEventId, OwnedRoomId, api::{federation::authentication::ServerSignatures, request, response}, metadata OwnedEventId, OwnedRoomId,
}; api::{federation::authentication::ServerSignatures, request, response},
metadata,
};
metadata! { metadata! {
method: POST, method: POST,
rate_limited: false, rate_limited: false,
authentication: ServerSignatures, authentication: ServerSignatures,
history: { history: {
unstable => "/_matrix/federation/unstable/org.matrix.msc3843/rooms/{room_id}/report/{event_id}", unstable => "/_matrix/federation/unstable/org.matrix.msc3843/rooms/{room_id}/report/{event_id}",
} }
} }
/// Request type for the `report_content` endpoint. /// Request type for the `report_content` endpoint.
#[request] #[request]
pub struct Request { pub struct Request {
/// The room ID that the reported event was sent in. /// The room ID that the reported event was sent in.
#[ruma_api(path)] #[ruma_api(path)]
pub room_id: OwnedRoomId, pub room_id: OwnedRoomId,
/// The event being reported. /// The event being reported.
#[ruma_api(path)] #[ruma_api(path)]
pub event_id: OwnedEventId, pub event_id: OwnedEventId,
/// The reason that the event is being reported. /// The reason that the event is being reported.
pub reason: String, pub reason: String,
} }
/// Response type for the `report_content` endpoint. /// Response type for the `report_content` endpoint.
#[response] #[response]
#[derive(Default)] #[derive(Default)]
pub struct Response; pub struct Response;
impl Request { impl Request {
/// Creates a `Request` with the given room ID, event ID and reason. /// Creates a `Request` with the given room ID, event ID and reason.
#[must_use] #[must_use]
pub fn new(room_id: OwnedRoomId, event_id: OwnedEventId, reason: String) -> Self { pub fn new(room_id: OwnedRoomId, event_id: OwnedEventId, reason: String) -> Self {
Self { room_id, event_id, reason } Self { room_id, event_id, reason }
} }
} }
impl Response { impl Response {
/// Creates a new empty `Response`. /// Creates a new empty `Response`.
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self { Self::default() }
Self::default() }
}
}
} }
+1
View File
@@ -107,6 +107,7 @@ nonzero_ext.workspace = true
rand.workspace = true rand.workspace = true
regex.workspace = true regex.workspace = true
reqwest.workspace = true reqwest.workspace = true
assign.workspace = true
ruma.workspace = true ruma.workspace = true
ruminuwuity.workspace = true ruminuwuity.workspace = true
rustyline-async.workspace = true rustyline-async.workspace = true
+9 -5
View File
@@ -7,10 +7,12 @@ use conduwuit::{
use database::{Deserialized, Handle, Ignore, Json, Map}; use database::{Deserialized, Handle, Ignore, Json, Map};
use futures::{Stream, StreamExt, TryFutureExt}; use futures::{Stream, StreamExt, TryFutureExt};
use ruma::{ use ruma::{
OwnedRoomId, OwnedUserId, RoomId, UserId, events::{ OwnedRoomId, OwnedUserId, RoomId, UserId,
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, events::{
GlobalAccountDataEventType, RoomAccountDataEventType, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, GlobalAccountDataEventType,
}, serde::Raw RoomAccountDataEventType,
},
serde::Raw,
}; };
use serde::Deserialize; use serde::Deserialize;
@@ -147,7 +149,9 @@ pub fn changes_since<'a>(
.stream_from(&first_possible) .stream_from(&first_possible)
.ignore_err() .ignore_err()
.ready_take_while(move |((room_id_, user_id_, count, _), _): &(Key, _)| { .ready_take_while(move |((room_id_, user_id_, count, _), _): &(Key, _)| {
room_id == room_id_.as_deref() && user_id == user_id_ && to.is_none_or(|to| *count <= to) room_id == room_id_.as_deref()
&& user_id == user_id_
&& to.is_none_or(|to| *count <= to)
}) })
.map(move |(_, v)| { .map(move |(_, v)| {
match room_id { match room_id {
+2 -1
View File
@@ -85,7 +85,8 @@ pub async fn create_admin_room(services: &Services) -> Result {
// 3. Power levels // 3. Power levels
let users = BTreeMap::from_iter([(server_user.into(), 69420.into())]); let users = BTreeMap::from_iter([(server_user.into(), 69420.into())]);
let mut power_levels_content = RoomPowerLevelsEventContent::new(&room_version.rules().unwrap().authorization); let mut power_levels_content =
RoomPowerLevelsEventContent::new(&room_version.rules().unwrap().authorization);
power_levels_content.users = users; power_levels_content.users = users;
services services
+14 -7
View File
@@ -30,7 +30,11 @@ use ruma::{
}; };
use tokio::sync::RwLock; use tokio::sync::RwLock;
use crate::{Dep, account_data, globals, media::{MXC_LENGTH, mxc::Mxc}, rooms::{self, state::RoomMutexGuard}}; use crate::{
Dep, account_data, globals,
media::{MXC_LENGTH, mxc::Mxc},
rooms::{self, state::RoomMutexGuard},
};
pub struct Service { pub struct Service {
services: Services, services: Services,
@@ -61,7 +65,7 @@ pub struct CommandInput {
pub command: String, pub command: String,
pub reply_id: Option<OwnedEventId>, pub reply_id: Option<OwnedEventId>,
pub source: InvocationSource, pub source: InvocationSource,
pub sender: Option<Box<UserId>>, pub sender: Option<OwnedUserId>,
} }
/// Where a command is being invoked from. /// Where a command is being invoked from.
@@ -205,7 +209,10 @@ impl Service {
metadata.mimetype = Some("text/markdown".to_owned()); metadata.mimetype = Some("text/markdown".to_owned());
metadata.size = Some(UInt::new_saturating(size_u64)); metadata.size = Some(UInt::new_saturating(size_u64));
let mut content = FileMessageEventContent::plain("Output was too large to send as text.".to_owned(), file); let mut content = FileMessageEventContent::plain(
"Output was too large to send as text.".to_owned(),
file,
);
content.filename = Some("output.md".to_owned()); content.filename = Some("output.md".to_owned());
content.info = Some(Box::new(metadata)); content.info = Some(Box::new(metadata));
@@ -313,7 +320,7 @@ impl Service {
command: String, command: String,
reply_id: Option<OwnedEventId>, reply_id: Option<OwnedEventId>,
source: InvocationSource, source: InvocationSource,
sender: Box<UserId>, sender: OwnedUserId,
) -> Result<()> { ) -> Result<()> {
self.channel self.channel
.0 .0
@@ -448,13 +455,13 @@ impl Service {
} }
async fn handle_response(&self, content: RoomMessageEventContent) -> Result<()> { async fn handle_response(&self, content: RoomMessageEventContent) -> Result<()> {
let Some(Relation::Reply { in_reply_to }) = content.relates_to.as_ref() else { let Some(Relation::Reply(reply )) = content.relates_to.as_ref() else {
return Ok(()); return Ok(());
}; };
let Ok(pdu) = self.services.timeline.get_pdu(&in_reply_to.event_id).await else { let Ok(pdu) = self.services.timeline.get_pdu(&reply.in_reply_to.event_id).await else {
error!( error!(
event_id = ?in_reply_to.event_id, event_id = ?reply.in_reply_to.event_id,
"Missing admin command in_reply_to event" "Missing admin command in_reply_to event"
); );
return Ok(()); return Ok(());
+9 -2
View File
@@ -2,7 +2,10 @@ use std::{fmt::Debug, sync::Arc};
use async_trait::async_trait; use async_trait::async_trait;
use conduwuit::{Result, config::Antispam, debug}; use conduwuit::{Result, config::Antispam, debug};
use ruma::{OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, path_builder::VersionHistory}}; use ruma::{
OwnedRoomId, OwnedUserId,
api::{auth_scheme::AppserviceToken, path_builder::VersionHistory},
};
use ruminuwuity::{draupnir_antispam, meowlnir_antispam}; use ruminuwuity::{draupnir_antispam, meowlnir_antispam};
use crate::{client, config, sending, service::Dep}; use crate::{client, config, sending, service::Dep};
@@ -38,7 +41,11 @@ impl Service {
request: T, request: T,
) -> Result<T::IncomingResponse> ) -> Result<T::IncomingResponse>
where where
T: ruma::api::OutgoingRequest<Authentication = AppserviceToken, PathBuilder = VersionHistory> + Debug + Send, T: ruma::api::OutgoingRequest<
Authentication = AppserviceToken,
PathBuilder = VersionHistory,
> + Debug
+ Send,
{ {
sending::antispam::send_antispam_request( sending::antispam::send_antispam_request(
&self.services.client.appservice, &self.services.client.appservice,
+3 -1
View File
@@ -72,7 +72,9 @@ impl Service {
None, None,
server_user, server_user,
GlobalAccountDataEventType::PushRules.to_string().into(), GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(&GlobalAccountDataEvent::new(PushRulesEventContent::new(ruleset))) &serde_json::to_value(&GlobalAccountDataEvent::new(PushRulesEventContent::new(
ruleset,
)))
.expect("to json value always works"), .expect("to json value always works"),
) )
.await?; .await?;
+74 -43
View File
@@ -2,13 +2,23 @@ use std::{any::Any, borrow::Cow, fmt::Debug, mem, sync::LazyLock};
use bytes::Bytes; use bytes::Bytes;
use conduwuit::{ use conduwuit::{
Err, Error, Result, debug, debug_error, debug_warn, err, implement, trace, utils::response::LimitReadExt, matrix::versions::{unstable_features, versions}, }; Err, Error, Result, debug, debug_error, debug_warn, err, implement,
matrix::versions::{unstable_features, versions},
trace,
utils::response::LimitReadExt,
};
use ipaddress::IPAddress; use ipaddress::IPAddress;
use reqwest::{Client, Method, Request, Response, Url}; use reqwest::{Client, Method, Request, Response, Url};
use ruma::{ use ruma::{
CanonicalJsonObject, CanonicalJsonValue, ServerName, ServerSigningKeyId, api::{ CanonicalJsonObject, CanonicalJsonValue, ServerName, ServerSigningKeyId,
EndpointError, IncomingResponse, Metadata, OutgoingRequest, SupportedVersions, auth_scheme::{AuthScheme, NoAuthentication, SendAccessToken}, client::error::Error as RumaError, federation::authentication::{ServerSignatures, ServerSignaturesInput, XMatrix}, path_builder::{PathBuilder, SinglePath, VersionHistory} api::{
}, serde::Base64 EndpointError, IncomingResponse, Metadata, OutgoingRequest, SupportedVersions,
auth_scheme::{AuthScheme, NoAuthentication, SendAccessToken},
error::Error as RumaError,
federation::authentication::{ServerSignatures, ServerSignaturesInput, XMatrix},
path_builder::{PathBuilder, SinglePath, VersionHistory},
},
serde::Base64,
}; };
use crate::{SUPPORTED_VERSIONS, resolver::actual::ActualDest}; use crate::{SUPPORTED_VERSIONS, resolver::actual::ActualDest};
@@ -18,7 +28,11 @@ use crate::{SUPPORTED_VERSIONS, resolver::actual::ActualDest};
#[tracing::instrument(skip_all, name = "request", level = "debug")] #[tracing::instrument(skip_all, name = "request", level = "debug")]
pub async fn execute<'i, T>(&self, dest: &ServerName, request: T) -> Result<T::IncomingResponse> pub async fn execute<'i, T>(&self, dest: &ServerName, request: T) -> Result<T::IncomingResponse>
where where
T: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send, T: OutgoingRequest<
Authentication = ServerSignatures,
PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>,
> + Debug
+ Send,
{ {
let client = &self.services.client.federation; let client = &self.services.client.federation;
self.execute_signed(client, dest, request).await self.execute_signed(client, dest, request).await
@@ -33,27 +47,46 @@ pub async fn execute_synapse<'i, T>(
request: T, request: T,
) -> Result<T::IncomingResponse> ) -> Result<T::IncomingResponse>
where where
T: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send, T: OutgoingRequest<
Authentication = ServerSignatures,
PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>,
> + Debug
+ Send,
{ {
let client = &self.services.client.synapse; let client = &self.services.client.synapse;
self.execute_signed(client, dest, request).await self.execute_signed(client, dest, request).await
} }
#[implement(super::Service)] #[implement(super::Service)]
pub async fn execute_unauthenticated<'i, T>(&self, dest: &ServerName, request: T) -> Result<T::IncomingResponse> pub async fn execute_unauthenticated<'i, T>(
where &self,
T: OutgoingRequest::<Authentication = NoAuthentication, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send, dest: &ServerName,
request: T,
) -> Result<T::IncomingResponse>
where
T: OutgoingRequest<
Authentication = NoAuthentication,
PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>,
> + Debug
+ Send,
{ {
let client = &self.services.client.federation; let client = &self.services.client.federation;
let authentication = SendAccessToken::None;
self.execute_on(client, dest, request, ()).await
self.execute_on(client, dest, request, authentication).await
} }
#[implement(super::Service)] #[implement(super::Service)]
pub async fn execute_signed<'i, T>(&self, client: &Client, dest: &ServerName, request: T) -> Result<T::IncomingResponse> pub async fn execute_signed<'i, T>(
&self,
client: &Client,
dest: &ServerName,
request: T,
) -> Result<T::IncomingResponse>
where where
T: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Send, T: OutgoingRequest<
Authentication = ServerSignatures,
PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>,
> + Send,
{ {
let authentication = ServerSignaturesInput::new( let authentication = ServerSignaturesInput::new(
self.services.server.name.clone(), self.services.server.name.clone(),
@@ -65,11 +98,7 @@ where
} }
#[implement(super::Service)] #[implement(super::Service)]
#[tracing::instrument( #[tracing::instrument(name = "fed", level = "info", skip(self, client, request, authentication))]
name = "fed",
level = "info",
skip(self, client, request, authentication),
)]
pub async fn execute_on<'i, T, PathBuilderInput>( pub async fn execute_on<'i, T, PathBuilderInput>(
&self, &self,
client: &Client, client: &Client,
@@ -78,8 +107,8 @@ pub async fn execute_on<'i, T, PathBuilderInput>(
authentication: <T::Authentication as AuthScheme>::Input<'_>, authentication: <T::Authentication as AuthScheme>::Input<'_>,
) -> Result<T::IncomingResponse> ) -> Result<T::IncomingResponse>
where where
T: OutgoingRequest::<PathBuilder: PathBuilder<Input<'i> = PathBuilderInput>> + Send, T: OutgoingRequest<PathBuilder: PathBuilder<Input<'i> = PathBuilderInput>> + Send,
PathBuilderInput: FederationPathBuilderInput PathBuilderInput: FederationPathBuilderInput,
{ {
if !self.services.server.config.allow_federation { if !self.services.server.config.allow_federation {
return Err!(Config("allow_federation", "Federation is disabled.")); return Err!(Config("allow_federation", "Federation is disabled."));
@@ -90,14 +119,12 @@ where
} }
let actual = self.services.resolver.get_actual_dest(dest).await?; let actual = self.services.resolver.get_actual_dest(dest).await?;
let request = Request::try_from( let request = Request::try_from(request.try_into_http_request::<Vec<u8>>(
request.try_into_http_request::<Vec<u8>>( actual.string().as_str(),
actual.string().as_str(), authentication,
authentication, PathBuilderInput::create(),
PathBuilderInput::create(), )?)?;
)?
)?;
self.validate_url(request.url())?; self.validate_url(request.url())?;
self.services.server.check_running()?; self.services.server.check_running()?;
@@ -248,17 +275,23 @@ fn handle_error(
Err(e.into()) Err(e.into())
} }
/// A trait for the input types of acceptable path builders for outgoing federation requests. /// A trait for the input types of acceptable path builders for outgoing
/// /// federation requests.
/// Ruma uses Rust's type system to encode the versioning scheme of endpoints in the Matrix spec. ///
/// Every endpoint has a `PathBuilder` associated type, which has an `Input` associated type. /// Ruma uses Rust's type system to encode the versioning scheme of endpoints in
/// Endpoints with multiple versions have `VersionHistory` as their `PathBuilder`, which has `SupportedVersions` /// the Matrix spec. Every endpoint has a `PathBuilder` associated type, which
/// as its `Input` type. Endpoints with no version have `SinglePath` as their `PathBuilder`, which has `()` as its `Input` type. /// has an `Input` associated type. Endpoints with multiple versions have
/// Both `SupportedVersions` and `()` can be created out of thin air using static data (or no data at all). This property /// `VersionHistory` as their `PathBuilder`, which has `SupportedVersions`
/// is what the `FederationPathBuilderInput` trait represents. /// as its `Input` type. Endpoints with no version have `SinglePath` as their
/// /// `PathBuilder`, which has `()` as its `Input` type. Both `SupportedVersions`
/// This trait allows the federation sender service's functions to accept requests for either versioned or unversioned endpoints, /// and `()` can be created out of thin air using static data (or no data at
/// by requiring that the `Input` of the `PathBuilder` of the endpoint implements `FederationPathBuilderInput`. /// all). This property is what the `FederationPathBuilderInput` trait
/// represents.
///
/// This trait allows the federation sender service's functions to accept
/// requests for either versioned or unversioned endpoints, by requiring that
/// the `Input` of the `PathBuilder` of the endpoint implements
/// `FederationPathBuilderInput`.
pub(crate) trait FederationPathBuilderInput { pub(crate) trait FederationPathBuilderInput {
fn create() -> Self; fn create() -> Self;
} }
@@ -268,7 +301,5 @@ impl FederationPathBuilderInput for () {
} }
impl FederationPathBuilderInput for Cow<'_, SupportedVersions> { impl FederationPathBuilderInput for Cow<'_, SupportedVersions> {
fn create() -> Self { fn create() -> Self { Cow::Borrowed(&SUPPORTED_VERSIONS) }
Cow::Borrowed(&SUPPORTED_VERSIONS)
}
} }
+1 -2
View File
@@ -1,9 +1,8 @@
mod execute; mod execute;
pub(crate) use execute::FederationPathBuilderInput;
use std::sync::Arc; use std::sync::Arc;
use conduwuit::{Result, Server}; use conduwuit::{Result, Server};
pub(crate) use execute::FederationPathBuilderInput;
use crate::{Dep, client, moderation, resolver, server_keys}; use crate::{Dep, client, moderation, resolver, server_keys};
+3 -1
View File
@@ -7,7 +7,9 @@ use conduwuit::{
use database::{Deserialized, Ignore, Interfix, Json, Map}; use database::{Deserialized, Ignore, Interfix, Json, Map};
use futures::StreamExt; use futures::StreamExt;
use ruma::{ use ruma::{
OwnedRoomId, OwnedUserId, RoomId, UserId, api::client::backup::{BackupAlgorithm, KeyBackupData, RoomKeyBackup}, serde::Raw OwnedRoomId, OwnedUserId, RoomId, UserId,
api::client::backup::{BackupAlgorithm, KeyBackupData, RoomKeyBackup},
serde::Raw,
}; };
use crate::{Dep, globals}; use crate::{Dep, globals};
+1 -2
View File
@@ -8,9 +8,8 @@ use database::{Database, Interfix, Map};
use futures::StreamExt; use futures::StreamExt;
use ruma::{OwnedMxcUri, OwnedUserId, UserId, http_headers::ContentDisposition}; use ruma::{OwnedMxcUri, OwnedUserId, UserId, http_headers::ContentDisposition};
use crate::media::mxc::Mxc;
use super::{preview::UrlPreviewData, thumbnail::Dim}; use super::{preview::UrlPreviewData, thumbnail::Dim};
use crate::media::mxc::Mxc;
pub(crate) struct Data { pub(crate) struct Data {
mediaid_file: Arc<Map>, mediaid_file: Arc<Map>,
+1 -1
View File
@@ -1,7 +1,7 @@
pub mod blurhash; pub mod blurhash;
pub mod mxc;
mod data; mod data;
pub(super) mod migrations; pub(super) mod migrations;
pub mod mxc;
mod preview; mod preview;
mod remote; mod remote;
mod tests; mod tests;
+26 -26
View File
@@ -6,49 +6,49 @@ use serde::{Serialize, Serializer};
/// A structured, valid MXC URI /// A structured, valid MXC URI
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Mxc<'a> { pub struct Mxc<'a> {
/// ServerName part of the MXC URI /// ServerName part of the MXC URI
pub server_name: &'a ServerName, pub server_name: &'a ServerName,
/// MediaId part of the MXC URI /// MediaId part of the MXC URI
pub media_id: &'a str, pub media_id: &'a str,
} }
impl fmt::Display for Mxc<'_> { impl fmt::Display for Mxc<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "mxc://{}/{}", self.server_name, self.media_id) write!(f, "mxc://{}/{}", self.server_name, self.media_id)
} }
} }
impl<'a> TryFrom<&'a MxcUri> for Mxc<'a> { impl<'a> TryFrom<&'a MxcUri> for Mxc<'a> {
type Error = MxcUriError; type Error = MxcUriError;
fn try_from(s: &'a MxcUri) -> Result<Self, Self::Error> { fn try_from(s: &'a MxcUri) -> Result<Self, Self::Error> {
let (server_name, media_id) = s.parts()?; let (server_name, media_id) = s.parts()?;
Ok(Self { server_name, media_id }) Ok(Self { server_name, media_id })
} }
} }
impl<'a> TryFrom<&'a str> for Mxc<'a> { impl<'a> TryFrom<&'a str> for Mxc<'a> {
type Error = MxcUriError; type Error = MxcUriError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> { fn try_from(s: &'a str) -> Result<Self, Self::Error> {
let s: &MxcUri = s.into(); let s: &MxcUri = s.into();
s.try_into() s.try_into()
} }
} }
impl<'a> TryFrom<&'a OwnedMxcUri> for Mxc<'a> { impl<'a> TryFrom<&'a OwnedMxcUri> for Mxc<'a> {
type Error = MxcUriError; type Error = MxcUriError;
fn try_from(s: &'a OwnedMxcUri) -> Result<Self, Self::Error> { fn try_from(s: &'a OwnedMxcUri) -> Result<Self, Self::Error> {
let s: &MxcUri = s.as_ref(); let s: &MxcUri = s.as_ref();
s.try_into() s.try_into()
} }
} }
impl Serialize for Mxc<'_> { impl Serialize for Mxc<'_> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> { fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.to_string().as_str()) s.serialize_str(self.to_string().as_str())
} }
} }
+48 -19
View File
@@ -6,17 +6,23 @@ use conduwuit::{
}; };
use http::header::{CONTENT_DISPOSITION, CONTENT_TYPE, HeaderValue}; use http::header::{CONTENT_DISPOSITION, CONTENT_TYPE, HeaderValue};
use ruma::{ use ruma::{
ServerName, UserId, api::{ ServerName, UserId,
Metadata, OutgoingRequest, auth_scheme::NoAuthentication, client::{ api::{
error::ErrorKind::{NotFound, Unrecognized}, Metadata, OutgoingRequest,
media, auth_scheme::{NoAccessToken, NoAuthentication},
}, federation::{self, authenticated_media::{Content, FileOrLocation}, authentication::ServerSignatures}, path_builder::PathBuilder client::media,
} error::ErrorKind::{NotFound, Unrecognized},
federation::{
self,
authenticated_media::{Content, FileOrLocation},
authentication::ServerSignatures,
},
path_builder::PathBuilder,
},
}; };
use crate::{federation::FederationPathBuilderInput, media::mxc::Mxc};
use super::{Dim, FileMeta}; use super::{Dim, FileMeta};
use crate::{federation::FederationPathBuilderInput, media::mxc::Mxc};
#[implement(super::Service)] #[implement(super::Service)]
pub async fn fetch_remote_thumbnail( pub async fn fetch_remote_thumbnail(
@@ -134,7 +140,12 @@ async fn fetch_thumbnail_unauthenticated(
) -> Result<FileMeta> { ) -> Result<FileMeta> {
use media::get_content_thumbnail::v3::{Request, Response}; use media::get_content_thumbnail::v3::{Request, Response};
let mut request = Request::new(mxc.media_id.into(), mxc.server_name.into(), dim.width.into(), dim.height.into()); let mut request = Request::new(
mxc.media_id.into(),
mxc.server_name.into(),
dim.width.into(),
dim.height.into(),
);
request.allow_redirect = true; request.allow_redirect = true;
request.allow_remote = true; request.allow_remote = true;
request.animated = Some(true); request.animated = Some(true);
@@ -143,7 +154,9 @@ async fn fetch_thumbnail_unauthenticated(
let Response { let Response {
file, content_type, content_disposition, .. file, content_type, content_disposition, ..
} = self.federation_request_unauthenticated(mxc, server, request).await?; } = self
.federation_request_legacy_media(mxc, server, request)
.await?;
let content = Content::new(file, content_type.unwrap(), content_disposition.unwrap()); let content = Content::new(file, content_type.unwrap(), content_disposition.unwrap());
@@ -168,7 +181,9 @@ async fn fetch_content_unauthenticated(
let Response { let Response {
file, content_type, content_disposition, .. file, content_type, content_disposition, ..
} = self.federation_request_unauthenticated(mxc, server, request).await?; } = self
.federation_request_legacy_media(mxc, server, request)
.await?;
let content = Content::new(file, content_type.unwrap(), content_disposition.unwrap()); let content = Content::new(file, content_type.unwrap(), content_disposition.unwrap());
@@ -300,7 +315,11 @@ async fn federation_request<'i, Request>(
request: Request, request: Request,
) -> Result<Request::IncomingResponse> ) -> Result<Request::IncomingResponse>
where where
Request: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send, Request: OutgoingRequest<
Authentication = ServerSignatures,
PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>,
> + Debug
+ Send,
{ {
self.services self.services
.sending .sending
@@ -309,18 +328,22 @@ where
} }
#[implement(super::Service)] #[implement(super::Service)]
async fn federation_request_unauthenticated<'i, Request>( async fn federation_request_legacy_media<'i, Request>(
&self, &self,
mxc: &Mxc<'_>, mxc: &Mxc<'_>,
server: Option<&ServerName>, server: Option<&ServerName>,
request: Request, request: Request,
) -> Result<Request::IncomingResponse> ) -> Result<Request::IncomingResponse>
where where
Request: OutgoingRequest::<Authentication = NoAuthentication, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send, Request: OutgoingRequest<
Authentication = NoAccessToken,
PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>,
> + Debug
+ Send,
{ {
self.services self.services
.sending .sending
.send_unauthenticated_request(server.unwrap_or(mxc.server_name), request) .send_legacy_media_request(server.unwrap_or(mxc.server_name), request)
.await .await
} }
@@ -335,7 +358,12 @@ pub async fn fetch_remote_thumbnail_legacy(
media_id: &body.media_id, media_id: &body.media_id,
}; };
let mut request = media::get_content_thumbnail::v3::Request::new(body.media_id.clone(), body.server_name.clone(), body.width, body.height); let mut request = media::get_content_thumbnail::v3::Request::new(
body.media_id.clone(),
body.server_name.clone(),
body.width,
body.height,
);
request.method = body.method.clone(); request.method = body.method.clone();
request.allow_remote = body.allow_remote; request.allow_remote = body.allow_remote;
request.allow_redirect = body.allow_redirect; request.allow_redirect = body.allow_redirect;
@@ -347,7 +375,7 @@ pub async fn fetch_remote_thumbnail_legacy(
let response = self let response = self
.services .services
.sending .sending
.send_unauthenticated_request(mxc.server_name, request) .send_legacy_media_request(mxc.server_name, request)
.await?; .await?;
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?; let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
@@ -372,7 +400,8 @@ pub async fn fetch_remote_content_legacy(
allow_redirect: bool, allow_redirect: bool,
timeout_ms: Duration, timeout_ms: Duration,
) -> Result<media::get_content::v3::Response, Error> { ) -> Result<media::get_content::v3::Response, Error> {
let mut request = media::get_content::v3::Request::new(mxc.media_id.into(), mxc.server_name.into()); let mut request =
media::get_content::v3::Request::new(mxc.media_id.into(), mxc.server_name.into());
request.allow_remote = true; request.allow_remote = true;
request.allow_redirect = allow_redirect; request.allow_redirect = allow_redirect;
request.timeout_ms = timeout_ms; request.timeout_ms = timeout_ms;
@@ -382,7 +411,7 @@ pub async fn fetch_remote_content_legacy(
let response = self let response = self
.services .services
.sending .sending
.send_unauthenticated_request(mxc.server_name, request) .send_legacy_media_request(mxc.server_name, request)
.await?; .await?;
let content_disposition = make_content_disposition( let content_disposition = make_content_disposition(
+1 -2
View File
@@ -14,9 +14,8 @@ use tokio::{
io::{AsyncReadExt, AsyncWriteExt}, io::{AsyncReadExt, AsyncWriteExt},
}; };
use crate::media::mxc::Mxc;
use super::{FileMeta, data::Metadata}; use super::{FileMeta, data::Metadata};
use crate::media::mxc::Mxc;
/// Dimension specification for a thumbnail. /// Dimension specification for a thumbnail.
#[derive(Debug)] #[derive(Debug)]
+2 -13
View File
@@ -270,13 +270,7 @@ async fn migrate(services: &Services) -> Result<()> {
{ {
let patterns = services.globals.forbidden_alias_names(); let patterns = services.globals.forbidden_alias_names();
if !patterns.is_empty() { if !patterns.is_empty() {
for room_id in services for room_id in services.rooms.metadata.iter_ids().collect::<Vec<_>>().await {
.rooms
.metadata
.iter_ids()
.collect::<Vec<_>>()
.await
{
services services
.rooms .rooms
.alias .alias
@@ -479,12 +473,7 @@ async fn retroactively_fix_bad_data_from_roomuserid_joined(services: &Services)
let db = &services.db; let db = &services.db;
let _cork = db.cork_and_sync(); let _cork = db.cork_and_sync();
let room_ids = services let room_ids = services.rooms.metadata.iter_ids().collect::<Vec<_>>().await;
.rooms
.metadata
.iter_ids()
.collect::<Vec<_>>()
.await;
for room_id in &room_ids { for room_id in &room_ids {
debug_info!("Fixing room {room_id}"); debug_info!("Fixing room {room_id}");
+3 -3
View File
@@ -49,9 +49,9 @@ conduwuit::mod_ctor! {}
conduwuit::mod_dtor! {} conduwuit::mod_dtor! {}
use std::sync::LazyLock; use std::sync::LazyLock;
use conduwuit::matrix::versions::{unstable_features, versions}; use conduwuit::matrix::versions::{unstable_features, versions};
use ruma::api::SupportedVersions; use ruma::api::SupportedVersions;
pub static SUPPORTED_VERSIONS: LazyLock<SupportedVersions> = LazyLock::new(|| { pub static SUPPORTED_VERSIONS: LazyLock<SupportedVersions> =
SupportedVersions::from_parts(&versions(), &unstable_features()) LazyLock::new(|| SupportedVersions::from_parts(&versions(), &unstable_features()));
});
+1 -4
View File
@@ -193,10 +193,7 @@ impl Service {
| _ => continue, | _ => continue,
}; };
if matches!( if matches!(presence.presence, PresenceState::Offline) {
presence.presence,
PresenceState::Offline
) {
trace!(%user_id, ?presence, "Skipping user"); trace!(%user_id, ?presence, "Skipping user");
continue; continue;
} }
+1 -4
View File
@@ -54,9 +54,6 @@ impl Presence {
content.displayname = users.displayname(user_id).await.ok(); content.displayname = users.displayname(user_id).await.ok();
content.avatar_url = users.avatar_url(user_id).await.ok(); content.avatar_url = users.avatar_url(user_id).await.ok();
PresenceEvent { PresenceEvent { sender: user_id.to_owned(), content }
sender: user_id.to_owned(),
content,
}
} }
} }
+40 -13
View File
@@ -11,17 +11,30 @@ use conduwuit_database::{Deserialized, Ignore, Interfix, Json, Map};
use futures::{Stream, StreamExt}; use futures::{Stream, StreamExt};
use ipaddress::IPAddress; use ipaddress::IPAddress;
use ruma::{ use ruma::{
DeviceId, OwnedDeviceId, RoomId, UInt, UserId, api::{ DeviceId, OwnedDeviceId, RoomId, UInt, UserId,
IncomingResponse, MatrixVersion, OutgoingRequest, auth_scheme::{NoAuthentication, SendAccessToken}, client::push::{Pusher, PusherKind, set_pusher}, path_builder::SinglePath, push_gateway::send_event_notification::{ api::{
IncomingResponse, MatrixVersion, OutgoingRequest,
auth_scheme::{NoAccessToken, NoAuthentication, SendAccessToken},
client::push::{Pusher, PusherKind, set_pusher},
path_builder::SinglePath,
push_gateway::send_event_notification::{
self, self,
v1::{Device, Notification, NotificationCounts, NotificationPriority}, v1::{Device, Notification, NotificationCounts, NotificationPriority},
} },
}, events::{ },
events::{
AnySyncTimelineEvent, StateEventType, TimelineEventType, AnySyncTimelineEvent, StateEventType, TimelineEventType,
room::{create::RoomCreateEventContent, power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent}}, room::{
}, push::{ create::RoomCreateEventContent,
Action, PushConditionPowerLevelsCtx, PushConditionRoomCtx, PushFormat, Ruleset, Tweak, power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
}, room_version_rules::{AuthorizationRules, RoomPowerLevelsRules, RoomVersionRules}, serde::Raw, uint },
},
push::{
Action, HighlightTweakValue, PushConditionPowerLevelsCtx, PushConditionRoomCtx, PushFormat, Ruleset, Tweak
},
room_version_rules::{AuthorizationRules, RoomPowerLevelsRules, RoomVersionRules},
serde::Raw,
uint,
}; };
use crate::{Dep, client, config, globals, rooms, sending, users}; use crate::{Dep, client, config, globals, rooms, sending, users};
@@ -189,7 +202,9 @@ impl Service {
#[tracing::instrument(skip(self, dest, request))] #[tracing::instrument(skip(self, dest, request))]
pub async fn send_request<T>(&self, dest: &str, request: T) -> Result<T::IncomingResponse> pub async fn send_request<T>(&self, dest: &str, request: T) -> Result<T::IncomingResponse>
where where
T: OutgoingRequest<Authentication = NoAuthentication, PathBuilder = SinglePath> + Debug + Send, T: OutgoingRequest<Authentication = NoAuthentication, PathBuilder = SinglePath>
+ Debug
+ Send,
{ {
const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_0]; const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_0];
@@ -197,7 +212,7 @@ impl Service {
trace!("Push gateway destination: {dest}"); trace!("Push gateway destination: {dest}");
let http_request = request let http_request = request
.try_into_http_request::<BytesMut>(&dest, SendAccessToken::None, ()) .try_into_http_request::<BytesMut>(&dest, (), ())
.map_err(|e| { .map_err(|e| {
err!(BadServerResponse(warn!( err!(BadServerResponse(warn!(
"Failed to find destination {dest} for push gateway: {e}" "Failed to find destination {dest} for push gateway: {e}"
@@ -307,7 +322,13 @@ impl Service {
let serialized = event.to_format(); let serialized = event.to_format();
for action in self for action in self
.get_actions(user, &ruleset, power_levels.clone(), &serialized, event.room_id().unwrap()) .get_actions(
user,
&ruleset,
power_levels.clone(),
&serialized,
event.room_id().unwrap(),
)
.await .await
{ {
let n = match action { let n = match action {
@@ -363,7 +384,13 @@ impl Service {
.await .await
.unwrap_or_else(|_| user.localpart().to_owned()); .unwrap_or_else(|_| user.localpart().to_owned());
let ctx = PushConditionRoomCtx::new(room_id.to_owned(), room_joined_count, user.to_owned(), user_display_name).with_power_levels(power_levels); let ctx = PushConditionRoomCtx::new(
room_id.to_owned(),
room_joined_count,
user.to_owned(),
user_display_name,
)
.with_power_levels(power_levels);
ruleset.get_actions(pdu, &ctx).await ruleset.get_actions(pdu, &ctx).await
} }
@@ -442,7 +469,7 @@ impl Service {
if *event.kind() == TimelineEventType::RoomEncrypted if *event.kind() == TimelineEventType::RoomEncrypted
|| tweaks || tweaks
.iter() .iter()
.any(|t| matches!(t, Tweak::Highlight(true) | Tweak::Sound(_))) .any(|t| matches!(t, Tweak::Highlight(HighlightTweakValue::Yes) | Tweak::Sound(_)))
{ {
notify.prio = NotificationPriority::High; notify.prio = NotificationPriority::High;
} else { } else {
+4 -2
View File
@@ -9,10 +9,12 @@ use conduwuit::{
use database::{Deserialized, Ignore, Interfix, Map}; use database::{Deserialized, Ignore, Interfix, Map};
use futures::{Stream, StreamExt, TryFutureExt}; use futures::{Stream, StreamExt, TryFutureExt};
use ruma::{ use ruma::{
OwnedRoomAliasId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId, UserId, events::{ OwnedRoomAliasId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomAliasId, RoomId,
RoomOrAliasId, UserId,
events::{
StateEventType, StateEventType,
room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent}, room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
} },
}; };
use crate::{Dep, admin, appservice, appservice::RegistrationInfo, globals, rooms, sending}; use crate::{Dep, admin, appservice, appservice::RegistrationInfo, globals, rooms, sending};
@@ -109,7 +109,10 @@ where
match self match self
.services .services
.sending .sending
.send_federation_request(origin, get_event::v1::Request::new((*next_id).to_owned())) .send_federation_request(
origin,
get_event::v1::Request::new((*next_id).to_owned()),
)
.await .await
{ {
| Ok(res) => { | Ok(res) => {
@@ -31,7 +31,10 @@ where
let res = self let res = self
.services .services
.sending .sending
.send_federation_request(origin, get_room_state_ids::v1::Request::new(event_id.to_owned(), room_id.to_owned())) .send_federation_request(
origin,
get_room_state_ids::v1::Request::new(event_id.to_owned(), room_id.to_owned()),
)
.await .await
.inspect_err(|e| debug_warn!("Fetching state for event failed: {e}"))?; .inspect_err(|e| debug_warn!("Fetching state for event failed: {e}"))?;
@@ -182,7 +182,8 @@ pub async fn handle_incoming_pdu<'a>(
// copied from https://github.com/element-hq/synapse/blob/7e4588a/synapse/handlers/federation_event.py#L255-L300 // copied from https://github.com/element-hq/synapse/blob/7e4588a/synapse/handlers/federation_event.py#L255-L300
if value.get("type").and_then(|t| t.as_str()) == Some("m.room.member") { if value.get("type").and_then(|t| t.as_str()) == Some("m.room.member") {
if let Some(pdu) = if let Some(pdu) =
should_rescind_invite(&self.services, &mut value.clone(), &sender, room_id).await? should_rescind_invite(&self.services, &mut value.clone(), &sender, room_id)
.await?
{ {
debug_info!( debug_info!(
"Invite to {room_id} appears to have been rescinded by {sender}, marking as \ "Invite to {room_id} appears to have been rescinded by {sender}, marking as \
@@ -42,7 +42,9 @@ where
// 2. Check signatures, otherwise drop // 2. Check signatures, otherwise drop
// 3. check content hash, redact if doesn't match // 3. check content hash, redact if doesn't match
let room_version = get_room_version(create_event)?; let room_version = get_room_version(create_event)?;
let room_rules = room_version.rules().expect("room version should have defined rules"); let room_rules = room_version
.rules()
.expect("room version should have defined rules");
let mut incoming_pdu = match self let mut incoming_pdu = match self
.services .services
.server_keys .server_keys
+1 -1
View File
@@ -119,4 +119,4 @@ fn get_room_version<Pdu: Event>(create_event: &Pdu) -> Result<RoomVersionId> {
let room_version = content.room_version; let room_version = content.room_version;
Ok(room_version) Ok(room_version)
} }
@@ -5,7 +5,8 @@ use conduwuit::{
matrix::event::{gen_event_id, gen_event_id_canonical_json}, matrix::event::{gen_event_id, gen_event_id_canonical_json},
}; };
use itertools::Itertools; use itertools::Itertools;
use ruma::{CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, RoomVersionId}; use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, RoomVersionId, };
use serde_json::value::RawValue as RawJsonValue; use serde_json::value::RawValue as RawJsonValue;
type Parsed = (OwnedRoomId, OwnedEventId, CanonicalJsonObject); type Parsed = (OwnedRoomId, OwnedEventId, CanonicalJsonObject);
@@ -10,16 +10,17 @@ use conduwuit::{
warn, warn,
}; };
use ruma::{ use ruma::{
CanonicalJsonObject, CanonicalJsonValue, KeyId, OwnedKeyId, RoomId, ServerName, SigningKeyId, events::StateEventType CanonicalJsonObject, CanonicalJsonValue, KeyId, OwnedKeyId, RoomId, ServerName, SigningKeyId,
events::StateEventType,
}; };
use ruminuwuity::policy::{ use ruminuwuity::policy::{
policy_check::unstable::Request as PolicyCheckRequest, event::RoomPolicyEventContent, policy_check::unstable::Request as PolicyCheckRequest,
policy_sign::unstable::Request as PolicySignRequest, policy_sign::unstable::Request as PolicySignRequest,
event::RoomPolicyEventContent
}; };
use serde_json::value::RawValue; use serde_json::value::RawValue;
static POLICY_EVENT_TYPE_UNSTABLE: LazyLock<StateEventType> = LazyLock::new(|| StateEventType::from("org.matrix.msc4284.policy")); static POLICY_EVENT_TYPE_UNSTABLE: LazyLock<StateEventType> =
LazyLock::new(|| StateEventType::from("org.matrix.msc4284.policy"));
/// Asks a remote policy server if the event is allowed. /// Asks a remote policy server if the event is allowed.
/// ///
@@ -88,7 +89,12 @@ pub async fn ask_policy_server(
return Ok(true); return Ok(true);
}, },
}; };
if !self.services.state_cache.server_in_room(&via, room_id).await { if !self
.services
.state_cache
.server_in_room(&via, room_id)
.await
{
debug!( debug!(
via = %via, via = %via,
"Policy server is not in the room, skipping spam check" "Policy server is not in the room, skipping spam check"
@@ -132,15 +138,13 @@ pub async fn ask_policy_server(
via = %via, via = %via,
"Checking event for spam with policy server via legacy check" "Checking event for spam with policy server via legacy check"
); );
let mut request = PolicyCheckRequest::new(pdu.event_id().to_owned()); let mut request = PolicyCheckRequest::new(pdu.event_id().to_owned());
request.pdu = Some(outgoing); request.pdu = Some(outgoing);
let response = tokio::time::timeout( let response = tokio::time::timeout(
Duration::from_secs(self.services.server.config.policy_server_request_timeout), Duration::from_secs(self.services.server.config.policy_server_request_timeout),
self.services self.services.sending.send_federation_request(&via, request),
.sending
.send_federation_request(&via, request),
) )
.await; .await;
let response = match response { let response = match response {
@@ -112,7 +112,13 @@ where
{ {
let event_fetch = |event_id| self.event_fetch(event_id); let event_fetch = |event_id| self.event_fetch(event_id);
let event_exists = |event_id| self.event_exists(event_id); let event_exists = |event_id| self.event_exists(event_id);
state_res::resolve(room_version_rules, state_sets, auth_chain_sets, &event_fetch, &event_exists) state_res::resolve(
.map_err(|e| err!(error!("State resolution failed: {e:?}"))) room_version_rules,
.await state_sets,
auth_chain_sets,
&event_fetch,
&event_exists,
)
.map_err(|e| err!(error!("State resolution failed: {e:?}")))
.await
} }
@@ -53,7 +53,9 @@ where
); );
let timer = Instant::now(); let timer = Instant::now();
let room_version_id = get_room_version(create_event)?; let room_version_id = get_room_version(create_event)?;
let room_version_rules = room_version_id.rules().expect("room version should have defined rules"); let room_version_rules = room_version_id
.rules()
.expect("room version should have defined rules");
// 10. Fetch missing state and auth chain events by calling /state_ids at // 10. Fetch missing state and auth chain events by calling /state_ids at
// backwards extremities doing all the checks in this list starting at 1. // backwards extremities doing all the checks in this list starting at 1.
+3 -1
View File
@@ -7,7 +7,9 @@ use conduwuit::{
use database::{Deserialized, Json, Map}; use database::{Deserialized, Json, Map};
use futures::{Stream, StreamExt}; use futures::{Stream, StreamExt};
use ruma::{ use ruma::{
CanonicalJsonObject, OwnedRoomId, OwnedUserId, RoomId, UserId, events::{AnySyncEphemeralRoomEvent, receipt::ReceiptEvent}, serde::Raw CanonicalJsonObject, OwnedRoomId, OwnedUserId, RoomId, UserId,
events::{AnySyncEphemeralRoomEvent, receipt::ReceiptEvent},
serde::Raw,
}; };
use crate::{Dep, globals}; use crate::{Dep, globals};
+25 -24
View File
@@ -17,16 +17,17 @@ use conduwuit_core::{
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, pin_mut, stream::FuturesUnordered}; use futures::{FutureExt, Stream, StreamExt, TryFutureExt, pin_mut, stream::FuturesUnordered};
use lru_cache::LruCache; use lru_cache::LruCache;
use ruma::{ use ruma::{
OwnedEventId, OwnedRoomId, OwnedServerName, RoomId, ServerName, UserId, api::{ OwnedEventId, OwnedRoomId, OwnedServerName, RoomId, ServerName, UserId,
api::{
client::space::SpaceHierarchyRoomsChunk, client::space::SpaceHierarchyRoomsChunk,
federation::{ federation::{self, space::SpaceHierarchyParentSummary},
self, },
space::SpaceHierarchyParentSummary, events::{
},
}, events::{
StateEventType, StateEventType,
space::child::{HierarchySpaceChildEvent, SpaceChildEventContent}, space::child::{HierarchySpaceChildEvent, SpaceChildEventContent},
}, room::{JoinRuleSummary, RoomSummary}, serde::Raw, },
room::{JoinRuleSummary, RoomSummary},
serde::Raw,
}; };
use tokio::sync::{Mutex, MutexGuard}; use tokio::sync::{Mutex, MutexGuard};
@@ -299,11 +300,7 @@ async fn get_room_summary(
let join_rule = self.services.state_accessor.get_join_rules(room_id).await; let join_rule = self.services.state_accessor.get_join_rules(room_id).await;
let is_accessible_child = self let is_accessible_child = self
.is_accessible_child( .is_accessible_child(room_id, &join_rule.clone().into(), identifier)
room_id,
&join_rule.clone().into(),
identifier,
)
.await; .await;
if !is_accessible_child { if !is_accessible_child {
@@ -375,7 +372,7 @@ async fn get_room_summary(
join_rule.clone().into(), join_rule.clone().into(),
guest_can_join, guest_can_join,
num_joined_members.try_into().unwrap_or_default(), num_joined_members.try_into().unwrap_or_default(),
world_readable world_readable,
); );
summary.canonical_alias = canonical_alias; summary.canonical_alias = canonical_alias;
summary.name = name; summary.name = name;
@@ -426,13 +423,17 @@ async fn is_accessible_child(
| JoinRuleSummary::Public | JoinRuleSummary::Public
| JoinRuleSummary::Knock | JoinRuleSummary::Knock
| JoinRuleSummary::KnockRestricted(_) => true, | JoinRuleSummary::KnockRestricted(_) => true,
| JoinRuleSummary::Restricted(restricted_summary) => { | JoinRuleSummary::Restricted(restricted_summary) =>
(&restricted_summary.allowed_room_ids).stream().any(async |room| match identifier { (&restricted_summary.allowed_room_ids)
| Identifier::UserId(user) => self.services.state_cache.is_joined(user, room).await, .stream()
| Identifier::ServerName(server) => self.services.state_cache.server_in_room(server, room).await, .any(async |room| match identifier {
}).await | Identifier::UserId(user) =>
}, self.services.state_cache.is_joined(user, room).await,
_ => false | Identifier::ServerName(server) =>
self.services.state_cache.server_in_room(server, room).await,
})
.await,
| _ => false,
} }
} }
@@ -463,10 +464,10 @@ async fn cache_insert(
summary: RoomSummary, summary: RoomSummary,
) { ) {
let children_state = self let children_state = self
.get_space_child_events(&summary.room_id) .get_space_child_events(&summary.room_id)
.map(Event::into_format) .map(Event::into_format)
.collect() .collect()
.await; .await;
let summary = SpaceHierarchyParentSummary::new(summary, children_state); let summary = SpaceHierarchyParentSummary::new(summary, children_state);
cache.insert(current_room.to_owned(), Some(CachedSpaceHierarchySummary { summary })); cache.insert(current_room.to_owned(), Some(CachedSpaceHierarchySummary { summary }));
+1 -1
View File
@@ -4,7 +4,7 @@ use std::{
}; };
use conduwuit::{Error, Result}; use conduwuit::{Error, Result};
use ruma::{UInt, api::client::error::ErrorKind}; use ruma::{UInt, api::error::ErrorKind};
use crate::rooms::short::ShortRoomId; use crate::rooms::short::ShortRoomId;
+12 -3
View File
@@ -1,7 +1,10 @@
use std::str::FromStr; use std::str::FromStr;
use ruma::{ use ruma::{
UInt, api::federation::space::SpaceHierarchyParentSummary, owned_room_id, owned_server_name, room::{JoinRuleSummary, RoomSummary}, UInt,
api::federation::space::SpaceHierarchyParentSummary,
owned_room_id, owned_server_name,
room::{JoinRuleSummary, RoomSummary},
}; };
use crate::rooms::spaces::{PaginationToken, get_parent_children_via}; use crate::rooms::spaces::{PaginationToken, get_parent_children_via};
@@ -9,7 +12,13 @@ use crate::rooms::spaces::{PaginationToken, get_parent_children_via};
#[test] #[test]
fn get_summary_children() { fn get_summary_children() {
let summary = SpaceHierarchyParentSummary::new( let summary = SpaceHierarchyParentSummary::new(
RoomSummary::new(owned_room_id!("!root:example.org"), JoinRuleSummary::Public, true, UInt::from(1_u32), true), RoomSummary::new(
owned_room_id!("!root:example.org"),
JoinRuleSummary::Public,
true,
UInt::from(1_u32),
true,
),
vec![ vec![
serde_json::from_str( serde_json::from_str(
r#"{ r#"{
@@ -55,7 +64,7 @@ fn get_summary_children() {
}"#, }"#,
) )
.unwrap(), .unwrap(),
] ],
); );
assert_eq!( assert_eq!(
+13 -5
View File
@@ -17,10 +17,13 @@ use futures::{
FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future::join_all, pin_mut, FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future::join_all, pin_mut,
}; };
use ruma::{ use ruma::{
EventId, OwnedEventId, OwnedRoomId, RoomId, RoomVersionId, UserId, events::{ EventId, OwnedEventId, OwnedRoomId, RoomId, RoomVersionId, UserId,
events::{
AnyStrippedStateEvent, StateEventType, TimelineEventType, AnyStrippedStateEvent, StateEventType, TimelineEventType,
room::create::RoomCreateEventContent, room::create::RoomCreateEventContent,
}, room_version_rules::RoomVersionRules, serde::Raw },
room_version_rules::RoomVersionRules,
serde::Raw,
}; };
use crate::{ use crate::{
@@ -107,7 +110,7 @@ impl Service {
.then(|shorteventid| { .then(|shorteventid| {
self.services self.services
.short .short
.get_eventid_from_short::<Box<_>>(shorteventid) .get_eventid_from_short::<OwnedEventId>(shorteventid)
}) })
.ignore_err(); .ignore_err();
@@ -426,8 +429,13 @@ impl Service {
return Ok(HashMap::new()); return Ok(HashMap::new());
}; };
let auth_types = let auth_types = state_res::auth_types_for_event(
state_res::auth_types_for_event(kind, sender, state_key, content, room_version_rules)?; kind,
sender,
state_key,
content,
room_version_rules,
)?;
debug!(?auth_types, "Auth types for event"); debug!(?auth_types, "Auth types for event");
let sauthevents: HashMap<_, _> = auth_types let sauthevents: HashMap<_, _> = auth_types
.iter() .iter()
+55 -14
View File
@@ -9,12 +9,25 @@ use async_trait::async_trait;
use conduwuit::{Event, Result, err}; use conduwuit::{Event, Result, err};
use database::Map; use database::Map;
use ruma::{ use ruma::{
EventEncryptionAlgorithm, JsOption, OwnedRoomAliasId, OwnedUserId, RoomId, UserId, events::{ EventEncryptionAlgorithm, JsOption, OwnedRoomAliasId, OwnedUserId, RoomId, UserId,
events::{
StateEventType, StateEventType,
room::{ room::{
avatar::RoomAvatarEventContent, canonical_alias::RoomCanonicalAliasEventContent, create::{RoomCreateEvent, RoomCreateEventContent}, encryption::RoomEncryptionEventContent, guest_access::{GuestAccess, RoomGuestAccessEventContent}, history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent}, join_rules::{JoinRule, RoomJoinRulesEventContent}, member::RoomMemberEventContent, name::RoomNameEventContent, pinned_events::RoomPinnedEventsEventContent, power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent}, topic::RoomTopicEventContent avatar::RoomAvatarEventContent,
canonical_alias::RoomCanonicalAliasEventContent,
create::{RoomCreateEvent, RoomCreateEventContent},
encryption::RoomEncryptionEventContent,
guest_access::{GuestAccess, RoomGuestAccessEventContent},
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
join_rules::{JoinRule, RoomJoinRulesEventContent},
member::RoomMemberEventContent,
name::RoomNameEventContent,
pinned_events::RoomPinnedEventsEventContent,
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
topic::RoomTopicEventContent,
}, },
}, room::RoomType },
room::RoomType,
}; };
use crate::{Dep, rooms}; use crate::{Dep, rooms};
@@ -152,12 +165,25 @@ impl Service {
.is_ok() .is_ok()
} }
/// Get a set of the room's creators. This will always contain a single user for room versions 11 and earlier. /// Get a set of the room's creators. This will always contain a single user
/// for room versions 11 and earlier.
pub async fn get_room_creators(&self, room_id: &RoomId) -> HashSet<OwnedUserId> { pub async fn get_room_creators(&self, room_id: &RoomId) -> HashSet<OwnedUserId> {
let room_version_rules = self.services.state.get_room_version(room_id).await.expect("room should have a version").rules().expect("room version should be known"); let room_version_rules = self
.services
.state
.get_room_version(room_id)
.await
.expect("room should have a version")
.rules()
.expect("room version should be known");
let create_event = self.room_state_get(room_id, &StateEventType::RoomCreate, "").await.expect("room should have a create event"); let create_event = self
let create_content: RoomCreateEventContent = create_event.get_content().expect("create event content should be valid"); .room_state_get(room_id, &StateEventType::RoomCreate, "")
.await
.expect("room should have a create event");
let create_content: RoomCreateEventContent = create_event
.get_content()
.expect("create event content should be valid");
let mut creators = HashSet::new(); let mut creators = HashSet::new();
if room_version_rules.authorization.use_room_create_sender { if room_version_rules.authorization.use_room_create_sender {
@@ -174,15 +200,30 @@ impl Service {
creators creators
} }
/// Get the room's power levels. This will never fail -- if the room has no power level state event, /// Get the room's power levels. This will never fail -- if the room has no
/// the default power levels for the room's version will be returned. /// power level state event, the default power levels for the room's
/// version will be returned.
pub async fn get_room_power_levels(&self, room_id: &RoomId) -> RoomPowerLevels { pub async fn get_room_power_levels(&self, room_id: &RoomId) -> RoomPowerLevels {
let room_version_rules = self.services.state.get_room_version(room_id).await.expect("room should have a version").rules().expect("room version should be known"); let room_version_rules = self
let creators = self.get_room_creators(room_id).await; .services
let power_levels_event: RoomPowerLevelsEventContent = self.room_state_get_content(room_id, &StateEventType::RoomPowerLevels, "") .state
.get_room_version(room_id)
.await .await
.unwrap_or_else(|_| RoomPowerLevelsEventContent::new(&room_version_rules.authorization)); .expect("room should have a version")
.rules()
.expect("room version should be known");
let creators = self.get_room_creators(room_id).await;
let power_levels_event: RoomPowerLevelsEventContent = self
.room_state_get_content(room_id, &StateEventType::RoomPowerLevels, "")
.await
.unwrap_or_else(|_| {
RoomPowerLevelsEventContent::new(&room_version_rules.authorization)
});
RoomPowerLevels::new(power_levels_event.into(), &room_version_rules.authorization, creators) RoomPowerLevels::new(
power_levels_event.into(),
&room_version_rules.authorization,
creators,
)
} }
} }
@@ -44,17 +44,13 @@ pub async fn server_can_see_event(
| HistoryVisibility::Invited => { | HistoryVisibility::Invited => {
// Allow if any member on requesting server was AT LEAST invited, else deny // Allow if any member on requesting server was AT LEAST invited, else deny
current_server_members current_server_members
.any(async |member| { .any(async |member| self.user_was_invited(shortstatehash, &member).await)
self.user_was_invited(shortstatehash, &member).await
})
.await .await
}, },
| HistoryVisibility::Joined => { | HistoryVisibility::Joined => {
// Allow if any member on requested server was joined, else deny // Allow if any member on requested server was joined, else deny
current_server_members current_server_members
.any(async |member| { .any(async |member| self.user_was_joined(shortstatehash, &member).await)
self.user_was_joined(shortstatehash, &member).await
})
.await .await
}, },
| HistoryVisibility::WorldReadable | HistoryVisibility::Shared | _ => true, | HistoryVisibility::WorldReadable | HistoryVisibility::Shared | _ => true,
+3 -1
View File
@@ -316,7 +316,9 @@ pub fn state_full(
shortstatehash: ShortStateHash, shortstatehash: ShortStateHash,
) -> impl Stream<Item = ((StateEventType, StateKey), impl Event)> + Send + '_ { ) -> impl Stream<Item = ((StateEventType, StateKey), impl Event)> + Send + '_ {
self.state_full_pdus(shortstatehash) self.state_full_pdus(shortstatehash)
.ready_filter_map(|pdu| Some(((pdu.kind().to_string().into(), pdu.state_key()?.into()), pdu))) .ready_filter_map(|pdu| {
Some(((pdu.kind().to_string().into(), pdu.state_key()?.into()), pdu))
})
} }
#[implement(super::Service)] #[implement(super::Service)]
+16 -9
View File
@@ -44,13 +44,22 @@ pub async fn user_can_redact(
))); )));
} }
let create_event = self.room_state_get(room_id, &StateEventType::RoomCreate, "").await?; let create_event = self
.room_state_get(room_id, &StateEventType::RoomCreate, "")
.await?;
let create_event_content: RoomCreateEventContent = create_event.get_content().unwrap(); let create_event_content: RoomCreateEventContent = create_event.get_content().unwrap();
let room_version_rules = create_event_content.room_version.rules().expect("room version should have defined rules"); let room_version_rules = create_event_content
if room_version_rules.authorization.explicitly_privilege_room_creators { .room_version
.rules()
.expect("room version should have defined rules");
if room_version_rules
.authorization
.explicitly_privilege_room_creators
{
let sender_owned = sender.to_owned(); let sender_owned = sender.to_owned();
// NOTE: we don't check the pre-v11 `creator` field because no room version has // NOTE: we don't check the pre-v11 `creator` field because no room version has
// `explicitly_privilege_room_creators` and `use_room_create_sender` set at the same time // `explicitly_privilege_room_creators` and `use_room_create_sender` set at the
// same time
if sender == create_event.sender() if sender == create_event.sender()
|| create_event_content || create_event_content
.additional_creators .additional_creators
@@ -60,7 +69,6 @@ pub async fn user_can_redact(
} }
} }
let power_levels = self.get_room_power_levels(room_id).await; let power_levels = self.get_room_power_levels(room_id).await;
if power_levels.user_can_redact_event_of_other(sender) { if power_levels.user_can_redact_event_of_other(sender) {
@@ -69,14 +77,13 @@ pub async fn user_can_redact(
if power_levels.user_can_redact_own_event(sender) { if power_levels.user_can_redact_own_event(sender) {
let is_own_event = match redacting_event { let is_own_event = match redacting_event {
Ok(redacting_event) => { | Ok(redacting_event) =>
if federation { if federation {
redacting_event.sender().server_name() == sender.server_name() redacting_event.sender().server_name() == sender.server_name()
} else { } else {
redacting_event.sender() == sender redacting_event.sender() == sender
} },
}, | _ => false,
_ => false
}; };
return Ok(is_own_event); return Ok(is_own_event);
+9 -3
View File
@@ -12,7 +12,9 @@ use conduwuit::{
use database::{Deserialized, Ignore, Interfix, Map}; use database::{Deserialized, Ignore, Interfix, Map};
use futures::{Stream, StreamExt, future::join5, pin_mut}; use futures::{Stream, StreamExt, future::join5, pin_mut};
use ruma::{ use ruma::{
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId, events::{AnyStrippedStateEvent, room::member::MembershipState}, serde::Raw OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
events::{AnyStrippedStateEvent, room::member::MembershipState},
serde::Raw,
}; };
use crate::{Dep, account_data, appservice::RegistrationInfo, config, globals, rooms, users}; use crate::{Dep, account_data, appservice::RegistrationInfo, config, globals, rooms, users};
@@ -415,7 +417,9 @@ pub async fn invite_state(
.qry(&key) .qry(&key)
.await .await
.deserialized() .deserialized()
.and_then(|val: Raw<Vec<AnyStrippedStateEvent>>| val.deserialize_as_unchecked().map_err(Into::into)) .and_then(|val: Raw<Vec<AnyStrippedStateEvent>>| {
val.deserialize_as_unchecked().map_err(Into::into)
})
} }
#[implement(Service)] #[implement(Service)]
@@ -431,7 +435,9 @@ pub async fn knock_state(
.qry(&key) .qry(&key)
.await .await
.deserialized() .deserialized()
.and_then(|val: Raw<Vec<AnyStrippedStateEvent>>| val.deserialize_as_unchecked().map_err(Into::into)) .and_then(|val: Raw<Vec<AnyStrippedStateEvent>>| {
val.deserialize_as_unchecked().map_err(Into::into)
})
} }
#[implement(Service)] #[implement(Service)]
+8 -2
View File
@@ -11,7 +11,9 @@ use conduwuit_core::{
use conduwuit_database::{Deserialized, Map}; use conduwuit_database::{Deserialized, Map};
use futures::{Stream, StreamExt}; use futures::{Stream, StreamExt};
use ruma::{ use ruma::{
CanonicalJsonValue, EventId, OwnedUserId, RoomId, UserId, api::client::threads::get_threads::v1::IncludeThreads, events::relation::BundledThread, serde::Raw, uint CanonicalJsonValue, EventId, OwnedUserId, RoomId, UserId,
api::client::threads::get_threads::v1::IncludeThreads, events::relation::BundledThread,
serde::Raw, uint,
}; };
use serde_json::json; use serde_json::json;
@@ -100,7 +102,11 @@ impl Service {
); );
} else { } else {
// New thread // New thread
let relations = BundledThread::new(Raw::from_json(event.content().to_owned()), uint!(1), true); let relations = BundledThread::new(
Raw::from_json(event.content().to_owned()),
uint!(1),
true,
);
let content = serde_json::to_value(relations).expect("to_value always works"); let content = serde_json::to_value(relations).expect("to_value always works");
+11 -8
View File
@@ -18,10 +18,7 @@ use ruma::{
events::{ events::{
GlobalAccountDataEventType, TimelineEventType, GlobalAccountDataEventType, TimelineEventType,
push_rules::PushRulesEvent, push_rules::PushRulesEvent,
room::{ room::{encrypted::Relation, redaction::RoomRedactionEventContent},
encrypted::Relation,
redaction::RoomRedactionEventContent,
},
}, },
push::{Action, Ruleset, Tweak}, push::{Action, Ruleset, Tweak},
}; };
@@ -204,7 +201,11 @@ where
drop(insert_lock); drop(insert_lock);
// See if the event matches any known pushers via power level // See if the event matches any known pushers via power level
let power_levels = self.services.state_accessor.get_room_power_levels(room_id).await; let power_levels = self
.services
.state_accessor
.get_room_power_levels(room_id)
.await;
let mut push_target: HashSet<_> = self let mut push_target: HashSet<_> = self
.services .services
.state_cache .state_cache
@@ -251,7 +252,7 @@ where
{ {
match action { match action {
| Action::Notify => notify = true, | Action::Notify => notify = true,
| Action::SetTweak(Tweak::Highlight(true)) => { | Action::SetTweak(Tweak::Highlight(ruma::push::HighlightTweakValue::Yes)) => {
highlight = true; highlight = true;
}, },
| _ => {}, | _ => {},
@@ -366,10 +367,12 @@ where
if let Ok(content) = pdu.get_content::<ExtractRelatesTo>() { if let Ok(content) = pdu.get_content::<ExtractRelatesTo>() {
match content.relates_to { match content.relates_to {
| Relation::Reply { in_reply_to } => { | Relation::Reply(in_reply_to) => {
// We need to do it again here, because replies don't have // We need to do it again here, because replies don't have
// event_id as a top level field // event_id as a top level field
if let Ok(related_pducount) = self.get_pdu_count(&in_reply_to.event_id).await { if let Ok(related_pducount) =
self.get_pdu_count(&in_reply_to.in_reply_to.event_id).await
{
self.services self.services
.pdu_metadata .pdu_metadata
.add_relation(count2, related_pducount); .add_relation(count2, related_pducount);
+37 -9
View File
@@ -12,10 +12,16 @@ use conduwuit_core::{
}; };
use futures::{FutureExt, Stream, StreamExt}; use futures::{FutureExt, Stream, StreamExt};
use ruma::{ use ruma::{
CanonicalJsonObject, EventId, Int, OwnedServerName, RoomId, ServerName, api::federation, events::{ CanonicalJsonObject, EventId, Int, OwnedServerName, RoomId, ServerName,
api::federation,
events::{
StateEventType, TimelineEventType, StateEventType, TimelineEventType,
room::{create::RoomCreateEventContent, power_levels::{RoomPowerLevelsEventContent, UserPowerLevel}}, room::{
}, uint create::RoomCreateEventContent,
power_levels::{RoomPowerLevelsEventContent, UserPowerLevel},
},
},
uint,
}; };
use serde_json::value::RawValue as RawJsonValue; use serde_json::value::RawValue as RawJsonValue;
@@ -66,7 +72,11 @@ pub async fn backfill_if_required(&self, room_id: &RoomId, from: PduCount) -> Re
.sending .sending
.send_federation_request( .send_federation_request(
&backfill_server, &backfill_server,
federation::backfill::get_backfill::v1::Request::new(room_id.to_owned(), vec![first_pdu.1.event_id().to_owned()], uint!(100)) federation::backfill::get_backfill::v1::Request::new(
room_id.to_owned(),
vec![first_pdu.1.event_id().to_owned()],
uint!(100),
),
) )
.await; .await;
match response { match response {
@@ -125,7 +135,10 @@ pub async fn get_remote_pdu(&self, room_id: &RoomId, event_id: &EventId) -> Resu
let value = self let value = self
.services .services
.sending .sending
.send_federation_request(&backfill_server, federation::event::get_event::v1::Request::new(event_id.to_owned())) .send_federation_request(
&backfill_server,
federation::event::get_event::v1::Request::new(event_id.to_owned()),
)
.await .await
.and_then(|response| { .and_then(|response| {
serde_json::from_str::<CanonicalJsonObject>(response.pdu.get()).map_err(|e| { serde_json::from_str::<CanonicalJsonObject>(response.pdu.get()).map_err(|e| {
@@ -220,7 +233,11 @@ pub async fn backfill_pdu(&self, origin: &ServerName, pdu: Box<RawJsonValue>) ->
async fn candidate_backfill_servers(&self, room_id: &RoomId) -> HashSet<OwnedServerName> { async fn candidate_backfill_servers(&self, room_id: &RoomId) -> HashSet<OwnedServerName> {
let mut candidate_backfill_servers = HashSet::new(); let mut candidate_backfill_servers = HashSet::new();
let power_levels = self.services.state_accessor.get_room_power_levels(room_id).await; let power_levels = self
.services
.state_accessor
.get_room_power_levels(room_id)
.await;
// Insert servers of room creators // Insert servers of room creators
if let Some(creators) = &power_levels.rules.privileged_creators { if let Some(creators) = &power_levels.rules.privileged_creators {
@@ -237,19 +254,30 @@ async fn candidate_backfill_servers(&self, room_id: &RoomId) -> HashSet<OwnedSer
} }
// Insert the canonical room alias server // Insert the canonical room alias server
if let Ok(canonical_alias) = self.services.state_accessor.get_canonical_alias(room_id).await { if let Ok(canonical_alias) = self
.services
.state_accessor
.get_canonical_alias(room_id)
.await
{
candidate_backfill_servers.insert(canonical_alias.server_name().to_owned()); candidate_backfill_servers.insert(canonical_alias.server_name().to_owned());
} }
// Insert all trusted servers in the config // Insert all trusted servers in the config
candidate_backfill_servers.extend(self.services.server.config.trusted_servers.iter().cloned()); candidate_backfill_servers
.extend(self.services.server.config.trusted_servers.iter().cloned());
// Remove our own name, we can't request backfill from ourselves // Remove our own name, we can't request backfill from ourselves
candidate_backfill_servers.remove(self.services.globals.server_name()); candidate_backfill_servers.remove(self.services.globals.server_name());
// Remove all servers that aren't in the room // Remove all servers that aren't in the room
for server in candidate_backfill_servers.clone() { for server in candidate_backfill_servers.clone() {
if !self.services.state_cache.server_in_room(&server, room_id).await { if !self
.services
.state_cache
.server_in_room(&server, room_id)
.await
{
candidate_backfill_servers.remove(&server); candidate_backfill_servers.remove(&server);
} }
} }
+1 -1
View File
@@ -140,7 +140,7 @@ pub async fn build_and_append_pdu(
body, body,
Some(pdu.event_id().into()), Some(pdu.event_id().into()),
source, source,
pdu.sender.clone().into(), pdu.sender.clone(),
)?; )?;
} }
} }
+5 -5
View File
@@ -96,7 +96,7 @@ pub async fn create_event(
event_type, event_type,
room_id.as_ref().map_or("None", |id| id.as_str()) room_id.as_ref().map_or("None", |id| id.as_str())
); );
let room_version_id = match room_id { let room_version = match room_id {
| Some(room_id) => { | Some(room_id) => {
trace!(%room_id, "Looking up existing room ID"); trace!(%room_id, "Looking up existing room ID");
self.services self.services
@@ -114,7 +114,7 @@ pub async fn create_event(
| None => { | None => {
trace!("No room ID, assuming room creation"); trace!("No room ID, assuming room creation");
room_version_from_event( room_version_from_event(
RoomId::new(self.services.globals.server_name()), RoomId::new_v1(self.services.globals.server_name()),
&event_type.clone(), &event_type.clone(),
&content.clone(), &content.clone(),
)? )?
@@ -261,7 +261,7 @@ pub async fn create_event(
pdu.event_id, pdu.event_id,
pdu.room_id.as_ref().map_or("None", |id| id.as_str()) pdu.room_id.as_ref().map_or("None", |id| id.as_str())
); );
Ok((pdu, room_version_id)) Ok((pdu, room_version))
} }
#[implement(super::Service)] #[implement(super::Service)]
@@ -276,7 +276,7 @@ pub async fn create_hash_and_sign_event(
if !self.services.globals.user_is_local(sender) { if !self.services.globals.user_is_local(sender) {
return Err!(Request(Forbidden("Sender must be a local user"))); return Err!(Request(Forbidden("Sender must be a local user")));
} }
let (mut pdu, room_version_id) = self let (mut pdu, room_version) = self
.create_event(pdu_builder, sender, room_id, mutex_lock) .create_event(pdu_builder, sender, room_id, mutex_lock)
.await?; .await?;
// Hash and sign // Hash and sign
@@ -292,7 +292,7 @@ pub async fn create_hash_and_sign_event(
.hash_and_sign_event(&mut pdu_json, &room_version) .hash_and_sign_event(&mut pdu_json, &room_version)
{ {
return match e { return match e {
| Error::Signatures(ruma::signatures::Error::PduSize) => { | Error::SignatureJson(ruma::signatures::JsonError::PduTooLarge) => {
Err!(Request(TooLarge("Message/PDU is too long (exceeds 65535 bytes)"))) Err!(Request(TooLarge("Message/PDU is too long (exceeds 65535 bytes)")))
}, },
| _ => Err!(Request(Unknown(warn!("Signing event failed: {e}")))), | _ => Err!(Request(Unknown(warn!("Signing event failed: {e}")))),

Some files were not shown because too many files have changed in this diff Show More