Compare commits

..

17 commits

Author SHA1 Message Date
Jade Ellis
293e7243b3
style: Fix formatting/clippy issues
Some checks failed
Documentation / Build and Deploy Documentation (push) Has been skipped
Checks / Prefligit / prefligit (push) Failing after 3s
Release Docker Image / define-variables (push) Failing after 12s
Release Docker Image / build-image (linux/amd64, release, linux-amd64, base) (push) Has been skipped
Release Docker Image / build-image (linux/arm64, release, linux-arm64, base) (push) Has been skipped
Release Docker Image / merge (push) Has been skipped
Checks / Rust / Format (push) Failing after 6s
Checks / Rust / Clippy (push) Failing after 20s
Checks / Rust / Cargo Test (push) Failing after 16s
2025-07-02 19:32:50 +01:00
Jason Volk
143cb55ac8
Fix clippy::unnecessary-unwrap.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-02 19:29:32 +01:00
Jason Volk
3c7c641d2d
Add revoke_admin to service.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-02 19:29:32 +01:00
Jason Volk
36e81ba185
Split state_cache service.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-02 19:29:31 +01:00
Jason Volk
56420a67ca
Outdent state_compressor service.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-02 19:29:31 +01:00
Jason Volk
c5c309ec43
Split timeline service.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-02 19:29:28 +01:00
Jason Volk
c06aa49a90
Fix regression 75aadd5c6a
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-02 19:17:09 +01:00
Jason Volk
364293608d
Post-formatting aesthetic and spacing corrections
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-02 19:17:06 +01:00
Jason Volk
af4f66c768
Cleanup/improve other async queries in some client handlers.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-02 19:12:51 +01:00
Jason Volk
116f85360f
Toward abstracting Pdu into trait Event.
Co-authored-by: Jade Ellis <jade@ellis.link>
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-02 19:03:26 +01:00
Jason Volk
3d0360bcd6
Dedup and parallelize current key backup count and etag fetching.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-02 19:03:26 +01:00
Jason Volk
667afedd24
Macroize various remaining Error constructions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-02 19:03:24 +01:00
Jason Volk
21bbee8e3c
Simplify api to send notices to admin room
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-02 18:58:18 +01:00
Jason Volk
732a77f3a8
Use integrated error instead of panic on some legacy codepaths
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-02 18:58:18 +01:00
Jason Volk
f3dd90df39
Mitigate large futures
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-02 18:58:18 +01:00
Jason Volk
2051c22a28
Support optional device_id's in lazy-loading context.
Co-authored-by: Jade Ellis <jade@ellis.link>
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-02 18:58:17 +01:00
Jason Volk
49f7a2487f
Modernize various sender_user/sender_device lets.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-02 18:58:14 +01:00
131 changed files with 7169 additions and 6895 deletions

View file

@ -7,7 +7,10 @@ use std::{
use conduwuit::{ use conduwuit::{
Err, Result, debug_error, err, info, Err, Result, debug_error, err, info,
matrix::pdu::{PduEvent, PduId, RawPduId}, matrix::{
Event,
pdu::{PduEvent, PduId, RawPduId},
},
trace, utils, trace, utils,
utils::{ utils::{
stream::{IterStream, ReadyExt}, stream::{IterStream, ReadyExt},
@ -19,7 +22,7 @@ use futures::{FutureExt, StreamExt, TryStreamExt};
use ruma::{ use ruma::{
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId, OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId,
api::federation::event::get_room_state, api::federation::event::get_room_state, events::AnyStateEvent, serde::Raw,
}; };
use service::rooms::{ use service::rooms::{
short::{ShortEventId, ShortRoomId}, short::{ShortEventId, ShortRoomId},
@ -296,12 +299,12 @@ pub(super) async fn get_remote_pdu(
#[admin_command] #[admin_command]
pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result { pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result {
let room_id = self.services.rooms.alias.resolve(&room).await?; let room_id = self.services.rooms.alias.resolve(&room).await?;
let room_state: Vec<_> = self let room_state: Vec<Raw<AnyStateEvent>> = self
.services .services
.rooms .rooms
.state_accessor .state_accessor
.room_state_full_pdus(&room_id) .room_state_full_pdus(&room_id)
.map_ok(PduEvent::into_state_event) .map_ok(Event::into_format)
.try_collect() .try_collect()
.await?; .await?;
@ -409,7 +412,9 @@ pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool)
.reload .reload
.reload(&new_filter_layer, Some(handles)) .reload(&new_filter_layer, Some(handles))
{ {
| Ok(()) => return self.write_str("Successfully changed log level").await, | Ok(()) => {
return self.write_str("Successfully changed log level").await;
},
| Err(e) => { | Err(e) => {
return Err!("Failed to modify and reload the global tracing log level: {e}"); return Err!("Failed to modify and reload the global tracing log level: {e}");
}, },
@ -553,8 +558,8 @@ pub(super) async fn force_set_room_state_from_server(
.latest_pdu_in_room(&room_id) .latest_pdu_in_room(&room_id)
.await .await
.map_err(|_| err!(Database("Failed to find the latest PDU in database")))? .map_err(|_| err!(Database("Failed to find the latest PDU in database")))?
.event_id .event_id()
.clone(), .to_owned(),
}; };
let room_version = self.services.rooms.state.get_room_version(&room_id).await?; let room_version = self.services.rooms.state.get_room_version(&room_id).await?;

View file

@ -94,8 +94,7 @@ async fn process_command(services: Arc<Services>, input: &CommandInput) -> Proce
#[allow(clippy::result_large_err)] #[allow(clippy::result_large_err)]
fn handle_panic(error: &Error, command: &CommandInput) -> ProcessorResult { fn handle_panic(error: &Error, command: &CommandInput) -> ProcessorResult {
let link = let link = "Please submit a [bug report](https://forgejo.ellis.link/continuwuation/continuwuity/issues/new). 🥺";
"Please submit a [bug report](https://forgejo.ellis.link/continuwuation/continuwuity/issues/new). 🥺";
let msg = format!("Panic occurred while processing command:\n```\n{error:#?}\n```\n{link}"); let msg = format!("Panic occurred while processing command:\n```\n{error:#?}\n```\n{link}");
let content = RoomMessageEventContent::notice_markdown(msg); let content = RoomMessageEventContent::notice_markdown(msg);
error!("Panic while processing command: {error:?}"); error!("Panic while processing command: {error:?}");

View file

@ -5,7 +5,7 @@ use conduwuit::{
utils::{IterStream, ReadyExt}, utils::{IterStream, ReadyExt},
warn, warn,
}; };
use futures::StreamExt; use futures::{FutureExt, StreamExt};
use ruma::{OwnedRoomId, OwnedRoomOrAliasId, RoomAliasId, RoomId, RoomOrAliasId}; use ruma::{OwnedRoomId, OwnedRoomOrAliasId, RoomAliasId, RoomId, RoomOrAliasId};
use crate::{admin_command, admin_command_dispatch, get_room_info}; use crate::{admin_command, admin_command_dispatch, get_room_info};
@ -132,7 +132,10 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
evicting admins too)", evicting admins too)",
); );
if let Err(e) = leave_room(self.services, user_id, &room_id, None).await { if let Err(e) = leave_room(self.services, user_id, &room_id, None)
.boxed()
.await
{
warn!("Failed to leave room: {e}"); warn!("Failed to leave room: {e}");
} }
@ -297,7 +300,10 @@ async fn ban_list_of_rooms(&self) -> Result {
evicting admins too)", evicting admins too)",
); );
if let Err(e) = leave_room(self.services, user_id, &room_id, None).await { if let Err(e) = leave_room(self.services, user_id, &room_id, None)
.boxed()
.await
{
warn!("Failed to leave room: {e}"); warn!("Failed to leave room: {e}");
} }

View file

@ -1,14 +1,16 @@
use std::{collections::BTreeMap, fmt::Write as _}; use std::{collections::BTreeMap, fmt::Write as _};
use api::client::{full_user_deactivate, join_room_by_id_helper, leave_room}; use api::client::{
full_user_deactivate, join_room_by_id_helper, leave_all_rooms, leave_room, update_avatar_url,
update_displayname,
};
use conduwuit::{ use conduwuit::{
Err, Result, debug, debug_warn, error, info, is_equal_to, Err, Result, debug, debug_warn, error, info, is_equal_to,
matrix::pdu::PduBuilder, matrix::{Event, pdu::PduBuilder},
utils::{self, ReadyExt}, utils::{self, ReadyExt},
warn, warn,
}; };
use conduwuit_api::client::{leave_all_rooms, update_avatar_url, update_displayname}; use futures::{FutureExt, StreamExt};
use futures::StreamExt;
use ruma::{ use ruma::{
OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId,
events::{ events::{
@ -284,8 +286,9 @@ pub(super) async fn reset_password(&self, username: String, password: Option<Str
.set_password(&user_id, Some(new_password.as_str())) .set_password(&user_id, Some(new_password.as_str()))
{ {
| Err(e) => return Err!("Couldn't reset the password for user {user_id}: {e}"), | Err(e) => return Err!("Couldn't reset the password for user {user_id}: {e}"),
| Ok(()) => | Ok(()) => {
write!(self, "Successfully reset the password for user {user_id}: `{new_password}`"), write!(self, "Successfully reset the password for user {user_id}: `{new_password}`")
},
} }
.await .await
} }
@ -696,7 +699,9 @@ pub(super) async fn force_leave_room(
return Err!("{user_id} is not joined in the room"); return Err!("{user_id} is not joined in the room");
} }
leave_room(self.services, &user_id, &room_id, None).await?; leave_room(self.services, &user_id, &room_id, None)
.boxed()
.await?;
self.write_str(&format!("{user_id} has left {room_id}.",)) self.write_str(&format!("{user_id} has left {room_id}.",))
.await .await
@ -733,7 +738,7 @@ pub(super) async fn force_demote(&self, user_id: String, room_id: OwnedRoomOrAli
.state_accessor .state_accessor
.room_state_get(&room_id, &StateEventType::RoomCreate, "") .room_state_get(&room_id, &StateEventType::RoomCreate, "")
.await .await
.is_ok_and(|event| event.sender == user_id); .is_ok_and(|event| event.sender() == user_id);
if !user_can_demote_self { if !user_can_demote_self {
return Err!("User is not allowed to modify their own power levels in the room.",); return Err!("User is not allowed to modify their own power levels in the room.",);
@ -884,10 +889,7 @@ pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result {
return Err!("Event is already redacted."); return Err!("Event is already redacted.");
} }
let room_id = event.room_id; if !self.services.globals.user_is_local(event.sender()) {
let sender_user = event.sender;
if !self.services.globals.user_is_local(&sender_user) {
return Err!("This command only works on local users."); return Err!("This command only works on local users.");
} }
@ -897,21 +899,21 @@ pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result {
); );
let redaction_event_id = { let redaction_event_id = {
let state_lock = self.services.rooms.state.mutex.lock(&room_id).await; let state_lock = self.services.rooms.state.mutex.lock(event.room_id()).await;
self.services self.services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
PduBuilder { PduBuilder {
redacts: Some(event.event_id.clone()), redacts: Some(event.event_id().to_owned()),
..PduBuilder::timeline(&RoomRedactionEventContent { ..PduBuilder::timeline(&RoomRedactionEventContent {
redacts: Some(event.event_id.clone()), redacts: Some(event.event_id().to_owned()),
reason: Some(reason), reason: Some(reason),
}) })
}, },
&sender_user, event.sender(),
&room_id, event.room_id(),
&state_lock, &state_lock,
) )
.await? .await?

View file

@ -3,10 +3,9 @@ use std::fmt::Write;
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Error, Result, debug_info, err, error, info, is_equal_to, Err, Error, Event, Result, debug_info, err, error, info, is_equal_to,
matrix::pdu::PduBuilder, matrix::pdu::PduBuilder,
utils, utils::{self, ReadyExt, stream::BroadbandExt},
utils::{ReadyExt, stream::BroadbandExt},
warn, warn,
}; };
use conduwuit_service::Services; use conduwuit_service::Services;
@ -151,16 +150,32 @@ pub(crate) async fn register_route(
if !services.config.allow_registration && body.appservice_info.is_none() { if !services.config.allow_registration && body.appservice_info.is_none() {
match (body.username.as_ref(), body.initial_device_display_name.as_ref()) { match (body.username.as_ref(), body.initial_device_display_name.as_ref()) {
| (Some(username), Some(device_display_name)) => { | (Some(username), Some(device_display_name)) => {
info!(%is_guest, user = %username, device_name = %device_display_name, "Rejecting registration attempt as registration is disabled"); info!(
%is_guest,
user = %username,
device_name = %device_display_name,
"Rejecting registration attempt as registration is disabled"
);
}, },
| (Some(username), _) => { | (Some(username), _) => {
info!(%is_guest, user = %username, "Rejecting registration attempt as registration is disabled"); info!(
%is_guest,
user = %username,
"Rejecting registration attempt as registration is disabled"
);
}, },
| (_, Some(device_display_name)) => { | (_, Some(device_display_name)) => {
info!(%is_guest, device_name = %device_display_name, "Rejecting registration attempt as registration is disabled"); info!(
%is_guest,
device_name = %device_display_name,
"Rejecting registration attempt as registration is disabled"
);
}, },
| (None, _) => { | (None, _) => {
info!(%is_guest, "Rejecting registration attempt as registration is disabled"); info!(
%is_guest,
"Rejecting registration attempt as registration is disabled"
);
}, },
} }
@ -351,8 +366,7 @@ pub(crate) async fn register_route(
if !services.globals.new_user_displayname_suffix().is_empty() if !services.globals.new_user_displayname_suffix().is_empty()
&& body.appservice_info.is_none() && body.appservice_info.is_none()
{ {
write!(displayname, " {}", services.server.config.new_user_displayname_suffix) write!(displayname, " {}", services.server.config.new_user_displayname_suffix)?;
.expect("should be able to write to string buffer");
} }
services services
@ -370,8 +384,7 @@ pub(crate) async fn register_route(
content: ruma::events::push_rules::PushRulesEventContent { content: ruma::events::push_rules::PushRulesEventContent {
global: push::Ruleset::server_default(&user_id), global: push::Ruleset::server_default(&user_id),
}, },
}) })?,
.expect("to json always works"),
) )
.await?; .await?;
@ -416,32 +429,21 @@ pub(crate) async fn register_route(
// log in conduit admin channel if a non-guest user registered // log in conduit admin channel if a non-guest user registered
if body.appservice_info.is_none() && !is_guest { if body.appservice_info.is_none() && !is_guest {
if !device_display_name.is_empty() { if !device_display_name.is_empty() {
info!( let notice = format!(
"New user \"{user_id}\" registered on this server with device display name: \ "New user \"{user_id}\" registered on this server from IP {client} and device \
\"{device_display_name}\"" display name \"{device_display_name}\""
); );
info!("{notice}");
if services.server.config.admin_room_notices { if services.server.config.admin_room_notices {
services services.admin.notice(&notice).await;
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"New user \"{user_id}\" registered on this server from IP {client} and \
device display name \"{device_display_name}\""
)))
.await
.ok();
} }
} else { } else {
info!("New user \"{user_id}\" registered on this server."); let notice = format!("New user \"{user_id}\" registered on this server.");
info!("{notice}");
if services.server.config.admin_room_notices { if services.server.config.admin_room_notices {
services services.admin.notice(&notice).await;
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"New user \"{user_id}\" registered on this server from IP {client}"
)))
.await
.ok();
} }
} }
} }
@ -454,24 +456,22 @@ pub(crate) async fn register_route(
if services.server.config.admin_room_notices { if services.server.config.admin_room_notices {
services services
.admin .admin
.send_message(RoomMessageEventContent::notice_plain(format!( .notice(&format!(
"Guest user \"{user_id}\" with device display name \ "Guest user \"{user_id}\" with device display name \
\"{device_display_name}\" registered on this server from IP {client}" \"{device_display_name}\" registered on this server from IP {client}"
))) ))
.await .await;
.ok();
} }
} else { } else {
#[allow(clippy::collapsible_else_if)] #[allow(clippy::collapsible_else_if)]
if services.server.config.admin_room_notices { if services.server.config.admin_room_notices {
services services
.admin .admin
.send_message(RoomMessageEventContent::notice_plain(format!( .notice(&format!(
"Guest user \"{user_id}\" with no device display name registered on \ "Guest user \"{user_id}\" with no device display name registered on \
this server from IP {client}", this server from IP {client}",
))) ))
.await .await;
.ok();
} }
} }
} }
@ -603,7 +603,6 @@ pub(crate) async fn change_password_route(
.sender_user .sender_user
.as_ref() .as_ref()
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?; .ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
let sender_device = body.sender_device();
let mut uiaainfo = UiaaInfo { let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Password] }], flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
@ -617,7 +616,7 @@ pub(crate) async fn change_password_route(
| Some(auth) => { | Some(auth) => {
let (worked, uiaainfo) = services let (worked, uiaainfo) = services
.uiaa .uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo) .try_auth(sender_user, body.sender_device(), auth, &uiaainfo)
.await?; .await?;
if !worked { if !worked {
@ -631,7 +630,7 @@ pub(crate) async fn change_password_route(
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services services
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, json); .create(sender_user, body.sender_device(), &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
}, },
@ -650,7 +649,7 @@ pub(crate) async fn change_password_route(
services services
.users .users
.all_device_ids(sender_user) .all_device_ids(sender_user)
.ready_filter(|id| *id != sender_device) .ready_filter(|id| *id != body.sender_device())
.for_each(|id| services.users.remove_device(sender_user, id)) .for_each(|id| services.users.remove_device(sender_user, id))
.await; .await;
@ -659,17 +658,17 @@ pub(crate) async fn change_password_route(
.pusher .pusher
.get_pushkeys(sender_user) .get_pushkeys(sender_user)
.map(ToOwned::to_owned) .map(ToOwned::to_owned)
.broad_filter_map(|pushkey| async move { .broad_filter_map(async |pushkey| {
services services
.pusher .pusher
.get_pusher_device(&pushkey) .get_pusher_device(&pushkey)
.await .await
.ok() .ok()
.filter(|pusher_device| pusher_device != sender_device) .filter(|pusher_device| pusher_device != body.sender_device())
.is_some() .is_some()
.then_some(pushkey) .then_some(pushkey)
}) })
.for_each(|pushkey| async move { .for_each(async |pushkey| {
services.pusher.delete_pusher(sender_user, &pushkey).await; services.pusher.delete_pusher(sender_user, &pushkey).await;
}) })
.await; .await;
@ -680,11 +679,8 @@ pub(crate) async fn change_password_route(
if services.server.config.admin_room_notices { if services.server.config.admin_room_notices {
services services
.admin .admin
.send_message(RoomMessageEventContent::notice_plain(format!( .notice(&format!("User {sender_user} changed their password."))
"User {sender_user} changed their password." .await;
)))
.await
.ok();
} }
Ok(change_password::v3::Response {}) Ok(change_password::v3::Response {})
@ -699,13 +695,10 @@ pub(crate) async fn whoami_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<whoami::v3::Request>, body: Ruma<whoami::v3::Request>,
) -> Result<whoami::v3::Response> { ) -> Result<whoami::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let device_id = body.sender_device.clone();
Ok(whoami::v3::Response { Ok(whoami::v3::Response {
user_id: sender_user.clone(), user_id: body.sender_user().to_owned(),
device_id, device_id: body.sender_device.clone(),
is_guest: services.users.is_deactivated(sender_user).await? is_guest: services.users.is_deactivated(body.sender_user()).await?
&& body.appservice_info.is_none(), && body.appservice_info.is_none(),
}) })
} }
@ -733,7 +726,6 @@ pub(crate) async fn deactivate_route(
.sender_user .sender_user
.as_ref() .as_ref()
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?; .ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
let sender_device = body.sender_device();
let mut uiaainfo = UiaaInfo { let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Password] }], flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
@ -747,7 +739,7 @@ pub(crate) async fn deactivate_route(
| Some(auth) => { | Some(auth) => {
let (worked, uiaainfo) = services let (worked, uiaainfo) = services
.uiaa .uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo) .try_auth(sender_user, body.sender_device(), auth, &uiaainfo)
.await?; .await?;
if !worked { if !worked {
@ -760,7 +752,7 @@ pub(crate) async fn deactivate_route(
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services services
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, json); .create(sender_user, body.sender_device(), &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
}, },
@ -782,18 +774,17 @@ pub(crate) async fn deactivate_route(
super::update_displayname(&services, sender_user, None, &all_joined_rooms).await; super::update_displayname(&services, sender_user, None, &all_joined_rooms).await;
super::update_avatar_url(&services, sender_user, None, None, &all_joined_rooms).await; super::update_avatar_url(&services, sender_user, None, None, &all_joined_rooms).await;
full_user_deactivate(&services, sender_user, &all_joined_rooms).await?; full_user_deactivate(&services, sender_user, &all_joined_rooms)
.boxed()
.await?;
info!("User {sender_user} deactivated their account."); info!("User {sender_user} deactivated their account.");
if services.server.config.admin_room_notices { if services.server.config.admin_room_notices {
services services
.admin .admin
.send_message(RoomMessageEventContent::notice_plain(format!( .notice(&format!("User {sender_user} deactivated their account."))
"User {sender_user} deactivated their account." .await;
)))
.await
.ok();
} }
Ok(deactivate::v3::Response { Ok(deactivate::v3::Response {
@ -870,6 +861,7 @@ pub async fn full_user_deactivate(
all_joined_rooms: &[OwnedRoomId], all_joined_rooms: &[OwnedRoomId],
) -> Result<()> { ) -> Result<()> {
services.users.deactivate_account(user_id).await.ok(); services.users.deactivate_account(user_id).await.ok();
super::update_displayname(services, user_id, None, all_joined_rooms).await; super::update_displayname(services, user_id, None, all_joined_rooms).await;
super::update_avatar_url(services, user_id, None, None, all_joined_rooms).await; super::update_avatar_url(services, user_id, None, None, all_joined_rooms).await;
@ -906,7 +898,7 @@ pub async fn full_user_deactivate(
.state_accessor .state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "") .room_state_get(room_id, &StateEventType::RoomCreate, "")
.await .await
.is_ok_and(|event| event.sender == user_id); .is_ok_and(|event| event.sender() == user_id);
if user_can_demote_self { if user_can_demote_self {
let mut power_levels_content = room_power_levels.unwrap_or_default(); let mut power_levels_content = room_power_levels.unwrap_or_default();
@ -934,7 +926,7 @@ pub async fn full_user_deactivate(
} }
} }
super::leave_all_rooms(services, user_id).await; super::leave_all_rooms(services, user_id).boxed().await;
Ok(()) Ok(())
} }

View file

@ -17,7 +17,7 @@ pub(crate) async fn create_alias_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<create_alias::v3::Request>, body: Ruma<create_alias::v3::Request>,
) -> Result<create_alias::v3::Response> { ) -> Result<create_alias::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? { if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended."))); return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
} }
@ -65,7 +65,7 @@ pub(crate) async fn delete_alias_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<delete_alias::v3::Request>, body: Ruma<delete_alias::v3::Request>,
) -> Result<delete_alias::v3::Response> { ) -> Result<delete_alias::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? { if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended."))); return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
} }

View file

@ -2,8 +2,10 @@ use std::cmp::Ordering;
use axum::extract::State; use axum::extract::State;
use conduwuit::{Err, Result, err}; use conduwuit::{Err, Result, err};
use conduwuit_service::Services;
use futures::{FutureExt, future::try_join};
use ruma::{ use ruma::{
UInt, UInt, UserId,
api::client::backup::{ api::client::backup::{
add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session, add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session,
create_backup_version, delete_backup_keys, delete_backup_keys_for_room, create_backup_version, delete_backup_keys, delete_backup_keys_for_room,
@ -58,21 +60,9 @@ pub(crate) async fn get_latest_backup_info_route(
.await .await
.map_err(|_| err!(Request(NotFound("Key backup does not exist."))))?; .map_err(|_| err!(Request(NotFound("Key backup does not exist."))))?;
Ok(get_latest_backup_info::v3::Response { let (count, etag) = get_count_etag(&services, body.sender_user(), &version).await?;
algorithm,
count: (UInt::try_from( Ok(get_latest_backup_info::v3::Response { algorithm, count, etag, version })
services
.key_backups
.count_keys(body.sender_user(), &version)
.await,
)
.expect("user backup keys count should not be that high")),
etag: services
.key_backups
.get_etag(body.sender_user(), &version)
.await,
version,
})
} }
/// # `GET /_matrix/client/v3/room_keys/version/{version}` /// # `GET /_matrix/client/v3/room_keys/version/{version}`
@ -90,17 +80,12 @@ pub(crate) async fn get_backup_info_route(
err!(Request(NotFound("Key backup does not exist at version {:?}", body.version))) err!(Request(NotFound("Key backup does not exist at version {:?}", body.version)))
})?; })?;
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
Ok(get_backup_info::v3::Response { Ok(get_backup_info::v3::Response {
algorithm, algorithm,
count: services count,
.key_backups etag,
.count_keys(body.sender_user(), &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(body.sender_user(), &body.version)
.await,
version: body.version.clone(), version: body.version.clone(),
}) })
} }
@ -155,17 +140,9 @@ pub(crate) async fn add_backup_keys_route(
} }
} }
Ok(add_backup_keys::v3::Response { let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
count: services
.key_backups Ok(add_backup_keys::v3::Response { count, etag })
.count_keys(body.sender_user(), &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(body.sender_user(), &body.version)
.await,
})
} }
/// # `PUT /_matrix/client/r0/room_keys/keys/{roomId}` /// # `PUT /_matrix/client/r0/room_keys/keys/{roomId}`
@ -198,17 +175,9 @@ pub(crate) async fn add_backup_keys_for_room_route(
.await?; .await?;
} }
Ok(add_backup_keys_for_room::v3::Response { let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
count: services
.key_backups Ok(add_backup_keys_for_room::v3::Response { count, etag })
.count_keys(body.sender_user(), &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(body.sender_user(), &body.version)
.await,
})
} }
/// # `PUT /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}` /// # `PUT /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
@ -306,17 +275,9 @@ pub(crate) async fn add_backup_keys_for_session_route(
.await?; .await?;
} }
Ok(add_backup_keys_for_session::v3::Response { let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
count: services
.key_backups Ok(add_backup_keys_for_session::v3::Response { count, etag })
.count_keys(body.sender_user(), &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(body.sender_user(), &body.version)
.await,
})
} }
/// # `GET /_matrix/client/r0/room_keys/keys` /// # `GET /_matrix/client/r0/room_keys/keys`
@ -379,17 +340,9 @@ pub(crate) async fn delete_backup_keys_route(
.delete_all_keys(body.sender_user(), &body.version) .delete_all_keys(body.sender_user(), &body.version)
.await; .await;
Ok(delete_backup_keys::v3::Response { let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
count: services
.key_backups Ok(delete_backup_keys::v3::Response { count, etag })
.count_keys(body.sender_user(), &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(body.sender_user(), &body.version)
.await,
})
} }
/// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}` /// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}`
@ -404,17 +357,9 @@ pub(crate) async fn delete_backup_keys_for_room_route(
.delete_room_keys(body.sender_user(), &body.version, &body.room_id) .delete_room_keys(body.sender_user(), &body.version, &body.room_id)
.await; .await;
Ok(delete_backup_keys_for_room::v3::Response { let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
count: services
.key_backups Ok(delete_backup_keys_for_room::v3::Response { count, etag })
.count_keys(body.sender_user(), &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(body.sender_user(), &body.version)
.await,
})
} }
/// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}` /// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
@ -429,15 +374,22 @@ pub(crate) async fn delete_backup_keys_for_session_route(
.delete_room_key(body.sender_user(), &body.version, &body.room_id, &body.session_id) .delete_room_key(body.sender_user(), &body.version, &body.room_id, &body.session_id)
.await; .await;
Ok(delete_backup_keys_for_session::v3::Response { let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
count: services
.key_backups Ok(delete_backup_keys_for_session::v3::Response { count, etag })
.count_keys(body.sender_user(), &body.version) }
.await
.try_into()?, async fn get_count_etag(
etag: services services: &Services,
.key_backups sender_user: &UserId,
.get_etag(body.sender_user(), &body.version) version: &str,
.await, ) -> Result<(UInt, String)> {
}) let count = services
.key_backups
.count_keys(sender_user, version)
.map(TryInto::try_into);
let etag = services.key_backups.get_etag(sender_user, version).map(Ok);
Ok(try_join(count, etag).await?)
} }

View file

@ -26,8 +26,8 @@ pub(crate) async fn get_capabilities_route(
let mut capabilities = Capabilities::default(); let mut capabilities = Capabilities::default();
capabilities.room_versions = RoomVersionsCapability { capabilities.room_versions = RoomVersionsCapability {
default: services.server.config.default_room_version.clone(),
available, available,
default: services.server.config.default_room_version.clone(),
}; };
// we do not implement 3PID stuff // we do not implement 3PID stuff
@ -38,16 +38,12 @@ pub(crate) async fn get_capabilities_route(
}; };
// MSC4133 capability // MSC4133 capability
capabilities capabilities.set("uk.tcpip.msc4133.profile_fields", json!({"enabled": true}))?;
.set("uk.tcpip.msc4133.profile_fields", json!({"enabled": true}))
.expect("this is valid JSON we created");
capabilities capabilities.set(
.set( "org.matrix.msc4267.forget_forced_upon_leave",
"org.matrix.msc4267.forget_forced_upon_leave", json!({"enabled": services.config.forget_forced_upon_leave}),
json!({"enabled": services.config.forget_forced_upon_leave}), )?;
)
.expect("valid JSON we created");
Ok(get_capabilities::v3::Response { capabilities }) Ok(get_capabilities::v3::Response { capabilities })
} }

View file

@ -1,8 +1,6 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Result, at, debug_warn, err, Err, Event, Result, at, debug_warn, err, ref_at,
matrix::pdu::PduEvent,
ref_at,
utils::{ utils::{
IterStream, IterStream,
future::TryExtExt, future::TryExtExt,
@ -111,7 +109,7 @@ pub(crate) async fn get_context_route(
let lazy_loading_context = lazy_loading::Context { let lazy_loading_context = lazy_loading::Context {
user_id: sender_user, user_id: sender_user,
device_id: sender_device, device_id: Some(sender_device),
room_id, room_id,
token: Some(base_count.into_unsigned()), token: Some(base_count.into_unsigned()),
options: Some(&filter.lazy_load_options), options: Some(&filter.lazy_load_options),
@ -179,12 +177,12 @@ pub(crate) async fn get_context_route(
.broad_filter_map(|event_id: &OwnedEventId| { .broad_filter_map(|event_id: &OwnedEventId| {
services.rooms.timeline.get_pdu(event_id.as_ref()).ok() services.rooms.timeline.get_pdu(event_id.as_ref()).ok()
}) })
.map(PduEvent::into_state_event) .map(Event::into_format)
.collect() .collect()
.await; .await;
Ok(get_context::v3::Response { Ok(get_context::v3::Response {
event: base_event.map(at!(1)).map(PduEvent::into_room_event), event: base_event.map(at!(1)).map(Event::into_format),
start: events_before start: events_before
.last() .last()
@ -203,13 +201,13 @@ pub(crate) async fn get_context_route(
events_before: events_before events_before: events_before
.into_iter() .into_iter()
.map(at!(1)) .map(at!(1))
.map(PduEvent::into_room_event) .map(Event::into_format)
.collect(), .collect(),
events_after: events_after events_after: events_after
.into_iter() .into_iter()
.map(at!(1)) .map(at!(1))
.map(PduEvent::into_room_event) .map(Event::into_format)
.collect(), .collect(),
state, state,

View file

@ -21,11 +21,9 @@ pub(crate) async fn get_devices_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<get_devices::v3::Request>, body: Ruma<get_devices::v3::Request>,
) -> Result<get_devices::v3::Response> { ) -> Result<get_devices::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let devices: Vec<device::Device> = services let devices: Vec<device::Device> = services
.users .users
.all_devices_metadata(sender_user) .all_devices_metadata(body.sender_user())
.collect() .collect()
.await; .await;
@ -39,11 +37,9 @@ pub(crate) async fn get_device_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<get_device::v3::Request>, body: Ruma<get_device::v3::Request>,
) -> Result<get_device::v3::Response> { ) -> Result<get_device::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let device = services let device = services
.users .users
.get_device_metadata(sender_user, &body.body.device_id) .get_device_metadata(body.sender_user(), &body.body.device_id)
.await .await
.map_err(|_| err!(Request(NotFound("Device not found."))))?; .map_err(|_| err!(Request(NotFound("Device not found."))))?;

View file

@ -1,7 +1,7 @@
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Result, err, info, Err, Event, Result, err, info,
utils::{ utils::{
TryFutureExtExt, TryFutureExtExt,
math::Expected, math::Expected,
@ -352,7 +352,7 @@ async fn user_can_publish_room(
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "") .room_state_get(room_id, &StateEventType::RoomPowerLevels, "")
.await .await
{ {
| Ok(event) => serde_json::from_str(event.content.get()) | Ok(event) => serde_json::from_str(event.content().get())
.map_err(|_| err!(Database("Invalid event content for m.room.power_levels"))) .map_err(|_| err!(Database("Invalid event content for m.room.power_levels")))
.map(|content: RoomPowerLevelsEventContent| { .map(|content: RoomPowerLevelsEventContent| {
RoomPowerLevels::from(content) RoomPowerLevels::from(content)
@ -365,7 +365,7 @@ async fn user_can_publish_room(
.room_state_get(room_id, &StateEventType::RoomCreate, "") .room_state_get(room_id, &StateEventType::RoomCreate, "")
.await .await
{ {
| Ok(event) => Ok(event.sender == user_id), | Ok(event) => Ok(event.sender() == user_id),
| _ => Err!(Request(Forbidden("User is not allowed to publish this room"))), | _ => Err!(Request(Forbidden("User is not allowed to publish this room"))),
} }
}, },

View file

@ -13,11 +13,9 @@ pub(crate) async fn get_filter_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<get_filter::v3::Request>, body: Ruma<get_filter::v3::Request>,
) -> Result<get_filter::v3::Response> { ) -> Result<get_filter::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services services
.users .users
.get_filter(sender_user, &body.filter_id) .get_filter(body.sender_user(), &body.filter_id)
.await .await
.map(get_filter::v3::Response::new) .map(get_filter::v3::Response::new)
.map_err(|_| err!(Request(NotFound("Filter not found.")))) .map_err(|_| err!(Request(NotFound("Filter not found."))))
@ -30,9 +28,9 @@ pub(crate) async fn create_filter_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<create_filter::v3::Request>, body: Ruma<create_filter::v3::Request>,
) -> Result<create_filter::v3::Response> { ) -> Result<create_filter::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let filter_id = services
.users
let filter_id = services.users.create_filter(sender_user, &body.filter); .create_filter(body.sender_user(), &body.filter);
Ok(create_filter::v3::Response::new(filter_id)) Ok(create_filter::v3::Response::new(filter_id))
} }

View file

@ -126,7 +126,7 @@ pub(crate) async fn get_keys_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<get_keys::v3::Request>, body: Ruma<get_keys::v3::Request>,
) -> Result<get_keys::v3::Response> { ) -> Result<get_keys::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
get_keys_helper( get_keys_helper(
&services, &services,
@ -157,8 +157,7 @@ pub(crate) async fn upload_signing_keys_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<upload_signing_keys::v3::Request>, body: Ruma<upload_signing_keys::v3::Request>,
) -> Result<upload_signing_keys::v3::Response> { ) -> Result<upload_signing_keys::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let (sender_user, sender_device) = body.sender();
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
// UIAA // UIAA
let mut uiaainfo = UiaaInfo { let mut uiaainfo = UiaaInfo {
@ -203,12 +202,12 @@ pub(crate) async fn upload_signing_keys_route(
} }
// Success! // Success!
}, },
| _ => match body.json_body { | _ => match body.json_body.as_ref() {
| Some(json) => { | Some(json) => {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services services
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, &json); .create(sender_user, sender_device, &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
}, },
@ -373,7 +372,7 @@ pub(crate) async fn get_key_changes_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<get_key_changes::v3::Request>, body: Ruma<get_key_changes::v3::Request>,
) -> Result<get_key_changes::v3::Response> { ) -> Result<get_key_changes::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
let mut device_list_updates = HashSet::new(); let mut device_list_updates = HashSet::new();

View file

@ -51,7 +51,7 @@ pub(crate) async fn create_content_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<create_content::v3::Request>, body: Ruma<create_content::v3::Request>,
) -> Result<create_content::v3::Response> { ) -> Result<create_content::v3::Response> {
let user = body.sender_user.as_ref().expect("user is authenticated"); let user = body.sender_user();
if services.users.is_suspended(user).await? { if services.users.is_suspended(user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended."))); return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
} }
@ -97,7 +97,7 @@ pub(crate) async fn get_content_thumbnail_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content_thumbnail::v1::Request>, body: Ruma<get_content_thumbnail::v1::Request>,
) -> Result<get_content_thumbnail::v1::Response> { ) -> Result<get_content_thumbnail::v1::Response> {
let user = body.sender_user.as_ref().expect("user is authenticated"); let user = body.sender_user();
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?; let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
let mxc = Mxc { let mxc = Mxc {
@ -134,7 +134,7 @@ pub(crate) async fn get_content_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content::v1::Request>, body: Ruma<get_content::v1::Request>,
) -> Result<get_content::v1::Response> { ) -> Result<get_content::v1::Response> {
let user = body.sender_user.as_ref().expect("user is authenticated"); let user = body.sender_user();
let mxc = Mxc { let mxc = Mxc {
server_name: &body.server_name, server_name: &body.server_name,
@ -170,7 +170,7 @@ pub(crate) async fn get_content_as_filename_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content_as_filename::v1::Request>, body: Ruma<get_content_as_filename::v1::Request>,
) -> Result<get_content_as_filename::v1::Response> { ) -> Result<get_content_as_filename::v1::Response> {
let user = body.sender_user.as_ref().expect("user is authenticated"); let user = body.sender_user();
let mxc = Mxc { let mxc = Mxc {
server_name: &body.server_name, server_name: &body.server_name,
@ -206,7 +206,7 @@ pub(crate) async fn get_media_preview_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_media_preview::v1::Request>, body: Ruma<get_media_preview::v1::Request>,
) -> Result<get_media_preview::v1::Response> { ) -> Result<get_media_preview::v1::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
let url = &body.url; let url = &body.url;
let url = Url::parse(&body.url).map_err(|e| { let url = Url::parse(&body.url).map_err(|e| {

View file

@ -55,7 +55,7 @@ pub(crate) async fn get_media_preview_legacy_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_media_preview::v3::Request>, body: Ruma<get_media_preview::v3::Request>,
) -> Result<get_media_preview::v3::Response> { ) -> Result<get_media_preview::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
let url = &body.url; let url = &body.url;
let url = Url::parse(&body.url).map_err(|e| { let url = Url::parse(&body.url).map_err(|e| {

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,60 @@
use axum::extract::State;
use conduwuit::{Err, Result, matrix::pdu::PduBuilder};
use ruma::{
api::client::membership::ban_user,
events::room::member::{MembershipState, RoomMemberEventContent},
};
use crate::Ruma;
/// # `POST /_matrix/client/r0/rooms/{roomId}/ban`
///
/// Tries to send a ban event into the room.
pub(crate) async fn ban_user_route(
State(services): State<crate::State>,
body: Ruma<ban_user::v3::Request>,
) -> Result<ban_user::v3::Response> {
let sender_user = body.sender_user();
if sender_user == body.user_id {
return Err!(Request(Forbidden("You cannot ban yourself.")));
}
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let current_member_content = services
.rooms
.state_accessor
.get_member(&body.room_id, &body.user_id)
.await
.unwrap_or_else(|_| RoomMemberEventContent::new(MembershipState::Ban));
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Ban,
reason: body.reason.clone(),
displayname: None, // display name may be offensive
avatar_url: None, // avatar may be offensive
is_direct: None,
join_authorized_via_users_server: None,
third_party_invite: None,
redact_events: body.redact_events,
..current_member_content
}),
sender_user,
&body.room_id,
&state_lock,
)
.await?;
drop(state_lock);
Ok(ban_user::v3::Response::new())
}

View file

@ -0,0 +1,52 @@
use axum::extract::State;
use conduwuit::{Err, Result, is_matching, result::NotFound, utils::FutureBoolExt};
use futures::pin_mut;
use ruma::{api::client::membership::forget_room, events::room::member::MembershipState};
use crate::Ruma;
/// # `POST /_matrix/client/v3/rooms/{roomId}/forget`
///
/// Forgets about a room.
///
/// - If the sender user currently left the room: Stops sender user from
/// receiving information about the room
///
/// Note: Other devices of the user have no way of knowing the room was
/// forgotten, so this has to be called from every device
pub(crate) async fn forget_room_route(
State(services): State<crate::State>,
body: Ruma<forget_room::v3::Request>,
) -> Result<forget_room::v3::Response> {
let user_id = body.sender_user();
let room_id = &body.room_id;
let joined = services.rooms.state_cache.is_joined(user_id, room_id);
let knocked = services.rooms.state_cache.is_knocked(user_id, room_id);
let invited = services.rooms.state_cache.is_invited(user_id, room_id);
pin_mut!(joined, knocked, invited);
if joined.or(knocked).or(invited).await {
return Err!(Request(Unknown("You must leave the room before forgetting it")));
}
let membership = services
.rooms
.state_accessor
.get_member(room_id, user_id)
.await;
if membership.is_not_found() {
return Err!(Request(Unknown("No membership event was found, room was never joined")));
}
let non_membership = membership
.map(|member| member.membership)
.is_ok_and(is_matching!(MembershipState::Leave | MembershipState::Ban));
if non_membership || services.rooms.state_cache.is_left(user_id, room_id).await {
services.rooms.state_cache.forget(room_id, user_id);
}
Ok(forget_room::v3::Response::new())
}

View file

@ -0,0 +1,238 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{
Err, Result, debug_error, err, info,
matrix::{event::gen_event_id_canonical_json, pdu::PduBuilder},
};
use futures::{FutureExt, join};
use ruma::{
OwnedServerName, RoomId, UserId,
api::{client::membership::invite_user, federation::membership::create_invite},
events::room::member::{MembershipState, RoomMemberEventContent},
};
use service::Services;
use super::banned_room_check;
use crate::Ruma;
/// # `POST /_matrix/client/r0/rooms/{roomId}/invite`
///
/// Tries to send an invite event into the room.
#[tracing::instrument(skip_all, fields(%client), name = "invite")]
pub(crate) async fn invite_user_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<invite_user::v3::Request>,
) -> Result<invite_user::v3::Response> {
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
if !services.users.is_admin(sender_user).await && services.config.block_non_admin_invites {
debug_error!(
"User {sender_user} is not an admin and attempted to send an invite to room {}",
&body.room_id
);
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
}
banned_room_check(
&services,
sender_user,
Some(&body.room_id),
body.room_id.server_name(),
client,
)
.await?;
match &body.recipient {
| invite_user::v3::InvitationRecipient::UserId { user_id } => {
let sender_ignored_recipient = services.users.user_is_ignored(sender_user, user_id);
let recipient_ignored_by_sender =
services.users.user_is_ignored(user_id, sender_user);
let (sender_ignored_recipient, recipient_ignored_by_sender) =
join!(sender_ignored_recipient, recipient_ignored_by_sender);
if sender_ignored_recipient {
return Ok(invite_user::v3::Response {});
}
if let Ok(target_user_membership) = services
.rooms
.state_accessor
.get_member(&body.room_id, user_id)
.await
{
if target_user_membership.membership == MembershipState::Ban {
return Err!(Request(Forbidden("User is banned from this room.")));
}
}
if recipient_ignored_by_sender {
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
return Ok(invite_user::v3::Response {});
}
invite_helper(
&services,
sender_user,
user_id,
&body.room_id,
body.reason.clone(),
false,
)
.boxed()
.await?;
Ok(invite_user::v3::Response {})
},
| _ => {
Err!(Request(NotFound("User not found.")))
},
}
}
pub(crate) async fn invite_helper(
services: &Services,
sender_user: &UserId,
user_id: &UserId,
room_id: &RoomId,
reason: Option<String>,
is_direct: bool,
) -> Result {
if !services.users.is_admin(sender_user).await && services.config.block_non_admin_invites {
info!(
"User {sender_user} is not an admin and attempted to send an invite to room \
{room_id}"
);
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
}
if !services.globals.user_is_local(user_id) {
let (pdu, pdu_json, invite_room_state) = {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let content = RoomMemberEventContent {
avatar_url: services.users.avatar_url(user_id).await.ok(),
is_direct: Some(is_direct),
reason,
..RoomMemberEventContent::new(MembershipState::Invite)
};
let (pdu, pdu_json) = services
.rooms
.timeline
.create_hash_and_sign_event(
PduBuilder::state(user_id.to_string(), &content),
sender_user,
room_id,
&state_lock,
)
.await?;
let invite_room_state = services.rooms.state.summary_stripped(&pdu).await;
drop(state_lock);
(pdu, pdu_json, invite_room_state)
};
let room_version_id = services.rooms.state.get_room_version(room_id).await?;
let response = services
.sending
.send_federation_request(user_id.server_name(), create_invite::v2::Request {
room_id: room_id.to_owned(),
event_id: (*pdu.event_id).to_owned(),
room_version: room_version_id.clone(),
event: services
.sending
.convert_to_outgoing_federation_event(pdu_json.clone())
.await,
invite_room_state,
via: services
.rooms
.state_cache
.servers_route_via(room_id)
.await
.ok(),
})
.await?;
// We do not add the event_id field to the pdu here because of signature and
// hashes checks
let (event_id, value) = gen_event_id_canonical_json(&response.event, &room_version_id)
.map_err(|e| {
err!(Request(BadJson(warn!("Could not convert event to canonical JSON: {e}"))))
})?;
if pdu.event_id != event_id {
return Err!(Request(BadJson(warn!(
%pdu.event_id, %event_id,
"Server {} sent event with wrong event ID",
user_id.server_name()
))));
}
let origin: OwnedServerName = serde_json::from_value(serde_json::to_value(
value
.get("origin")
.ok_or_else(|| err!(Request(BadJson("Event missing origin field."))))?,
)?)
.map_err(|e| {
err!(Request(BadJson(warn!("Origin field in event is not a valid server name: {e}"))))
})?;
let pdu_id = services
.rooms
.event_handler
.handle_incoming_pdu(&origin, room_id, &event_id, value, true)
.boxed()
.await?
.ok_or_else(|| {
err!(Request(InvalidParam("Could not accept incoming PDU as timeline event.")))
})?;
return services.sending.send_pdu_room(room_id, &pdu_id).await;
}
if !services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
{
return Err!(Request(Forbidden(
"You must be joined in the room you are trying to invite from."
)));
}
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let content = RoomMemberEventContent {
displayname: services.users.displayname(user_id).await.ok(),
avatar_url: services.users.avatar_url(user_id).await.ok(),
blurhash: services.users.blurhash(user_id).await.ok(),
is_direct: Some(is_direct),
reason,
..RoomMemberEventContent::new(MembershipState::Invite)
};
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(user_id.to_string(), &content),
sender_user,
room_id,
&state_lock,
)
.await?;
drop(state_lock);
Ok(())
}

View file

@ -0,0 +1,989 @@
use std::{borrow::Borrow, collections::HashMap, iter::once, sync::Arc};
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{
Err, Result, debug, debug_info, debug_warn, err, error, info,
matrix::{
StateKey,
event::{gen_event_id, gen_event_id_canonical_json},
pdu::{PduBuilder, PduEvent},
state_res,
},
result::FlatOk,
trace,
utils::{
self, shuffle,
stream::{IterStream, ReadyExt},
},
warn,
};
use futures::{FutureExt, StreamExt};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId,
RoomVersionId, UserId,
api::{
client::{
error::ErrorKind,
membership::{ThirdPartySigned, join_room_by_id, join_room_by_id_or_alias},
},
federation::{self},
},
canonical_json::to_canonical_value,
events::{
StateEventType,
room::{
join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent},
},
},
};
use service::{
Services,
appservice::RegistrationInfo,
rooms::{
state::RoomMutexGuard,
state_compressor::{CompressedState, HashSetCompressStateEvent},
},
};
use super::banned_room_check;
use crate::Ruma;
/// # `POST /_matrix/client/r0/rooms/{roomId}/join`
///
/// Tries to join the sender user into a room.
///
/// - If the server knowns about this room: creates the join event and does auth
/// rules locally
/// - If the server does not know about the room: asks other servers over
/// federation
#[tracing::instrument(skip_all, fields(%client), name = "join")]
pub(crate) async fn join_room_by_id_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<join_room_by_id::v3::Request>,
) -> Result<join_room_by_id::v3::Response> {
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
banned_room_check(
&services,
sender_user,
Some(&body.room_id),
body.room_id.server_name(),
client,
)
.await?;
// There is no body.server_name for /roomId/join
let mut servers: Vec<_> = services
.rooms
.state_cache
.servers_invite_via(&body.room_id)
.map(ToOwned::to_owned)
.collect()
.await;
servers.extend(
services
.rooms
.state_cache
.invite_state(sender_user, &body.room_id)
.await
.unwrap_or_default()
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.map(|user| user.server_name().to_owned()),
);
if let Some(server) = body.room_id.server_name() {
servers.push(server.into());
}
servers.sort_unstable();
servers.dedup();
shuffle(&mut servers);
join_room_by_id_helper(
&services,
sender_user,
&body.room_id,
body.reason.clone(),
&servers,
body.third_party_signed.as_ref(),
&body.appservice_info,
)
.boxed()
.await
}
/// # `POST /_matrix/client/r0/join/{roomIdOrAlias}`
///
/// Tries to join the sender user into a room.
///
/// - If the server knowns about this room: creates the join event and does auth
/// rules locally
/// - If the server does not know about the room: use the server name query
/// param if specified. if not specified, asks other servers over federation
/// via room alias server name and room ID server name
#[tracing::instrument(skip_all, fields(%client), name = "join")]
pub(crate) async fn join_room_by_id_or_alias_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<join_room_by_id_or_alias::v3::Request>,
) -> Result<join_room_by_id_or_alias::v3::Response> {
let sender_user = body.sender_user();
let appservice_info = &body.appservice_info;
let body = &body.body;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias.clone()) {
| Ok(room_id) => {
banned_room_check(
&services,
sender_user,
Some(&room_id),
room_id.server_name(),
client,
)
.boxed()
.await?;
let mut servers = body.via.clone();
servers.extend(
services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
);
servers.extend(
services
.rooms
.state_cache
.invite_state(sender_user, &room_id)
.await
.unwrap_or_default()
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.map(|user| user.server_name().to_owned()),
);
if let Some(server) = room_id.server_name() {
servers.push(server.to_owned());
}
servers.sort_unstable();
servers.dedup();
shuffle(&mut servers);
(servers, room_id)
},
| Err(room_alias) => {
let (room_id, mut servers) = services
.rooms
.alias
.resolve_alias(&room_alias, Some(body.via.clone()))
.await?;
banned_room_check(
&services,
sender_user,
Some(&room_id),
Some(room_alias.server_name()),
client,
)
.await?;
let addl_via_servers = services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned);
let addl_state_servers = services
.rooms
.state_cache
.invite_state(sender_user, &room_id)
.await
.unwrap_or_default();
let mut addl_servers: Vec<_> = addl_state_servers
.iter()
.map(|event| event.get_field("sender"))
.filter_map(FlatOk::flat_ok)
.map(|user: &UserId| user.server_name().to_owned())
.stream()
.chain(addl_via_servers)
.collect()
.await;
addl_servers.sort_unstable();
addl_servers.dedup();
shuffle(&mut addl_servers);
servers.append(&mut addl_servers);
(servers, room_id)
},
};
let join_room_response = join_room_by_id_helper(
&services,
sender_user,
&room_id,
body.reason.clone(),
&servers,
body.third_party_signed.as_ref(),
appservice_info,
)
.boxed()
.await?;
Ok(join_room_by_id_or_alias::v3::Response { room_id: join_room_response.room_id })
}
pub async fn join_room_by_id_helper(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
reason: Option<String>,
servers: &[OwnedServerName],
third_party_signed: Option<&ThirdPartySigned>,
appservice_info: &Option<RegistrationInfo>,
) -> Result<join_room_by_id::v3::Response> {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let user_is_guest = services
.users
.is_deactivated(sender_user)
.await
.unwrap_or(false)
&& appservice_info.is_none();
if user_is_guest && !services.rooms.state_accessor.guest_can_join(room_id).await {
return Err!(Request(Forbidden("Guests are not allowed to join this room")));
}
if services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
{
debug_warn!("{sender_user} is already joined in {room_id}");
return Ok(join_room_by_id::v3::Response { room_id: room_id.into() });
}
let server_in_room = services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room_id)
.await;
// Only check our known membership if we're already in the room.
// See: https://forgejo.ellis.link/continuwuation/continuwuity/issues/855
let membership = if server_in_room {
services
.rooms
.state_accessor
.get_member(room_id, sender_user)
.await
} else {
debug!("Ignoring local state for join {room_id}, we aren't in the room yet.");
Ok(RoomMemberEventContent::new(MembershipState::Leave))
};
if let Ok(m) = membership {
if m.membership == MembershipState::Ban {
debug_warn!("{sender_user} is banned from {room_id} but attempted to join");
// TODO: return reason
return Err!(Request(Forbidden("You are banned from the room.")));
}
}
let local_join = server_in_room
|| servers.is_empty()
|| (servers.len() == 1 && services.globals.server_is_ours(&servers[0]));
if local_join {
join_room_by_id_helper_local(
services,
sender_user,
room_id,
reason,
servers,
third_party_signed,
state_lock,
)
.boxed()
.await?;
} else {
// Ask a remote server if we are not participating in this room
join_room_by_id_helper_remote(
services,
sender_user,
room_id,
reason,
servers,
third_party_signed,
state_lock,
)
.boxed()
.await?;
}
Ok(join_room_by_id::v3::Response::new(room_id.to_owned()))
}
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_remote")]
async fn join_room_by_id_helper_remote(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
reason: Option<String>,
servers: &[OwnedServerName],
_third_party_signed: Option<&ThirdPartySigned>,
state_lock: RoomMutexGuard,
) -> Result {
info!("Joining {room_id} over federation.");
let (make_join_response, remote_server) =
make_join_request(services, sender_user, room_id, servers).await?;
info!("make_join finished");
let Some(room_version_id) = make_join_response.room_version else {
return Err!(BadServerResponse("Remote room version is not supported by conduwuit"));
};
if !services.server.supported_room_version(&room_version_id) {
return Err!(BadServerResponse(
"Remote room version {room_version_id} is not supported by conduwuit"
));
}
let mut join_event_stub: CanonicalJsonObject =
serde_json::from_str(make_join_response.event.get()).map_err(|e| {
err!(BadServerResponse(warn!(
"Invalid make_join event json received from server: {e:?}"
)))
})?;
let join_authorized_via_users_server = {
use RoomVersionId::*;
if !matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6 | V7) {
join_event_stub
.get("content")
.map(|s| {
s.as_object()?
.get("join_authorised_via_users_server")?
.as_str()
})
.and_then(|s| OwnedUserId::try_from(s.unwrap_or_default()).ok())
} else {
None
}
};
join_event_stub.insert(
"origin".to_owned(),
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
);
join_event_stub.insert(
"origin_server_ts".to_owned(),
CanonicalJsonValue::Integer(
utils::millis_since_unix_epoch()
.try_into()
.expect("Timestamp is valid js_int value"),
),
);
join_event_stub.insert(
"content".to_owned(),
to_canonical_value(RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason,
join_authorized_via_users_server: join_authorized_via_users_server.clone(),
..RoomMemberEventContent::new(MembershipState::Join)
})
.expect("event is valid, we just created it"),
);
// We keep the "event_id" in the pdu only in v1 or
// v2 rooms
match room_version_id {
| RoomVersionId::V1 | RoomVersionId::V2 => {},
| _ => {
join_event_stub.remove("event_id");
},
}
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut join_event_stub, &room_version_id)?;
// Generate event id
let event_id = gen_event_id(&join_event_stub, &room_version_id)?;
// Add event_id back
join_event_stub
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let mut join_event = join_event_stub;
info!("Asking {remote_server} for send_join in room {room_id}");
let send_join_request = federation::membership::create_join_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
omit_members: false,
pdu: services
.sending
.convert_to_outgoing_federation_event(join_event.clone())
.await,
};
let send_join_response = match services
.sending
.send_synapse_request(&remote_server, send_join_request)
.await
{
| Ok(response) => response,
| Err(e) => {
error!("send_join failed: {e}");
return Err(e);
},
};
info!("send_join finished");
if join_authorized_via_users_server.is_some() {
if let Some(signed_raw) = &send_join_response.room_state.event {
debug_info!(
"There is a signed event with join_authorized_via_users_server. This room is \
probably using restricted joins. Adding signature to our event"
);
let (signed_event_id, signed_value) =
gen_event_id_canonical_json(signed_raw, &room_version_id).map_err(|e| {
err!(Request(BadJson(warn!(
"Could not convert event to canonical JSON: {e}"
))))
})?;
if signed_event_id != event_id {
return Err!(Request(BadJson(warn!(
%signed_event_id, %event_id,
"Server {remote_server} sent event with wrong event ID"
))));
}
match signed_value["signatures"]
.as_object()
.ok_or_else(|| {
err!(BadServerResponse(warn!(
"Server {remote_server} sent invalid signatures type"
)))
})
.and_then(|e| {
e.get(remote_server.as_str()).ok_or_else(|| {
err!(BadServerResponse(warn!(
"Server {remote_server} did not send its signature for a restricted \
room"
)))
})
}) {
| Ok(signature) => {
join_event
.get_mut("signatures")
.expect("we created a valid pdu")
.as_object_mut()
.expect("we created a valid pdu")
.insert(remote_server.to_string(), signature.clone());
},
| Err(e) => {
warn!(
"Server {remote_server} sent invalid signature in send_join signatures \
for event {signed_value:?}: {e:?}",
);
},
}
}
}
services
.rooms
.short
.get_or_create_shortroomid(room_id)
.await;
info!("Parsing join event");
let parsed_join_pdu = PduEvent::from_id_val(&event_id, join_event.clone())
.map_err(|e| err!(BadServerResponse("Invalid join event PDU: {e:?}")))?;
info!("Acquiring server signing keys for response events");
let resp_events = &send_join_response.room_state;
let resp_state = &resp_events.state;
let resp_auth = &resp_events.auth_chain;
services
.server_keys
.acquire_events_pubkeys(resp_auth.iter().chain(resp_state.iter()))
.await;
info!("Going through send_join response room_state");
let cork = services.db.cork_and_flush();
let state = send_join_response
.room_state
.state
.iter()
.stream()
.then(|pdu| {
services
.server_keys
.validate_and_add_event_id_no_fetch(pdu, &room_version_id)
})
.ready_filter_map(Result::ok)
.fold(HashMap::new(), |mut state, (event_id, value)| async move {
let pdu = match PduEvent::from_id_val(&event_id, value.clone()) {
| Ok(pdu) => pdu,
| Err(e) => {
debug_warn!("Invalid PDU in send_join response: {e:?}: {value:#?}");
return state;
},
};
services.rooms.outlier.add_pdu_outlier(&event_id, &value);
if let Some(state_key) = &pdu.state_key {
let shortstatekey = services
.rooms
.short
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)
.await;
state.insert(shortstatekey, pdu.event_id.clone());
}
state
})
.await;
drop(cork);
info!("Going through send_join response auth_chain");
let cork = services.db.cork_and_flush();
send_join_response
.room_state
.auth_chain
.iter()
.stream()
.then(|pdu| {
services
.server_keys
.validate_and_add_event_id_no_fetch(pdu, &room_version_id)
})
.ready_filter_map(Result::ok)
.ready_for_each(|(event_id, value)| {
services.rooms.outlier.add_pdu_outlier(&event_id, &value);
})
.await;
drop(cork);
debug!("Running send_join auth check");
let fetch_state = &state;
let state_fetch = |k: StateEventType, s: StateKey| async move {
let shortstatekey = services.rooms.short.get_shortstatekey(&k, &s).await.ok()?;
let event_id = fetch_state.get(&shortstatekey)?;
services.rooms.timeline.get_pdu(event_id).await.ok()
};
let auth_check = state_res::event_auth::auth_check(
&state_res::RoomVersion::new(&room_version_id)?,
&parsed_join_pdu,
None, // TODO: third party invite
|k, s| state_fetch(k.clone(), s.into()),
)
.await
.map_err(|e| err!(Request(Forbidden(warn!("Auth check failed: {e:?}")))))?;
if !auth_check {
return Err!(Request(Forbidden("Auth check failed")));
}
info!("Compressing state from send_join");
let compressed: CompressedState = services
.rooms
.state_compressor
.compress_state_events(state.iter().map(|(ssk, eid)| (ssk, eid.borrow())))
.collect()
.await;
debug!("Saving compressed state");
let HashSetCompressStateEvent {
shortstatehash: statehash_before_join,
added,
removed,
} = services
.rooms
.state_compressor
.save_state(room_id, Arc::new(compressed))
.await?;
debug!("Forcing state for new room");
services
.rooms
.state
.force_state(room_id, statehash_before_join, added, removed, &state_lock)
.await?;
info!("Updating joined counts for new room");
services
.rooms
.state_cache
.update_joined_count(room_id)
.await;
// We append to state before appending the pdu, so we don't have a moment in
// time with the pdu without it's state. This is okay because append_pdu can't
// fail.
let statehash_after_join = services
.rooms
.state
.append_to_state(&parsed_join_pdu)
.await?;
info!("Appending new room join event");
services
.rooms
.timeline
.append_pdu(
&parsed_join_pdu,
join_event,
once(parsed_join_pdu.event_id.borrow()),
&state_lock,
)
.await?;
info!("Setting final room state for new room");
// We set the room state after inserting the pdu, so that we never have a moment
// in time where events in the current room state do not exist
services
.rooms
.state
.set_room_state(room_id, statehash_after_join, &state_lock);
Ok(())
}
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_local")]
async fn join_room_by_id_helper_local(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
reason: Option<String>,
servers: &[OwnedServerName],
_third_party_signed: Option<&ThirdPartySigned>,
state_lock: RoomMutexGuard,
) -> Result {
debug_info!("We can join locally");
let join_rules_event_content = services
.rooms
.state_accessor
.room_state_get_content::<RoomJoinRulesEventContent>(
room_id,
&StateEventType::RoomJoinRules,
"",
)
.await;
let restriction_rooms = match join_rules_event_content {
| Ok(RoomJoinRulesEventContent {
join_rule: JoinRule::Restricted(restricted) | JoinRule::KnockRestricted(restricted),
}) => restricted
.allow
.into_iter()
.filter_map(|a| match a {
| AllowRule::RoomMembership(r) => Some(r.room_id),
| _ => None,
})
.collect(),
| _ => Vec::new(),
};
let join_authorized_via_users_server: Option<OwnedUserId> = {
if restriction_rooms
.iter()
.stream()
.any(|restriction_room_id| {
services
.rooms
.state_cache
.is_joined(sender_user, restriction_room_id)
})
.await
{
services
.rooms
.state_cache
.local_users_in_room(room_id)
.filter(|user| {
services.rooms.state_accessor.user_can_invite(
room_id,
user,
sender_user,
&state_lock,
)
})
.boxed()
.next()
.await
.map(ToOwned::to_owned)
} else {
None
}
};
let content = RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason: reason.clone(),
join_authorized_via_users_server,
..RoomMemberEventContent::new(MembershipState::Join)
};
// Try normal join first
let Err(error) = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(sender_user.to_string(), &content),
sender_user,
room_id,
&state_lock,
)
.await
else {
return Ok(());
};
if restriction_rooms.is_empty()
&& (servers.is_empty()
|| servers.len() == 1 && services.globals.server_is_ours(&servers[0]))
{
return Err(error);
}
warn!(
"We couldn't do the join locally, maybe federation can help to satisfy the restricted \
join requirements"
);
let Ok((make_join_response, remote_server)) =
make_join_request(services, sender_user, room_id, servers).await
else {
return Err(error);
};
let Some(room_version_id) = make_join_response.room_version else {
return Err!(BadServerResponse("Remote room version is not supported by conduwuit"));
};
if !services.server.supported_room_version(&room_version_id) {
return Err!(BadServerResponse(
"Remote room version {room_version_id} is not supported by conduwuit"
));
}
let mut join_event_stub: CanonicalJsonObject =
serde_json::from_str(make_join_response.event.get()).map_err(|e| {
err!(BadServerResponse("Invalid make_join event json received from server: {e:?}"))
})?;
let join_authorized_via_users_server = join_event_stub
.get("content")
.map(|s| {
s.as_object()?
.get("join_authorised_via_users_server")?
.as_str()
})
.and_then(|s| OwnedUserId::try_from(s.unwrap_or_default()).ok());
join_event_stub.insert(
"origin".to_owned(),
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
);
join_event_stub.insert(
"origin_server_ts".to_owned(),
CanonicalJsonValue::Integer(
utils::millis_since_unix_epoch()
.try_into()
.expect("Timestamp is valid js_int value"),
),
);
join_event_stub.insert(
"content".to_owned(),
to_canonical_value(RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason,
join_authorized_via_users_server,
..RoomMemberEventContent::new(MembershipState::Join)
})
.expect("event is valid, we just created it"),
);
// We keep the "event_id" in the pdu only in v1 or
// v2 rooms
match room_version_id {
| RoomVersionId::V1 | RoomVersionId::V2 => {},
| _ => {
join_event_stub.remove("event_id");
},
}
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut join_event_stub, &room_version_id)?;
// Generate event id
let event_id = gen_event_id(&join_event_stub, &room_version_id)?;
// Add event_id back
join_event_stub
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let join_event = join_event_stub;
let send_join_response = services
.sending
.send_synapse_request(
&remote_server,
federation::membership::create_join_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
omit_members: false,
pdu: services
.sending
.convert_to_outgoing_federation_event(join_event.clone())
.await,
},
)
.await?;
if let Some(signed_raw) = send_join_response.room_state.event {
let (signed_event_id, signed_value) =
gen_event_id_canonical_json(&signed_raw, &room_version_id).map_err(|e| {
err!(Request(BadJson(warn!("Could not convert event to canonical JSON: {e}"))))
})?;
if signed_event_id != event_id {
return Err!(Request(BadJson(
warn!(%signed_event_id, %event_id, "Server {remote_server} sent event with wrong event ID")
)));
}
drop(state_lock);
services
.rooms
.event_handler
.handle_incoming_pdu(&remote_server, room_id, &signed_event_id, signed_value, true)
.boxed()
.await?;
} else {
return Err(error);
}
Ok(())
}
async fn make_join_request(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
servers: &[OwnedServerName],
) -> Result<(federation::membership::prepare_join_event::v1::Response, OwnedServerName)> {
let mut make_join_response_and_server =
Err!(BadServerResponse("No server available to assist in joining."));
let mut make_join_counter: usize = 0;
let mut incompatible_room_version_count: usize = 0;
for remote_server in servers {
if services.globals.server_is_ours(remote_server) {
continue;
}
info!("Asking {remote_server} for make_join ({make_join_counter})");
let make_join_response = services
.sending
.send_federation_request(
remote_server,
federation::membership::prepare_join_event::v1::Request {
room_id: room_id.to_owned(),
user_id: sender_user.to_owned(),
ver: services.server.supported_room_versions().collect(),
},
)
.await;
trace!("make_join response: {:?}", make_join_response);
make_join_counter = make_join_counter.saturating_add(1);
if let Err(ref e) = make_join_response {
if matches!(
e.kind(),
ErrorKind::IncompatibleRoomVersion { .. } | ErrorKind::UnsupportedRoomVersion
) {
incompatible_room_version_count =
incompatible_room_version_count.saturating_add(1);
}
if incompatible_room_version_count > 15 {
info!(
"15 servers have responded with M_INCOMPATIBLE_ROOM_VERSION or \
M_UNSUPPORTED_ROOM_VERSION, assuming that conduwuit does not support the \
room version {room_id}: {e}"
);
make_join_response_and_server =
Err!(BadServerResponse("Room version is not supported by Conduwuit"));
return make_join_response_and_server;
}
if make_join_counter > 40 {
warn!(
"40 servers failed to provide valid make_join response, assuming no server \
can assist in joining."
);
make_join_response_and_server =
Err!(BadServerResponse("No server available to assist in joining."));
return make_join_response_and_server;
}
}
make_join_response_and_server = make_join_response.map(|r| (r, remote_server.clone()));
if make_join_response_and_server.is_ok() {
break;
}
}
make_join_response_and_server
}

View file

@ -0,0 +1,65 @@
use axum::extract::State;
use conduwuit::{Err, Result, matrix::pdu::PduBuilder};
use ruma::{
api::client::membership::kick_user,
events::room::member::{MembershipState, RoomMemberEventContent},
};
use crate::Ruma;
/// # `POST /_matrix/client/r0/rooms/{roomId}/kick`
///
/// Tries to send a kick event into the room.
pub(crate) async fn kick_user_route(
State(services): State<crate::State>,
body: Ruma<kick_user::v3::Request>,
) -> Result<kick_user::v3::Response> {
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let Ok(event) = services
.rooms
.state_accessor
.get_member(&body.room_id, &body.user_id)
.await
else {
// copy synapse's behaviour of returning 200 without any change to the state
// instead of erroring on left users
return Ok(kick_user::v3::Response::new());
};
if !matches!(
event.membership,
MembershipState::Invite | MembershipState::Knock | MembershipState::Join,
) {
return Err!(Request(Forbidden(
"Cannot kick a user who is not apart of the room (current membership: {})",
event.membership
)));
}
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Leave,
reason: body.reason.clone(),
is_direct: None,
join_authorized_via_users_server: None,
third_party_invite: None,
..event
}),
sender_user,
&body.room_id,
&state_lock,
)
.await?;
drop(state_lock);
Ok(kick_user::v3::Response::new())
}

View file

@ -0,0 +1,770 @@
use std::{borrow::Borrow, collections::HashMap, iter::once, sync::Arc};
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{
Err, Result, debug, debug_info, debug_warn, err, info,
matrix::{
event::{Event, gen_event_id},
pdu::{PduBuilder, PduEvent},
},
result::FlatOk,
trace,
utils::{self, shuffle, stream::IterStream},
warn,
};
use futures::{FutureExt, StreamExt};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName, RoomId,
RoomVersionId, UserId,
api::{
client::knock::knock_room,
federation::{self},
},
canonical_json::to_canonical_value,
events::{
StateEventType,
room::{
join_rules::{AllowRule, JoinRule},
member::{MembershipState, RoomMemberEventContent},
},
},
};
use service::{
Services,
rooms::{
state::RoomMutexGuard,
state_compressor::{CompressedState, HashSetCompressStateEvent},
},
};
use super::{banned_room_check, join::join_room_by_id_helper};
use crate::Ruma;
/// # `POST /_matrix/client/*/knock/{roomIdOrAlias}`
///
/// Tries to knock the room to ask permission to join for the sender user.
#[tracing::instrument(skip_all, fields(%client), name = "knock")]
pub(crate) async fn knock_room_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<knock_room::v3::Request>,
) -> Result<knock_room::v3::Response> {
let sender_user = body.sender_user();
let body = &body.body;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias.clone()) {
| Ok(room_id) => {
banned_room_check(
&services,
sender_user,
Some(&room_id),
room_id.server_name(),
client,
)
.await?;
let mut servers = body.via.clone();
servers.extend(
services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
);
servers.extend(
services
.rooms
.state_cache
.invite_state(sender_user, &room_id)
.await
.unwrap_or_default()
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.map(|user| user.server_name().to_owned()),
);
if let Some(server) = room_id.server_name() {
servers.push(server.to_owned());
}
servers.sort_unstable();
servers.dedup();
shuffle(&mut servers);
(servers, room_id)
},
| Err(room_alias) => {
let (room_id, mut servers) = services
.rooms
.alias
.resolve_alias(&room_alias, Some(body.via.clone()))
.await?;
banned_room_check(
&services,
sender_user,
Some(&room_id),
Some(room_alias.server_name()),
client,
)
.await?;
let addl_via_servers = services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned);
let addl_state_servers = services
.rooms
.state_cache
.invite_state(sender_user, &room_id)
.await
.unwrap_or_default();
let mut addl_servers: Vec<_> = addl_state_servers
.iter()
.map(|event| event.get_field("sender"))
.filter_map(FlatOk::flat_ok)
.map(|user: &UserId| user.server_name().to_owned())
.stream()
.chain(addl_via_servers)
.collect()
.await;
addl_servers.sort_unstable();
addl_servers.dedup();
shuffle(&mut addl_servers);
servers.append(&mut addl_servers);
(servers, room_id)
},
};
knock_room_by_id_helper(&services, sender_user, &room_id, body.reason.clone(), &servers)
.boxed()
.await
}
async fn knock_room_by_id_helper(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
reason: Option<String>,
servers: &[OwnedServerName],
) -> Result<knock_room::v3::Response> {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
if services
.rooms
.state_cache
.is_invited(sender_user, room_id)
.await
{
debug_warn!("{sender_user} is already invited in {room_id} but attempted to knock");
return Err!(Request(Forbidden(
"You cannot knock on a room you are already invited/accepted to."
)));
}
if services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
{
debug_warn!("{sender_user} is already joined in {room_id} but attempted to knock");
return Err!(Request(Forbidden("You cannot knock on a room you are already joined in.")));
}
if services
.rooms
.state_cache
.is_knocked(sender_user, room_id)
.await
{
debug_warn!("{sender_user} is already knocked in {room_id}");
return Ok(knock_room::v3::Response { room_id: room_id.into() });
}
if let Ok(membership) = services
.rooms
.state_accessor
.get_member(room_id, sender_user)
.await
{
if membership.membership == MembershipState::Ban {
debug_warn!("{sender_user} is banned from {room_id} but attempted to knock");
return Err!(Request(Forbidden("You cannot knock on a room you are banned from.")));
}
}
// For knock_restricted rooms, check if the user meets the restricted conditions
// If they do, attempt to join instead of knock
// This is not mentioned in the spec, but should be allowable (we're allowed to
// auto-join invites to knocked rooms)
let join_rule = services.rooms.state_accessor.get_join_rules(room_id).await;
if let JoinRule::KnockRestricted(restricted) = &join_rule {
let restriction_rooms: Vec<_> = restricted
.allow
.iter()
.filter_map(|a| match a {
| AllowRule::RoomMembership(r) => Some(&r.room_id),
| _ => None,
})
.collect();
// Check if the user is in any of the allowed rooms
let mut user_meets_restrictions = false;
for restriction_room_id in &restriction_rooms {
if services
.rooms
.state_cache
.is_joined(sender_user, restriction_room_id)
.await
{
user_meets_restrictions = true;
break;
}
}
// If the user meets the restrictions, try joining instead
if user_meets_restrictions {
debug_info!(
"{sender_user} meets the restricted criteria in knock_restricted room \
{room_id}, attempting to join instead of knock"
);
// For this case, we need to drop the state lock and get a new one in
// join_room_by_id_helper We need to release the lock here and let
// join_room_by_id_helper acquire it again
drop(state_lock);
match join_room_by_id_helper(
services,
sender_user,
room_id,
reason.clone(),
servers,
None,
&None,
)
.await
{
| Ok(_) => return Ok(knock_room::v3::Response::new(room_id.to_owned())),
| Err(e) => {
debug_warn!(
"Failed to convert knock to join for {sender_user} in {room_id}: {e:?}"
);
// Get a new state lock for the remaining knock logic
let new_state_lock = services.rooms.state.mutex.lock(room_id).await;
let server_in_room = services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room_id)
.await;
let local_knock = server_in_room
|| servers.is_empty()
|| (servers.len() == 1 && services.globals.server_is_ours(&servers[0]));
if local_knock {
knock_room_helper_local(
services,
sender_user,
room_id,
reason,
servers,
new_state_lock,
)
.boxed()
.await?;
} else {
knock_room_helper_remote(
services,
sender_user,
room_id,
reason,
servers,
new_state_lock,
)
.boxed()
.await?;
}
return Ok(knock_room::v3::Response::new(room_id.to_owned()));
},
}
}
} else if !matches!(join_rule, JoinRule::Knock | JoinRule::KnockRestricted(_)) {
debug_warn!(
"{sender_user} attempted to knock on room {room_id} but its join rule is \
{join_rule:?}, not knock or knock_restricted"
);
}
let server_in_room = services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room_id)
.await;
let local_knock = server_in_room
|| servers.is_empty()
|| (servers.len() == 1 && services.globals.server_is_ours(&servers[0]));
if local_knock {
knock_room_helper_local(services, sender_user, room_id, reason, servers, state_lock)
.boxed()
.await?;
} else {
knock_room_helper_remote(services, sender_user, room_id, reason, servers, state_lock)
.boxed()
.await?;
}
Ok(knock_room::v3::Response::new(room_id.to_owned()))
}
async fn knock_room_helper_local(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
reason: Option<String>,
servers: &[OwnedServerName],
state_lock: RoomMutexGuard,
) -> Result {
debug_info!("We can knock locally");
let room_version_id = services.rooms.state.get_room_version(room_id).await?;
if matches!(
room_version_id,
RoomVersionId::V1
| RoomVersionId::V2
| RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5
| RoomVersionId::V6
) {
return Err!(Request(Forbidden("This room does not support knocking.")));
}
let content = RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason: reason.clone(),
..RoomMemberEventContent::new(MembershipState::Knock)
};
// Try normal knock first
let Err(error) = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(sender_user.to_string(), &content),
sender_user,
room_id,
&state_lock,
)
.await
else {
return Ok(());
};
if servers.is_empty() || (servers.len() == 1 && services.globals.server_is_ours(&servers[0]))
{
return Err(error);
}
warn!("We couldn't do the knock locally, maybe federation can help to satisfy the knock");
let (make_knock_response, remote_server) =
make_knock_request(services, sender_user, room_id, servers).await?;
info!("make_knock finished");
let room_version_id = make_knock_response.room_version;
if !services.server.supported_room_version(&room_version_id) {
return Err!(BadServerResponse(
"Remote room version {room_version_id} is not supported by conduwuit"
));
}
let mut knock_event_stub = serde_json::from_str::<CanonicalJsonObject>(
make_knock_response.event.get(),
)
.map_err(|e| {
err!(BadServerResponse("Invalid make_knock event json received from server: {e:?}"))
})?;
knock_event_stub.insert(
"origin".to_owned(),
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
);
knock_event_stub.insert(
"origin_server_ts".to_owned(),
CanonicalJsonValue::Integer(
utils::millis_since_unix_epoch()
.try_into()
.expect("Timestamp is valid js_int value"),
),
);
knock_event_stub.insert(
"content".to_owned(),
to_canonical_value(RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason,
..RoomMemberEventContent::new(MembershipState::Knock)
})
.expect("event is valid, we just created it"),
);
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut knock_event_stub, &room_version_id)?;
// Generate event id
let event_id = gen_event_id(&knock_event_stub, &room_version_id)?;
// Add event_id
knock_event_stub
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let knock_event = knock_event_stub;
info!("Asking {remote_server} for send_knock in room {room_id}");
let send_knock_request = federation::knock::send_knock::v1::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
pdu: services
.sending
.convert_to_outgoing_federation_event(knock_event.clone())
.await,
};
let send_knock_response = services
.sending
.send_federation_request(&remote_server, send_knock_request)
.await?;
info!("send_knock finished");
services
.rooms
.short
.get_or_create_shortroomid(room_id)
.await;
info!("Parsing knock event");
let parsed_knock_pdu = PduEvent::from_id_val(&event_id, knock_event.clone())
.map_err(|e| err!(BadServerResponse("Invalid knock event PDU: {e:?}")))?;
info!("Updating membership locally to knock state with provided stripped state events");
services
.rooms
.state_cache
.update_membership(
room_id,
sender_user,
parsed_knock_pdu
.get_content::<RoomMemberEventContent>()
.expect("we just created this"),
sender_user,
Some(send_knock_response.knock_room_state),
None,
false,
)
.await?;
info!("Appending room knock event locally");
services
.rooms
.timeline
.append_pdu(
&parsed_knock_pdu,
knock_event,
once(parsed_knock_pdu.event_id.borrow()),
&state_lock,
)
.await?;
Ok(())
}
async fn knock_room_helper_remote(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
reason: Option<String>,
servers: &[OwnedServerName],
state_lock: RoomMutexGuard,
) -> Result {
info!("Knocking {room_id} over federation.");
let (make_knock_response, remote_server) =
make_knock_request(services, sender_user, room_id, servers).await?;
info!("make_knock finished");
let room_version_id = make_knock_response.room_version;
if !services.server.supported_room_version(&room_version_id) {
return Err!(BadServerResponse(
"Remote room version {room_version_id} is not supported by conduwuit"
));
}
let mut knock_event_stub: CanonicalJsonObject =
serde_json::from_str(make_knock_response.event.get()).map_err(|e| {
err!(BadServerResponse("Invalid make_knock event json received from server: {e:?}"))
})?;
knock_event_stub.insert(
"origin".to_owned(),
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
);
knock_event_stub.insert(
"origin_server_ts".to_owned(),
CanonicalJsonValue::Integer(
utils::millis_since_unix_epoch()
.try_into()
.expect("Timestamp is valid js_int value"),
),
);
knock_event_stub.insert(
"content".to_owned(),
to_canonical_value(RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason,
..RoomMemberEventContent::new(MembershipState::Knock)
})
.expect("event is valid, we just created it"),
);
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut knock_event_stub, &room_version_id)?;
// Generate event id
let event_id = gen_event_id(&knock_event_stub, &room_version_id)?;
// Add event_id
knock_event_stub
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let knock_event = knock_event_stub;
info!("Asking {remote_server} for send_knock in room {room_id}");
let send_knock_request = federation::knock::send_knock::v1::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
pdu: services
.sending
.convert_to_outgoing_federation_event(knock_event.clone())
.await,
};
let send_knock_response = services
.sending
.send_federation_request(&remote_server, send_knock_request)
.await?;
info!("send_knock finished");
services
.rooms
.short
.get_or_create_shortroomid(room_id)
.await;
info!("Parsing knock event");
let parsed_knock_pdu = PduEvent::from_id_val(&event_id, knock_event.clone())
.map_err(|e| err!(BadServerResponse("Invalid knock event PDU: {e:?}")))?;
info!("Going through send_knock response knock state events");
let state = send_knock_response
.knock_room_state
.iter()
.map(|event| serde_json::from_str::<CanonicalJsonObject>(event.clone().into_json().get()))
.filter_map(Result::ok);
let mut state_map: HashMap<u64, OwnedEventId> = HashMap::new();
for event in state {
let Some(state_key) = event.get("state_key") else {
debug_warn!("send_knock stripped state event missing state_key: {event:?}");
continue;
};
let Some(event_type) = event.get("type") else {
debug_warn!("send_knock stripped state event missing event type: {event:?}");
continue;
};
let Ok(state_key) = serde_json::from_value::<String>(state_key.clone().into()) else {
debug_warn!("send_knock stripped state event has invalid state_key: {event:?}");
continue;
};
let Ok(event_type) = serde_json::from_value::<StateEventType>(event_type.clone().into())
else {
debug_warn!("send_knock stripped state event has invalid event type: {event:?}");
continue;
};
let event_id = gen_event_id(&event, &room_version_id)?;
let shortstatekey = services
.rooms
.short
.get_or_create_shortstatekey(&event_type, &state_key)
.await;
services.rooms.outlier.add_pdu_outlier(&event_id, &event);
state_map.insert(shortstatekey, event_id.clone());
}
info!("Compressing state from send_knock");
let compressed: CompressedState = services
.rooms
.state_compressor
.compress_state_events(state_map.iter().map(|(ssk, eid)| (ssk, eid.borrow())))
.collect()
.await;
debug!("Saving compressed state");
let HashSetCompressStateEvent {
shortstatehash: statehash_before_knock,
added,
removed,
} = services
.rooms
.state_compressor
.save_state(room_id, Arc::new(compressed))
.await?;
debug!("Forcing state for new room");
services
.rooms
.state
.force_state(room_id, statehash_before_knock, added, removed, &state_lock)
.await?;
let statehash_after_knock = services
.rooms
.state
.append_to_state(&parsed_knock_pdu)
.await?;
info!("Updating membership locally to knock state with provided stripped state events");
services
.rooms
.state_cache
.update_membership(
room_id,
sender_user,
parsed_knock_pdu
.get_content::<RoomMemberEventContent>()
.expect("we just created this"),
sender_user,
Some(send_knock_response.knock_room_state),
None,
false,
)
.await?;
info!("Appending room knock event locally");
services
.rooms
.timeline
.append_pdu(
&parsed_knock_pdu,
knock_event,
once(parsed_knock_pdu.event_id.borrow()),
&state_lock,
)
.await?;
info!("Setting final room state for new room");
// We set the room state after inserting the pdu, so that we never have a moment
// in time where events in the current room state do not exist
services
.rooms
.state
.set_room_state(room_id, statehash_after_knock, &state_lock);
Ok(())
}
async fn make_knock_request(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
servers: &[OwnedServerName],
) -> Result<(federation::knock::create_knock_event_template::v1::Response, OwnedServerName)> {
let mut make_knock_response_and_server =
Err!(BadServerResponse("No server available to assist in knocking."));
let mut make_knock_counter: usize = 0;
for remote_server in servers {
if services.globals.server_is_ours(remote_server) {
continue;
}
info!("Asking {remote_server} for make_knock ({make_knock_counter})");
let make_knock_response = services
.sending
.send_federation_request(
remote_server,
federation::knock::create_knock_event_template::v1::Request {
room_id: room_id.to_owned(),
user_id: sender_user.to_owned(),
ver: services.server.supported_room_versions().collect(),
},
)
.await;
trace!("make_knock response: {make_knock_response:?}");
make_knock_counter = make_knock_counter.saturating_add(1);
make_knock_response_and_server = make_knock_response.map(|r| (r, remote_server.clone()));
if make_knock_response_and_server.is_ok() {
break;
}
if make_knock_counter > 40 {
warn!(
"50 servers failed to provide valid make_knock response, assuming no server can \
assist in knocking."
);
make_knock_response_and_server =
Err!(BadServerResponse("No server available to assist in knocking."));
return make_knock_response_and_server;
}
}
make_knock_response_and_server
}

View file

@ -0,0 +1,386 @@
use std::collections::HashSet;
use axum::extract::State;
use conduwuit::{
Err, Result, debug_info, debug_warn, err,
matrix::{event::gen_event_id, pdu::PduBuilder},
utils::{self, FutureBoolExt, future::ReadyEqExt},
warn,
};
use futures::{FutureExt, StreamExt, TryFutureExt, pin_mut};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedServerName, RoomId, RoomVersionId, UserId,
api::{
client::membership::leave_room,
federation::{self},
},
events::{
StateEventType,
room::member::{MembershipState, RoomMemberEventContent},
},
};
use service::Services;
use crate::Ruma;
/// # `POST /_matrix/client/v3/rooms/{roomId}/leave`
///
/// Tries to leave the sender user from a room.
///
/// - This should always work if the user is currently joined.
pub(crate) async fn leave_room_route(
State(services): State<crate::State>,
body: Ruma<leave_room::v3::Request>,
) -> Result<leave_room::v3::Response> {
leave_room(&services, body.sender_user(), &body.room_id, body.reason.clone())
.boxed()
.await
.map(|()| leave_room::v3::Response::new())
}
// Make a user leave all their joined rooms, rescinds knocks, forgets all rooms,
// and ignores errors
pub async fn leave_all_rooms(services: &Services, user_id: &UserId) {
let rooms_joined = services
.rooms
.state_cache
.rooms_joined(user_id)
.map(ToOwned::to_owned);
let rooms_invited = services
.rooms
.state_cache
.rooms_invited(user_id)
.map(|(r, _)| r);
let rooms_knocked = services
.rooms
.state_cache
.rooms_knocked(user_id)
.map(|(r, _)| r);
let all_rooms: Vec<_> = rooms_joined
.chain(rooms_invited)
.chain(rooms_knocked)
.collect()
.await;
for room_id in all_rooms {
// ignore errors
if let Err(e) = leave_room(services, user_id, &room_id, None).boxed().await {
warn!(%user_id, "Failed to leave {room_id} remotely: {e}");
}
services.rooms.state_cache.forget(&room_id, user_id);
}
}
pub async fn leave_room(
services: &Services,
user_id: &UserId,
room_id: &RoomId,
reason: Option<String>,
) -> Result {
let default_member_content = RoomMemberEventContent {
membership: MembershipState::Leave,
reason: reason.clone(),
join_authorized_via_users_server: None,
is_direct: None,
avatar_url: None,
displayname: None,
third_party_invite: None,
blurhash: None,
redact_events: None,
};
let is_banned = services.rooms.metadata.is_banned(room_id);
let is_disabled = services.rooms.metadata.is_disabled(room_id);
pin_mut!(is_banned, is_disabled);
if is_banned.or(is_disabled).await {
// the room is banned/disabled, the room must be rejected locally since we
// cant/dont want to federate with this server
services
.rooms
.state_cache
.update_membership(
room_id,
user_id,
default_member_content,
user_id,
None,
None,
true,
)
.await?;
return Ok(());
}
let dont_have_room = services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room_id)
.eq(&false);
let not_knocked = services
.rooms
.state_cache
.is_knocked(user_id, room_id)
.eq(&false);
// Ask a remote server if we don't have this room and are not knocking on it
if dont_have_room.and(not_knocked).await {
if let Err(e) = remote_leave_room(services, user_id, room_id, reason.clone())
.boxed()
.await
{
warn!(%user_id, "Failed to leave room {room_id} remotely: {e}");
// Don't tell the client about this error
}
let last_state = services
.rooms
.state_cache
.invite_state(user_id, room_id)
.or_else(|_| services.rooms.state_cache.knock_state(user_id, room_id))
.or_else(|_| services.rooms.state_cache.left_state(user_id, room_id))
.await
.ok();
// We always drop the invite, we can't rely on other servers
services
.rooms
.state_cache
.update_membership(
room_id,
user_id,
default_member_content,
user_id,
last_state,
None,
true,
)
.await?;
} else {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let Ok(event) = services
.rooms
.state_accessor
.room_state_get_content::<RoomMemberEventContent>(
room_id,
&StateEventType::RoomMember,
user_id.as_str(),
)
.await
else {
debug_warn!(
"Trying to leave a room you are not a member of, marking room as left locally."
);
return services
.rooms
.state_cache
.update_membership(
room_id,
user_id,
default_member_content,
user_id,
None,
None,
true,
)
.await;
};
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Leave,
reason,
join_authorized_via_users_server: None,
is_direct: None,
..event
}),
user_id,
room_id,
&state_lock,
)
.await?;
}
Ok(())
}
async fn remote_leave_room(
services: &Services,
user_id: &UserId,
room_id: &RoomId,
reason: Option<String>,
) -> Result<()> {
let mut make_leave_response_and_server =
Err!(BadServerResponse("No remote server available to assist in leaving {room_id}."));
let mut servers: HashSet<OwnedServerName> = services
.rooms
.state_cache
.servers_invite_via(room_id)
.map(ToOwned::to_owned)
.collect()
.await;
match services
.rooms
.state_cache
.invite_state(user_id, room_id)
.await
{
| Ok(invite_state) => {
servers.extend(
invite_state
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.map(|user| user.server_name().to_owned()),
);
},
| _ => {
match services
.rooms
.state_cache
.knock_state(user_id, room_id)
.await
{
| Ok(knock_state) => {
servers.extend(
knock_state
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.filter_map(|sender| {
if !services.globals.user_is_local(sender) {
Some(sender.server_name().to_owned())
} else {
None
}
}),
);
},
| _ => {},
}
},
}
if let Some(room_id_server_name) = room_id.server_name() {
servers.insert(room_id_server_name.to_owned());
}
debug_info!("servers in remote_leave_room: {servers:?}");
for remote_server in servers {
let make_leave_response = services
.sending
.send_federation_request(
&remote_server,
federation::membership::prepare_leave_event::v1::Request {
room_id: room_id.to_owned(),
user_id: user_id.to_owned(),
},
)
.await;
make_leave_response_and_server = make_leave_response.map(|r| (r, remote_server));
if make_leave_response_and_server.is_ok() {
break;
}
}
let (make_leave_response, remote_server) = make_leave_response_and_server?;
let Some(room_version_id) = make_leave_response.room_version else {
return Err!(BadServerResponse(warn!(
"No room version was returned by {remote_server} for {room_id}, room version is \
likely not supported by conduwuit"
)));
};
if !services.server.supported_room_version(&room_version_id) {
return Err!(BadServerResponse(warn!(
"Remote room version {room_version_id} for {room_id} is not supported by conduwuit",
)));
}
let mut leave_event_stub = serde_json::from_str::<CanonicalJsonObject>(
make_leave_response.event.get(),
)
.map_err(|e| {
err!(BadServerResponse(warn!(
"Invalid make_leave event json received from {remote_server} for {room_id}: {e:?}"
)))
})?;
// TODO: Is origin needed?
leave_event_stub.insert(
"origin".to_owned(),
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
);
leave_event_stub.insert(
"origin_server_ts".to_owned(),
CanonicalJsonValue::Integer(
utils::millis_since_unix_epoch()
.try_into()
.expect("Timestamp is valid js_int value"),
),
);
// Inject the reason key into the event content dict if it exists
if let Some(reason) = reason {
if let Some(CanonicalJsonValue::Object(content)) = leave_event_stub.get_mut("content") {
content.insert("reason".to_owned(), CanonicalJsonValue::String(reason));
}
}
// room v3 and above removed the "event_id" field from remote PDU format
match room_version_id {
| RoomVersionId::V1 | RoomVersionId::V2 => {},
| _ => {
leave_event_stub.remove("event_id");
},
}
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut leave_event_stub, &room_version_id)?;
// Generate event id
let event_id = gen_event_id(&leave_event_stub, &room_version_id)?;
// Add event_id back
leave_event_stub
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let leave_event = leave_event_stub;
services
.sending
.send_federation_request(
&remote_server,
federation::membership::create_leave_event::v2::Request {
room_id: room_id.to_owned(),
event_id,
pdu: services
.sending
.convert_to_outgoing_federation_event(leave_event.clone())
.await,
},
)
.await?;
Ok(())
}

View file

@ -0,0 +1,147 @@
use axum::extract::State;
use conduwuit::{
Err, Event, Result, at,
utils::{
future::TryExtExt,
stream::{BroadbandExt, ReadyExt},
},
};
use futures::{FutureExt, StreamExt, future::join};
use ruma::{
api::client::membership::{
get_member_events::{self, v3::MembershipEventFilter},
joined_members::{self, v3::RoomMember},
},
events::{
StateEventType,
room::member::{MembershipState, RoomMemberEventContent},
},
};
use crate::Ruma;
/// # `POST /_matrix/client/r0/rooms/{roomId}/members`
///
/// Lists all joined users in a room (TODO: at a specific point in time, with a
/// specific membership).
///
/// - Only works if the user is currently joined
pub(crate) async fn get_member_events_route(
State(services): State<crate::State>,
body: Ruma<get_member_events::v3::Request>,
) -> Result<get_member_events::v3::Response> {
let sender_user = body.sender_user();
let membership = body.membership.as_ref();
let not_membership = body.not_membership.as_ref();
if !services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, &body.room_id)
.await
{
return Err!(Request(Forbidden("You don't have permission to view this room.")));
}
Ok(get_member_events::v3::Response {
chunk: services
.rooms
.state_accessor
.room_state_full(&body.room_id)
.ready_filter_map(Result::ok)
.ready_filter(|((ty, _), _)| *ty == StateEventType::RoomMember)
.map(at!(1))
.ready_filter_map(|pdu| membership_filter(pdu, membership, not_membership))
.map(Event::into_format)
.collect()
.boxed()
.await,
})
}
/// # `POST /_matrix/client/r0/rooms/{roomId}/joined_members`
///
/// Lists all members of a room.
///
/// - The sender user must be in the room
/// - TODO: An appservice just needs a puppet joined
pub(crate) async fn joined_members_route(
State(services): State<crate::State>,
body: Ruma<joined_members::v3::Request>,
) -> Result<joined_members::v3::Response> {
if !services
.rooms
.state_accessor
.user_can_see_state_events(body.sender_user(), &body.room_id)
.await
{
return Err!(Request(Forbidden("You don't have permission to view this room.")));
}
Ok(joined_members::v3::Response {
joined: services
.rooms
.state_cache
.room_members(&body.room_id)
.map(ToOwned::to_owned)
.broad_then(|user_id| async move {
let (display_name, avatar_url) = join(
services.users.displayname(&user_id).ok(),
services.users.avatar_url(&user_id).ok(),
)
.await;
(user_id, RoomMember { display_name, avatar_url })
})
.collect()
.await,
})
}
fn membership_filter<Pdu: Event>(
pdu: Pdu,
for_membership: Option<&MembershipEventFilter>,
not_membership: Option<&MembershipEventFilter>,
) -> Option<impl Event> {
let membership_state_filter = match for_membership {
| Some(MembershipEventFilter::Ban) => MembershipState::Ban,
| Some(MembershipEventFilter::Invite) => MembershipState::Invite,
| Some(MembershipEventFilter::Knock) => MembershipState::Knock,
| Some(MembershipEventFilter::Leave) => MembershipState::Leave,
| Some(_) | None => MembershipState::Join,
};
let not_membership_state_filter = match not_membership {
| Some(MembershipEventFilter::Ban) => MembershipState::Ban,
| Some(MembershipEventFilter::Invite) => MembershipState::Invite,
| Some(MembershipEventFilter::Join) => MembershipState::Join,
| Some(MembershipEventFilter::Knock) => MembershipState::Knock,
| Some(_) | None => MembershipState::Leave,
};
let evt_membership = pdu.get_content::<RoomMemberEventContent>().ok()?.membership;
if for_membership.is_some() && not_membership.is_some() {
if membership_state_filter != evt_membership
|| not_membership_state_filter == evt_membership
{
None
} else {
Some(pdu)
}
} else if for_membership.is_some() && not_membership.is_none() {
if membership_state_filter != evt_membership {
None
} else {
Some(pdu)
}
} else if not_membership.is_some() && for_membership.is_none() {
if not_membership_state_filter == evt_membership {
None
} else {
Some(pdu)
}
} else {
Some(pdu)
}
}

View file

@ -0,0 +1,156 @@
mod ban;
mod forget;
mod invite;
mod join;
mod kick;
mod knock;
mod leave;
mod members;
mod unban;
use std::net::IpAddr;
use axum::extract::State;
use conduwuit::{Err, Result, warn};
use futures::{FutureExt, StreamExt};
use ruma::{OwnedRoomId, RoomId, ServerName, UserId, api::client::membership::joined_rooms};
use service::Services;
pub(crate) use self::{
ban::ban_user_route,
forget::forget_room_route,
invite::{invite_helper, invite_user_route},
join::{join_room_by_id_or_alias_route, join_room_by_id_route},
kick::kick_user_route,
knock::knock_room_route,
leave::leave_room_route,
members::{get_member_events_route, joined_members_route},
unban::unban_user_route,
};
pub use self::{
join::join_room_by_id_helper,
leave::{leave_all_rooms, leave_room},
};
use crate::{Ruma, client::full_user_deactivate};
/// # `POST /_matrix/client/r0/joined_rooms`
///
/// Lists all rooms the user has joined.
pub(crate) async fn joined_rooms_route(
State(services): State<crate::State>,
body: Ruma<joined_rooms::v3::Request>,
) -> Result<joined_rooms::v3::Response> {
Ok(joined_rooms::v3::Response {
joined_rooms: services
.rooms
.state_cache
.rooms_joined(body.sender_user())
.map(ToOwned::to_owned)
.collect()
.await,
})
}
/// Checks if the room is banned in any way possible and the sender user is not
/// an admin.
///
/// Performs automatic deactivation if `auto_deactivate_banned_room_attempts` is
/// enabled
#[tracing::instrument(skip(services))]
pub(crate) async fn banned_room_check(
services: &Services,
user_id: &UserId,
room_id: Option<&RoomId>,
server_name: Option<&ServerName>,
client_ip: IpAddr,
) -> Result {
if services.users.is_admin(user_id).await {
return Ok(());
}
if let Some(room_id) = room_id {
if services.rooms.metadata.is_banned(room_id).await
|| services
.moderation
.is_remote_server_forbidden(room_id.server_name().expect("legacy room mxid"))
{
warn!(
"User {user_id} who is not an admin attempted to send an invite for or \
attempted to join a banned room or banned room server name: {room_id}"
);
if services.server.config.auto_deactivate_banned_room_attempts {
warn!(
"Automatically deactivating user {user_id} due to attempted banned room join"
);
if services.server.config.admin_room_notices {
services
.admin
.send_text(&format!(
"Automatically deactivating user {user_id} due to attempted banned \
room join from IP {client_ip}"
))
.await;
}
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(user_id)
.map(Into::into)
.collect()
.await;
full_user_deactivate(services, user_id, &all_joined_rooms)
.boxed()
.await?;
}
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
}
} else if let Some(server_name) = server_name {
if services
.config
.forbidden_remote_server_names
.is_match(server_name.host())
{
warn!(
"User {user_id} who is not an admin tried joining a room which has the server \
name {server_name} that is globally forbidden. Rejecting.",
);
if services.server.config.auto_deactivate_banned_room_attempts {
warn!(
"Automatically deactivating user {user_id} due to attempted banned room join"
);
if services.server.config.admin_room_notices {
services
.admin
.send_text(&format!(
"Automatically deactivating user {user_id} due to attempted banned \
room join from IP {client_ip}"
))
.await;
}
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(user_id)
.map(Into::into)
.collect()
.await;
full_user_deactivate(services, user_id, &all_joined_rooms)
.boxed()
.await?;
}
return Err!(Request(Forbidden("This remote server is banned on this homeserver.")));
}
}
Ok(())
}

View file

@ -0,0 +1,58 @@
use axum::extract::State;
use conduwuit::{Err, Result, matrix::pdu::PduBuilder};
use ruma::{
api::client::membership::unban_user,
events::room::member::{MembershipState, RoomMemberEventContent},
};
use crate::Ruma;
/// # `POST /_matrix/client/r0/rooms/{roomId}/unban`
///
/// Tries to send an unban event into the room.
pub(crate) async fn unban_user_route(
State(services): State<crate::State>,
body: Ruma<unban_user::v3::Request>,
) -> Result<unban_user::v3::Response> {
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let current_member_content = services
.rooms
.state_accessor
.get_member(&body.room_id, &body.user_id)
.await
.unwrap_or_else(|_| RoomMemberEventContent::new(MembershipState::Leave));
if current_member_content.membership != MembershipState::Ban {
return Err!(Request(Forbidden(
"Cannot unban a user who is not banned (current membership: {})",
current_member_content.membership
)));
}
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Leave,
reason: body.reason.clone(),
join_authorized_via_users_server: None,
third_party_invite: None,
is_direct: None,
..current_member_content
}),
sender_user,
&body.room_id,
&state_lock,
)
.await?;
drop(state_lock);
Ok(unban_user::v3::Response::new())
}

View file

@ -1,12 +1,11 @@
use core::panic;
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Result, at, Err, Result, at,
matrix::{ matrix::{
Event, event::{Event, Matches},
pdu::{PduCount, PduEvent}, pdu::PduCount,
}, },
ref_at,
utils::{ utils::{
IterStream, ReadyExt, IterStream, ReadyExt,
result::{FlatOk, LogErr}, result::{FlatOk, LogErr},
@ -34,6 +33,7 @@ use ruma::{
}, },
serde::Raw, serde::Raw,
}; };
use tracing::warn;
use crate::Ruma; use crate::Ruma;
@ -73,7 +73,7 @@ pub(crate) async fn get_message_events_route(
) -> Result<get_message_events::v3::Response> { ) -> Result<get_message_events::v3::Response> {
debug_assert!(IGNORED_MESSAGE_TYPES.is_sorted(), "IGNORED_MESSAGE_TYPES is not sorted"); debug_assert!(IGNORED_MESSAGE_TYPES.is_sorted(), "IGNORED_MESSAGE_TYPES is not sorted");
let sender_user = body.sender_user(); let sender_user = body.sender_user();
let sender_device = body.sender_device.as_ref(); let sender_device = body.sender_device.as_deref();
let room_id = &body.room_id; let room_id = &body.room_id;
let filter = &body.filter; let filter = &body.filter;
@ -137,18 +137,17 @@ pub(crate) async fn get_message_events_route(
let lazy_loading_context = lazy_loading::Context { let lazy_loading_context = lazy_loading::Context {
user_id: sender_user, user_id: sender_user,
device_id: match sender_device { device_id: sender_device.or_else(|| {
| Some(device_id) => device_id, if let Some(registration) = body.appservice_info.as_ref() {
| None => Some(<&DeviceId>::from(registration.registration.id.as_str()))
if let Some(registration) = body.appservice_info.as_ref() { } else {
<&DeviceId>::from(registration.registration.id.as_str()) warn!(
} else { "No device_id provided and no appservice registration found, this should be \
panic!( unreachable"
"No device_id provided and no appservice registration found, this \ );
should be unreachable" None
); }
}, }),
},
room_id, room_id,
token: Some(from.into_unsigned()), token: Some(from.into_unsigned()),
options: Some(&filter.lazy_load_options), options: Some(&filter.lazy_load_options),
@ -177,7 +176,7 @@ pub(crate) async fn get_message_events_route(
let chunk = events let chunk = events
.into_iter() .into_iter()
.map(at!(1)) .map(at!(1))
.map(PduEvent::into_room_event) .map(Event::into_format)
.collect(); .collect();
Ok(get_message_events::v3::Response { Ok(get_message_events::v3::Response {
@ -218,7 +217,9 @@ where
pin_mut!(receipts); pin_mut!(receipts);
let witness: Witness = events let witness: Witness = events
.stream() .stream()
.map(|(_, pdu)| pdu.sender.clone()) .map(ref_at!(1))
.map(Event::sender)
.map(ToOwned::to_owned)
.chain( .chain(
receipts receipts
.ready_take_while(|(_, c, _)| *c <= newest.into_unsigned()) .ready_take_while(|(_, c, _)| *c <= newest.into_unsigned())
@ -243,7 +244,7 @@ async fn get_member_event(
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str()) .room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())
.map_ok(PduEvent::into_state_event) .map_ok(Event::into_format)
.await .await
.ok() .ok()
} }
@ -263,27 +264,33 @@ pub(crate) async fn ignored_filter(
} }
#[inline] #[inline]
pub(crate) async fn is_ignored_pdu( pub(crate) async fn is_ignored_pdu<Pdu>(
services: &Services, services: &Services,
pdu: &PduEvent, event: &Pdu,
user_id: &UserId, user_id: &UserId,
) -> bool { ) -> bool
where
Pdu: Event + Send + Sync,
{
// exclude Synapse's dummy events from bloating up response bodies. clients // exclude Synapse's dummy events from bloating up response bodies. clients
// don't need to see this. // don't need to see this.
if pdu.kind.to_cow_str() == "org.matrix.dummy_event" { if event.kind().to_cow_str() == "org.matrix.dummy_event" {
return true; return true;
} }
let ignored_type = IGNORED_MESSAGE_TYPES.binary_search(&pdu.kind).is_ok(); let ignored_type = IGNORED_MESSAGE_TYPES.binary_search(event.kind()).is_ok();
let ignored_server = services let ignored_server = services
.moderation .moderation
.is_remote_server_ignored(pdu.sender().server_name()); .is_remote_server_ignored(event.sender().server_name());
if ignored_type if ignored_type
&& (ignored_server && (ignored_server
|| (!services.config.send_messages_from_ignored_users_to_client || (!services.config.send_messages_from_ignored_users_to_client
&& services.users.user_is_ignored(&pdu.sender, user_id).await)) && services
.users
.user_is_ignored(event.sender(), user_id)
.await))
{ {
return true; return true;
} }
@ -302,7 +309,7 @@ pub(crate) async fn visibility_filter(
services services
.rooms .rooms
.state_accessor .state_accessor
.user_can_see_event(user_id, &pdu.room_id, &pdu.event_id) .user_can_see_event(user_id, pdu.room_id(), pdu.event_id())
.await .await
.then_some(item) .then_some(item)
} }
@ -310,7 +317,7 @@ pub(crate) async fn visibility_filter(
#[inline] #[inline]
pub(crate) fn event_filter(item: PdusIterItem, filter: &RoomEventFilter) -> Option<PdusIterItem> { pub(crate) fn event_filter(item: PdusIterItem, filter: &RoomEventFilter) -> Option<PdusIterItem> {
let (_, pdu) = &item; let (_, pdu) = &item;
pdu.matches(filter).then_some(item) filter.matches(pdu).then_some(item)
} }
#[cfg_attr(debug_assertions, conduwuit::ctor)] #[cfg_attr(debug_assertions, conduwuit::ctor)]

View file

@ -1,11 +1,8 @@
use std::time::Duration; use std::time::Duration;
use axum::extract::State; use axum::extract::State;
use conduwuit::{Error, Result, utils}; use conduwuit::{Err, Result, utils};
use ruma::{ use ruma::{api::client::account, authentication::TokenType};
api::client::{account, error::ErrorKind},
authentication::TokenType,
};
use super::TOKEN_LENGTH; use super::TOKEN_LENGTH;
use crate::Ruma; use crate::Ruma;
@ -19,17 +16,15 @@ pub(crate) async fn create_openid_token_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<account::request_openid_token::v3::Request>, body: Ruma<account::request_openid_token::v3::Request>,
) -> Result<account::request_openid_token::v3::Response> { ) -> Result<account::request_openid_token::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
if sender_user != &body.user_id { if sender_user != body.user_id {
return Err(Error::BadRequest( return Err!(Request(InvalidParam(
ErrorKind::InvalidParam,
"Not allowed to request OpenID tokens on behalf of other users", "Not allowed to request OpenID tokens on behalf of other users",
)); )));
} }
let access_token = utils::random_string(TOKEN_LENGTH); let access_token = utils::random_string(TOKEN_LENGTH);
let expires_in = services let expires_in = services
.users .users
.create_openid_token(&body.user_id, &access_token)?; .create_openid_token(&body.user_id, &access_token)?;

View file

@ -2,21 +2,21 @@ use std::collections::BTreeMap;
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Error, Result, Err, Result,
matrix::pdu::PduBuilder, matrix::pdu::PduBuilder,
utils::{IterStream, stream::TryIgnore}, utils::{IterStream, future::TryExtExt, stream::TryIgnore},
warn, warn,
}; };
use conduwuit_service::Services; use conduwuit_service::Services;
use futures::{StreamExt, TryStreamExt, future::join3}; use futures::{
StreamExt, TryStreamExt,
future::{join, join3, join4},
};
use ruma::{ use ruma::{
OwnedMxcUri, OwnedRoomId, UserId, OwnedMxcUri, OwnedRoomId, UserId,
api::{ api::{
client::{ client::profile::{
error::ErrorKind, get_avatar_url, get_display_name, get_profile, set_avatar_url, set_display_name,
profile::{
get_avatar_url, get_display_name, get_profile, set_avatar_url, set_display_name,
},
}, },
federation, federation,
}, },
@ -35,7 +35,7 @@ pub(crate) async fn set_displayname_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<set_display_name::v3::Request>, body: Ruma<set_display_name::v3::Request>,
) -> Result<set_display_name::v3::Response> { ) -> Result<set_display_name::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? { if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended."))); return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
} }
@ -110,7 +110,7 @@ pub(crate) async fn get_displayname_route(
if !services.users.exists(&body.user_id).await { if !services.users.exists(&body.user_id).await {
// Return 404 if this user doesn't exist and we couldn't fetch it over // Return 404 if this user doesn't exist and we couldn't fetch it over
// federation // federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found.")); return Err!(Request(NotFound("Profile was not found.")));
} }
Ok(get_display_name::v3::Response { Ok(get_display_name::v3::Response {
@ -127,7 +127,7 @@ pub(crate) async fn set_avatar_url_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<set_avatar_url::v3::Request>, body: Ruma<set_avatar_url::v3::Request>,
) -> Result<set_avatar_url::v3::Response> { ) -> Result<set_avatar_url::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? { if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended."))); return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
} }
@ -195,11 +195,9 @@ pub(crate) async fn get_avatar_url_route(
services services
.users .users
.set_displayname(&body.user_id, response.displayname.clone()); .set_displayname(&body.user_id, response.displayname.clone());
services services
.users .users
.set_avatar_url(&body.user_id, response.avatar_url.clone()); .set_avatar_url(&body.user_id, response.avatar_url.clone());
services services
.users .users
.set_blurhash(&body.user_id, response.blurhash.clone()); .set_blurhash(&body.user_id, response.blurhash.clone());
@ -214,13 +212,16 @@ pub(crate) async fn get_avatar_url_route(
if !services.users.exists(&body.user_id).await { if !services.users.exists(&body.user_id).await {
// Return 404 if this user doesn't exist and we couldn't fetch it over // Return 404 if this user doesn't exist and we couldn't fetch it over
// federation // federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found.")); return Err!(Request(NotFound("Profile was not found.")));
} }
Ok(get_avatar_url::v3::Response { let (avatar_url, blurhash) = join(
avatar_url: services.users.avatar_url(&body.user_id).await.ok(), services.users.avatar_url(&body.user_id).ok(),
blurhash: services.users.blurhash(&body.user_id).await.ok(), services.users.blurhash(&body.user_id).ok(),
}) )
.await;
Ok(get_avatar_url::v3::Response { avatar_url, blurhash })
} }
/// # `GET /_matrix/client/v3/profile/{userId}` /// # `GET /_matrix/client/v3/profile/{userId}`
@ -253,15 +254,12 @@ pub(crate) async fn get_profile_route(
services services
.users .users
.set_displayname(&body.user_id, response.displayname.clone()); .set_displayname(&body.user_id, response.displayname.clone());
services services
.users .users
.set_avatar_url(&body.user_id, response.avatar_url.clone()); .set_avatar_url(&body.user_id, response.avatar_url.clone());
services services
.users .users
.set_blurhash(&body.user_id, response.blurhash.clone()); .set_blurhash(&body.user_id, response.blurhash.clone());
services services
.users .users
.set_timezone(&body.user_id, response.tz.clone()); .set_timezone(&body.user_id, response.tz.clone());
@ -287,7 +285,7 @@ pub(crate) async fn get_profile_route(
if !services.users.exists(&body.user_id).await { if !services.users.exists(&body.user_id).await {
// Return 404 if this user doesn't exist and we couldn't fetch it over // Return 404 if this user doesn't exist and we couldn't fetch it over
// federation // federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found.")); return Err!(Request(NotFound("Profile was not found.")));
} }
let mut custom_profile_fields: BTreeMap<String, serde_json::Value> = services let mut custom_profile_fields: BTreeMap<String, serde_json::Value> = services
@ -300,11 +298,19 @@ pub(crate) async fn get_profile_route(
custom_profile_fields.remove("us.cloke.msc4175.tz"); custom_profile_fields.remove("us.cloke.msc4175.tz");
custom_profile_fields.remove("m.tz"); custom_profile_fields.remove("m.tz");
let (avatar_url, blurhash, displayname, tz) = join4(
services.users.avatar_url(&body.user_id).ok(),
services.users.blurhash(&body.user_id).ok(),
services.users.displayname(&body.user_id).ok(),
services.users.timezone(&body.user_id).ok(),
)
.await;
Ok(get_profile::v3::Response { Ok(get_profile::v3::Response {
avatar_url: services.users.avatar_url(&body.user_id).await.ok(), avatar_url,
blurhash: services.users.blurhash(&body.user_id).await.ok(), blurhash,
displayname: services.users.displayname(&body.user_id).await.ok(), displayname,
tz: services.users.timezone(&body.user_id).await.ok(), tz,
custom_profile_fields, custom_profile_fields,
}) })
} }
@ -316,16 +322,12 @@ pub async fn update_displayname(
all_joined_rooms: &[OwnedRoomId], all_joined_rooms: &[OwnedRoomId],
) { ) {
let (current_avatar_url, current_blurhash, current_displayname) = join3( let (current_avatar_url, current_blurhash, current_displayname) = join3(
services.users.avatar_url(user_id), services.users.avatar_url(user_id).ok(),
services.users.blurhash(user_id), services.users.blurhash(user_id).ok(),
services.users.displayname(user_id), services.users.displayname(user_id).ok(),
) )
.await; .await;
let current_avatar_url = current_avatar_url.ok();
let current_blurhash = current_blurhash.ok();
let current_displayname = current_displayname.ok();
if displayname == current_displayname { if displayname == current_displayname {
return; return;
} }
@ -369,16 +371,12 @@ pub async fn update_avatar_url(
all_joined_rooms: &[OwnedRoomId], all_joined_rooms: &[OwnedRoomId],
) { ) {
let (current_avatar_url, current_blurhash, current_displayname) = join3( let (current_avatar_url, current_blurhash, current_displayname) = join3(
services.users.avatar_url(user_id), services.users.avatar_url(user_id).ok(),
services.users.blurhash(user_id), services.users.blurhash(user_id).ok(),
services.users.displayname(user_id), services.users.displayname(user_id).ok(),
) )
.await; .await;
let current_avatar_url = current_avatar_url.ok();
let current_blurhash = current_blurhash.ok();
let current_displayname = current_displayname.ok();
if current_avatar_url == avatar_url && current_blurhash == blurhash { if current_avatar_url == avatar_url && current_blurhash == blurhash {
return; return;
} }

View file

@ -79,17 +79,14 @@ pub(crate) async fn get_pushrules_all_route(
global_ruleset.update_with_server_default(Ruleset::server_default(sender_user)); global_ruleset.update_with_server_default(Ruleset::server_default(sender_user));
let ty = GlobalAccountDataEventType::PushRules;
let event = PushRulesEvent {
content: PushRulesEventContent { global: global_ruleset.clone() },
};
services services
.account_data .account_data
.update( .update(None, sender_user, ty.to_string().into(), &serde_json::to_value(event)?)
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(PushRulesEvent {
content: PushRulesEventContent { global: global_ruleset.clone() },
})
.expect("to json always works"),
)
.await?; .await?;
} }
}; };
@ -106,7 +103,7 @@ pub(crate) async fn get_pushrules_global_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<get_pushrules_global_scope::v3::Request>, body: Ruma<get_pushrules_global_scope::v3::Request>,
) -> Result<get_pushrules_global_scope::v3::Response> { ) -> Result<get_pushrules_global_scope::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
let Some(content_value) = services let Some(content_value) = services
.account_data .account_data
@ -118,19 +115,17 @@ pub(crate) async fn get_pushrules_global_route(
else { else {
// user somehow has non-existent push rule event. recreate it and return server // user somehow has non-existent push rule event. recreate it and return server
// default silently // default silently
let ty = GlobalAccountDataEventType::PushRules;
let event = PushRulesEvent {
content: PushRulesEventContent {
global: Ruleset::server_default(sender_user),
},
};
services services
.account_data .account_data
.update( .update(None, sender_user, ty.to_string().into(), &serde_json::to_value(event)?)
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(PushRulesEvent {
content: PushRulesEventContent {
global: Ruleset::server_default(sender_user),
},
})
.expect("to json always works"),
)
.await?; .await?;
return Ok(get_pushrules_global_scope::v3::Response { return Ok(get_pushrules_global_scope::v3::Response {
@ -223,7 +218,7 @@ pub(crate) async fn get_pushrule_route(
if let Some(rule) = rule { if let Some(rule) = rule {
Ok(get_pushrule::v3::Response { rule }) Ok(get_pushrule::v3::Response { rule })
} else { } else {
Err(Error::BadRequest(ErrorKind::NotFound, "Push rule not found.")) Err!(Request(NotFound("Push rule not found.")))
} }
} }
@ -234,9 +229,8 @@ pub(crate) async fn set_pushrule_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<set_pushrule::v3::Request>, body: Ruma<set_pushrule::v3::Request>,
) -> Result<set_pushrule::v3::Response> { ) -> Result<set_pushrule::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
let body = body.body; let body = &body.body;
let mut account_data: PushRulesEvent = services let mut account_data: PushRulesEvent = services
.account_data .account_data
.get_global(sender_user, GlobalAccountDataEventType::PushRules) .get_global(sender_user, GlobalAccountDataEventType::PushRules)
@ -275,14 +269,10 @@ pub(crate) async fn set_pushrule_route(
return Err(err); return Err(err);
} }
let ty = GlobalAccountDataEventType::PushRules;
services services
.account_data .account_data
.update( .update(None, sender_user, ty.to_string().into(), &serde_json::to_value(account_data)?)
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(account_data).expect("to json value always works"),
)
.await?; .await?;
Ok(set_pushrule::v3::Response {}) Ok(set_pushrule::v3::Response {})
@ -295,7 +285,7 @@ pub(crate) async fn get_pushrule_actions_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<get_pushrule_actions::v3::Request>, body: Ruma<get_pushrule_actions::v3::Request>,
) -> Result<get_pushrule_actions::v3::Response> { ) -> Result<get_pushrule_actions::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
// remove old deprecated mentions push rules as per MSC4210 // remove old deprecated mentions push rules as per MSC4210
#[allow(deprecated)] #[allow(deprecated)]
@ -329,7 +319,7 @@ pub(crate) async fn set_pushrule_actions_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<set_pushrule_actions::v3::Request>, body: Ruma<set_pushrule_actions::v3::Request>,
) -> Result<set_pushrule_actions::v3::Response> { ) -> Result<set_pushrule_actions::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
let mut account_data: PushRulesEvent = services let mut account_data: PushRulesEvent = services
.account_data .account_data
@ -343,17 +333,13 @@ pub(crate) async fn set_pushrule_actions_route(
.set_actions(body.kind.clone(), &body.rule_id, body.actions.clone()) .set_actions(body.kind.clone(), &body.rule_id, body.actions.clone())
.is_err() .is_err()
{ {
return Err(Error::BadRequest(ErrorKind::NotFound, "Push rule not found.")); return Err!(Request(NotFound("Push rule not found.")));
} }
let ty = GlobalAccountDataEventType::PushRules;
services services
.account_data .account_data
.update( .update(None, sender_user, ty.to_string().into(), &serde_json::to_value(account_data)?)
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(account_data).expect("to json value always works"),
)
.await?; .await?;
Ok(set_pushrule_actions::v3::Response {}) Ok(set_pushrule_actions::v3::Response {})
@ -366,7 +352,7 @@ pub(crate) async fn get_pushrule_enabled_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<get_pushrule_enabled::v3::Request>, body: Ruma<get_pushrule_enabled::v3::Request>,
) -> Result<get_pushrule_enabled::v3::Response> { ) -> Result<get_pushrule_enabled::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
// remove old deprecated mentions push rules as per MSC4210 // remove old deprecated mentions push rules as per MSC4210
#[allow(deprecated)] #[allow(deprecated)]
@ -400,7 +386,7 @@ pub(crate) async fn set_pushrule_enabled_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<set_pushrule_enabled::v3::Request>, body: Ruma<set_pushrule_enabled::v3::Request>,
) -> Result<set_pushrule_enabled::v3::Response> { ) -> Result<set_pushrule_enabled::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
let mut account_data: PushRulesEvent = services let mut account_data: PushRulesEvent = services
.account_data .account_data
@ -414,17 +400,13 @@ pub(crate) async fn set_pushrule_enabled_route(
.set_enabled(body.kind.clone(), &body.rule_id, body.enabled) .set_enabled(body.kind.clone(), &body.rule_id, body.enabled)
.is_err() .is_err()
{ {
return Err(Error::BadRequest(ErrorKind::NotFound, "Push rule not found.")); return Err!(Request(NotFound("Push rule not found.")));
} }
let ty = GlobalAccountDataEventType::PushRules;
services services
.account_data .account_data
.update( .update(None, sender_user, ty.to_string().into(), &serde_json::to_value(account_data)?)
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(account_data).expect("to json value always works"),
)
.await?; .await?;
Ok(set_pushrule_enabled::v3::Response {}) Ok(set_pushrule_enabled::v3::Response {})
@ -437,7 +419,7 @@ pub(crate) async fn delete_pushrule_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<delete_pushrule::v3::Request>, body: Ruma<delete_pushrule::v3::Request>,
) -> Result<delete_pushrule::v3::Response> { ) -> Result<delete_pushrule::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
let mut account_data: PushRulesEvent = services let mut account_data: PushRulesEvent = services
.account_data .account_data
@ -463,14 +445,10 @@ pub(crate) async fn delete_pushrule_route(
return Err(err); return Err(err);
} }
let ty = GlobalAccountDataEventType::PushRules;
services services
.account_data .account_data
.update( .update(None, sender_user, ty.to_string().into(), &serde_json::to_value(account_data)?)
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(account_data).expect("to json value always works"),
)
.await?; .await?;
Ok(delete_pushrule::v3::Response {}) Ok(delete_pushrule::v3::Response {})
@ -483,7 +461,7 @@ pub(crate) async fn get_pushers_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<get_pushers::v3::Request>, body: Ruma<get_pushers::v3::Request>,
) -> Result<get_pushers::v3::Response> { ) -> Result<get_pushers::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
Ok(get_pushers::v3::Response { Ok(get_pushers::v3::Response {
pushers: services.pusher.get_pushers(sender_user).await, pushers: services.pusher.get_pushers(sender_user).await,
@ -499,7 +477,7 @@ pub(crate) async fn set_pushers_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<set_pusher::v3::Request>, body: Ruma<set_pusher::v3::Request>,
) -> Result<set_pusher::v3::Response> { ) -> Result<set_pusher::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
services services
.pusher .pusher
@ -515,19 +493,16 @@ async fn recreate_push_rules_and_return(
services: &Services, services: &Services,
sender_user: &ruma::UserId, sender_user: &ruma::UserId,
) -> Result<get_pushrules_all::v3::Response> { ) -> Result<get_pushrules_all::v3::Response> {
let ty = GlobalAccountDataEventType::PushRules;
let event = PushRulesEvent {
content: PushRulesEventContent {
global: Ruleset::server_default(sender_user),
},
};
services services
.account_data .account_data
.update( .update(None, sender_user, ty.to_string().into(), &serde_json::to_value(event)?)
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(PushRulesEvent {
content: PushRulesEventContent {
global: Ruleset::server_default(sender_user),
},
})
.expect("to json always works"),
)
.await?; .await?;
Ok(get_pushrules_all::v3::Response { Ok(get_pushrules_all::v3::Response {

View file

@ -37,7 +37,7 @@ pub(crate) async fn set_read_marker_route(
Some(&body.room_id), Some(&body.room_id),
sender_user, sender_user,
RoomAccountDataEventType::FullyRead, RoomAccountDataEventType::FullyRead,
&serde_json::to_value(fully_read_event).expect("to json value always works"), &serde_json::to_value(fully_read_event)?,
) )
.await?; .await?;
} }
@ -151,7 +151,7 @@ pub(crate) async fn create_receipt_route(
Some(&body.room_id), Some(&body.room_id),
sender_user, sender_user,
RoomAccountDataEventType::FullyRead, RoomAccountDataEventType::FullyRead,
&serde_json::to_value(fully_read_event).expect("to json value always works"), &serde_json::to_value(fully_read_event)?,
) )
.await?; .await?;
}, },

View file

@ -15,8 +15,8 @@ pub(crate) async fn redact_event_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<redact_event::v3::Request>, body: Ruma<redact_event::v3::Request>,
) -> Result<redact_event::v3::Response> { ) -> Result<redact_event::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
let body = body.body; let body = &body.body;
if services.users.is_suspended(sender_user).await? { if services.users.is_suspended(sender_user).await? {
// TODO: Users can redact their own messages while suspended // TODO: Users can redact their own messages while suspended
return Err!(Request(UserSuspended("You cannot perform this action while suspended."))); return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));

View file

@ -1,10 +1,10 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Result, at, Result, at,
matrix::pdu::PduCount, matrix::{Event, event::RelationTypeEqual, pdu::PduCount},
utils::{IterStream, ReadyExt, result::FlatOk, stream::WidebandExt}, utils::{IterStream, ReadyExt, result::FlatOk, stream::WidebandExt},
}; };
use conduwuit_service::{Services, rooms::timeline::PdusIterItem}; use conduwuit_service::Services;
use futures::StreamExt; use futures::StreamExt;
use ruma::{ use ruma::{
EventId, RoomId, UInt, UserId, EventId, RoomId, UInt, UserId,
@ -129,7 +129,7 @@ async fn paginate_relations_with_filter(
// Spec (v1.10) recommends depth of at least 3 // Spec (v1.10) recommends depth of at least 3
let depth: u8 = if recurse { 3 } else { 1 }; let depth: u8 = if recurse { 3 } else { 1 };
let events: Vec<PdusIterItem> = services let events: Vec<_> = services
.rooms .rooms
.pdu_metadata .pdu_metadata
.get_relations(sender_user, room_id, target, start, limit, depth, dir) .get_relations(sender_user, room_id, target, start, limit, depth, dir)
@ -138,12 +138,12 @@ async fn paginate_relations_with_filter(
.filter(|(_, pdu)| { .filter(|(_, pdu)| {
filter_event_type filter_event_type
.as_ref() .as_ref()
.is_none_or(|kind| *kind == pdu.kind) .is_none_or(|kind| kind == pdu.kind())
}) })
.filter(|(_, pdu)| { .filter(|(_, pdu)| {
filter_rel_type filter_rel_type
.as_ref() .as_ref()
.is_none_or(|rel_type| pdu.relation_type_equal(rel_type)) .is_none_or(|rel_type| rel_type.relation_type_equal(pdu))
}) })
.stream() .stream()
.ready_take_while(|(count, _)| Some(*count) != to) .ready_take_while(|(count, _)| Some(*count) != to)
@ -167,22 +167,22 @@ async fn paginate_relations_with_filter(
chunk: events chunk: events
.into_iter() .into_iter()
.map(at!(1)) .map(at!(1))
.map(|pdu| pdu.to_message_like_event()) .map(Event::into_format)
.collect(), .collect(),
}) })
} }
async fn visibility_filter( async fn visibility_filter<Pdu: Event + Send + Sync>(
services: &Services, services: &Services,
sender_user: &UserId, sender_user: &UserId,
item: PdusIterItem, item: (PduCount, Pdu),
) -> Option<PdusIterItem> { ) -> Option<(PduCount, Pdu)> {
let (_, pdu) = &item; let (_, pdu) = &item;
services services
.rooms .rooms
.state_accessor .state_accessor
.user_can_see_event(sender_user, &pdu.room_id, &pdu.event_id) .user_can_see_event(sender_user, pdu.room_id(), pdu.event_id())
.await .await
.then_some(item) .then_some(item)
} }

View file

@ -2,13 +2,12 @@ use std::{fmt::Write as _, ops::Mul, time::Duration};
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{Err, Error, Result, debug_info, info, matrix::pdu::PduEvent, utils::ReadyExt}; use conduwuit::{Err, Result, debug_info, info, matrix::pdu::PduEvent, utils::ReadyExt};
use conduwuit_service::Services; use conduwuit_service::Services;
use rand::Rng; use rand::Rng;
use ruma::{ use ruma::{
EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId, EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
api::client::{ api::client::{
error::ErrorKind,
report_user, report_user,
room::{report_content, report_room}, room::{report_content, report_room},
}, },
@ -38,16 +37,14 @@ pub(crate) async fn report_room_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<report_room::v3::Request>, body: Ruma<report_room::v3::Request>,
) -> Result<report_room::v3::Response> { ) -> Result<report_room::v3::Response> {
// user authentication let sender_user = body.sender_user();
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if services.users.is_suspended(sender_user).await? { if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended."))); return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
} }
if body.reason.as_ref().is_some_and(|s| s.len() > 750) { if body.reason.as_ref().is_some_and(|s| s.len() > 750) {
return Err(Error::BadRequest( return Err!(Request(
ErrorKind::InvalidParam, InvalidParam("Reason too long, should be 750 characters or fewer",)
"Reason too long, should be 750 characters or fewer",
)); ));
} }
@ -94,7 +91,7 @@ pub(crate) async fn report_event_route(
body: Ruma<report_content::v3::Request>, body: Ruma<report_content::v3::Request>,
) -> Result<report_content::v3::Response> { ) -> Result<report_content::v3::Response> {
// user authentication // user authentication
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? { if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended."))); return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
} }
@ -150,9 +147,8 @@ pub(crate) async fn report_user_route(
} }
if body.reason.as_ref().is_some_and(|s| s.len() > 750) { if body.reason.as_ref().is_some_and(|s| s.len() > 750) {
return Err(Error::BadRequest( return Err!(Request(
ErrorKind::InvalidParam, InvalidParam("Reason too long, should be 750 characters or fewer",)
"Reason too long, should be 750 characters or fewer",
)); ));
} }
@ -205,23 +201,16 @@ async fn is_event_report_valid(
); );
if room_id != pdu.room_id { if room_id != pdu.room_id {
return Err(Error::BadRequest( return Err!(Request(NotFound("Event ID does not belong to the reported room",)));
ErrorKind::NotFound,
"Event ID does not belong to the reported room",
));
} }
if score.is_some_and(|s| s > int!(0) || s < int!(-100)) { if score.is_some_and(|s| s > int!(0) || s < int!(-100)) {
return Err(Error::BadRequest( return Err!(Request(InvalidParam("Invalid score, must be within 0 to -100",)));
ErrorKind::InvalidParam,
"Invalid score, must be within 0 to -100",
));
} }
if reason.as_ref().is_some_and(|s| s.len() > 750) { if reason.as_ref().is_some_and(|s| s.len() > 750) {
return Err(Error::BadRequest( return Err!(Request(
ErrorKind::InvalidParam, InvalidParam("Reason too long, should be 750 characters or fewer",)
"Reason too long, should be 750 characters or fewer",
)); ));
} }
@ -232,10 +221,7 @@ async fn is_event_report_valid(
.ready_any(|user_id| user_id == sender_user) .ready_any(|user_id| user_id == sender_user)
.await .await
{ {
return Err(Error::BadRequest( return Err!(Request(NotFound("You are not in the room you are reporting.",)));
ErrorKind::NotFound,
"You are not in the room you are reporting.",
));
} }
Ok(()) Ok(())
@ -273,5 +259,6 @@ async fn delay_response() {
"Got successful /report request, waiting {time_to_wait} seconds before sending \ "Got successful /report request, waiting {time_to_wait} seconds before sending \
successful response." successful response."
); );
sleep(Duration::from_secs(time_to_wait)).await; sleep(Duration::from_secs(time_to_wait)).await;
} }

View file

@ -1,7 +1,7 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{Error, Result}; use conduwuit::{Err, Result};
use futures::StreamExt; use futures::StreamExt;
use ruma::api::client::{error::ErrorKind, room::aliases}; use ruma::api::client::room::aliases;
use crate::Ruma; use crate::Ruma;
@ -15,7 +15,7 @@ pub(crate) async fn get_room_aliases_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<aliases::v3::Request>, body: Ruma<aliases::v3::Request>,
) -> Result<aliases::v3::Response> { ) -> Result<aliases::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
if !services if !services
.rooms .rooms
@ -23,10 +23,7 @@ pub(crate) async fn get_room_aliases_route(
.user_can_see_state_events(sender_user, &body.room_id) .user_can_see_state_events(sender_user, &body.room_id)
.await .await
{ {
return Err(Error::BadRequest( return Err!(Request(Forbidden("You don't have permission to view this room.",)));
ErrorKind::forbidden(),
"You don't have permission to view this room.",
));
} }
Ok(aliases::v3::Response { Ok(aliases::v3::Response {

View file

@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Error, Result, debug_info, debug_warn, err, error, info, Err, Result, debug_info, debug_warn, err, info,
matrix::{StateKey, pdu::PduBuilder}, matrix::{StateKey, pdu::PduBuilder},
warn, warn,
}; };
@ -10,10 +10,7 @@ use conduwuit_service::{Services, appservice::RegistrationInfo};
use futures::FutureExt; use futures::FutureExt;
use ruma::{ use ruma::{
CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId, CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId,
api::client::{ api::client::room::{self, create_room},
error::ErrorKind,
room::{self, create_room},
},
events::{ events::{
TimelineEventType, TimelineEventType,
room::{ room::{
@ -58,16 +55,13 @@ pub(crate) async fn create_room_route(
) -> Result<create_room::v3::Response> { ) -> Result<create_room::v3::Response> {
use create_room::v3::RoomPreset; use create_room::v3::RoomPreset;
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
if !services.globals.allow_room_creation() if !services.globals.allow_room_creation()
&& body.appservice_info.is_none() && body.appservice_info.is_none()
&& !services.users.is_admin(sender_user).await && !services.users.is_admin(sender_user).await
{ {
return Err(Error::BadRequest( return Err!(Request(Forbidden("Room creation has been disabled.",)));
ErrorKind::forbidden(),
"Room creation has been disabled.",
));
} }
if services.users.is_suspended(sender_user).await? { if services.users.is_suspended(sender_user).await? {
@ -81,10 +75,7 @@ pub(crate) async fn create_room_route(
// 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
if services.rooms.short.get_shortroomid(&room_id).await.is_ok() { if services.rooms.short.get_shortroomid(&room_id).await.is_ok() {
return Err(Error::BadRequest( return Err!(Request(RoomInUse("Room with that custom room ID already exists",)));
ErrorKind::RoomInUse,
"Room with that custom room ID already exists",
));
} }
if body.visibility == room::Visibility::Public if body.visibility == room::Visibility::Public
@ -92,19 +83,17 @@ pub(crate) async fn create_room_route(
&& !services.users.is_admin(sender_user).await && !services.users.is_admin(sender_user).await
&& body.appservice_info.is_none() && body.appservice_info.is_none()
{ {
info!( warn!(
"Non-admin user {sender_user} tried to publish {0} to the room directory while \ "Non-admin user {sender_user} tried to publish {room_id} to the room directory \
\"lockdown_public_room_directory\" is enabled", while \"lockdown_public_room_directory\" is enabled"
&room_id
); );
if services.server.config.admin_room_notices { if services.server.config.admin_room_notices {
services services
.admin .admin
.send_text(&format!( .notice(&format!(
"Non-admin user {sender_user} tried to publish {0} to the room directory \ "Non-admin user {sender_user} tried to publish {room_id} to the room \
while \"lockdown_public_room_directory\" is enabled", directory while \"lockdown_public_room_directory\" is enabled"
&room_id
)) ))
.await; .await;
} }
@ -129,10 +118,9 @@ pub(crate) async fn create_room_route(
if services.server.supported_room_version(&room_version) { if services.server.supported_room_version(&room_version) {
room_version room_version
} else { } else {
return Err(Error::BadRequest( return Err!(Request(UnsupportedRoomVersion(
ErrorKind::UnsupportedRoomVersion, "This server does not support that room version."
"This server does not support that room version.", )));
));
}, },
| None => services.server.config.default_room_version.clone(), | None => services.server.config.default_room_version.clone(),
}; };
@ -144,16 +132,17 @@ pub(crate) async fn create_room_route(
let mut content = content let mut content = content
.deserialize_as::<CanonicalJsonObject>() .deserialize_as::<CanonicalJsonObject>()
.map_err(|e| { .map_err(|e| {
error!("Failed to deserialise content as canonical JSON: {}", e); err!(Request(BadJson(error!(
Error::bad_database("Failed to deserialise content as canonical JSON.") "Failed to deserialise content as canonical JSON: {e}"
))))
})?; })?;
match room_version { match room_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => { | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
content.insert( content.insert(
"creator".into(), "creator".into(),
json!(&sender_user).try_into().map_err(|e| { json!(&sender_user).try_into().map_err(|e| {
info!("Invalid creation content: {e}"); err!(Request(BadJson(debug_error!("Invalid creation content: {e}"))))
Error::BadRequest(ErrorKind::BadJson, "Invalid creation content")
})?, })?,
); );
}, },
@ -163,9 +152,9 @@ pub(crate) async fn create_room_route(
} }
content.insert( content.insert(
"room_version".into(), "room_version".into(),
json!(room_version.as_str()).try_into().map_err(|_| { json!(room_version.as_str())
Error::BadRequest(ErrorKind::BadJson, "Invalid creation content") .try_into()
})?, .map_err(|e| err!(Request(BadJson("Invalid creation content: {e}"))))?,
); );
content content
}, },
@ -174,21 +163,13 @@ pub(crate) async fn create_room_route(
let content = match room_version { let content = match room_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
RoomCreateEventContent::new_v1(sender_user.clone()), RoomCreateEventContent::new_v1(sender_user.to_owned()),
| _ => RoomCreateEventContent::new_v11(), | _ => RoomCreateEventContent::new_v11(),
}; };
let mut content = serde_json::from_str::<CanonicalJsonObject>( let mut content =
to_raw_value(&content) serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())
.expect("we just created this as content was None") .unwrap();
.get(), content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
)
.unwrap();
content.insert(
"room_version".into(),
json!(room_version.as_str())
.try_into()
.expect("we just created this as content was None"),
);
content content
}, },
}; };
@ -200,8 +181,7 @@ pub(crate) async fn create_room_route(
.build_and_append_pdu( .build_and_append_pdu(
PduBuilder { PduBuilder {
event_type: TimelineEventType::RoomCreate, event_type: TimelineEventType::RoomCreate,
content: to_raw_value(&create_content) content: to_raw_value(&create_content)?,
.expect("create event content serialization"),
state_key: Some(StateKey::new()), state_key: Some(StateKey::new()),
..Default::default() ..Default::default()
}, },
@ -239,7 +219,7 @@ pub(crate) async fn create_room_route(
| _ => RoomPreset::PrivateChat, // Room visibility should not be custom | _ => RoomPreset::PrivateChat, // Room visibility should not be custom
}); });
let mut users = BTreeMap::from_iter([(sender_user.clone(), int!(100))]); let mut users = BTreeMap::from_iter([(sender_user.to_owned(), int!(100))]);
if preset == RoomPreset::TrustedPrivateChat { if preset == RoomPreset::TrustedPrivateChat {
for invite in &body.invite { for invite in &body.invite {
@ -267,8 +247,7 @@ pub(crate) async fn create_room_route(
.build_and_append_pdu( .build_and_append_pdu(
PduBuilder { PduBuilder {
event_type: TimelineEventType::RoomPowerLevels, event_type: TimelineEventType::RoomPowerLevels,
content: to_raw_value(&power_levels_content) content: to_raw_value(&power_levels_content)?,
.expect("serialized power_levels event content"),
state_key: Some(StateKey::new()), state_key: Some(StateKey::new()),
..Default::default() ..Default::default()
}, },
@ -357,8 +336,7 @@ pub(crate) async fn create_room_route(
// 6. Events listed in initial_state // 6. Events listed in initial_state
for event in &body.initial_state { for event in &body.initial_state {
let mut pdu_builder = event.deserialize_as::<PduBuilder>().map_err(|e| { let mut pdu_builder = event.deserialize_as::<PduBuilder>().map_err(|e| {
warn!("Invalid initial state event: {:?}", e); err!(Request(InvalidParam(warn!("Invalid initial state event: {e:?}"))))
Error::BadRequest(ErrorKind::InvalidParam, "Invalid initial state event.")
})?; })?;
debug_info!("Room creation initial state event: {event:?}"); debug_info!("Room creation initial state event: {event:?}");
@ -367,7 +345,7 @@ pub(crate) async fn create_room_route(
// state event in there with the content of literally `{}` (not null or empty // state event in there with the content of literally `{}` (not null or empty
// string), let's just skip it over and warn. // string), let's just skip it over and warn.
if pdu_builder.content.get().eq("{}") { if pdu_builder.content.get().eq("{}") {
info!("skipping empty initial state event with content of `{{}}`: {event:?}"); debug_warn!("skipping empty initial state event with content of `{{}}`: {event:?}");
debug_warn!("content: {}", pdu_builder.content.get()); debug_warn!("content: {}", pdu_builder.content.get());
continue; continue;
} }
@ -514,9 +492,7 @@ fn default_power_levels_content(
if let Some(power_level_content_override) = power_level_content_override { if let Some(power_level_content_override) = power_level_content_override {
let json: JsonObject = serde_json::from_str(power_level_content_override.json().get()) let json: JsonObject = serde_json::from_str(power_level_content_override.json().get())
.map_err(|_| { .map_err(|e| err!(Request(BadJson("Invalid power_level_content_override: {e:?}"))))?;
Error::BadRequest(ErrorKind::BadJson, "Invalid power_level_content_override.")
})?;
for (key, value) in json { for (key, value) in json {
power_levels_content[key] = value; power_levels_content[key] = value;
@ -534,16 +510,14 @@ async fn room_alias_check(
) -> Result<OwnedRoomAliasId> { ) -> Result<OwnedRoomAliasId> {
// Basic checks on the room alias validity // Basic checks on the room alias validity
if room_alias_name.contains(':') { if room_alias_name.contains(':') {
return Err(Error::BadRequest( return Err!(Request(InvalidParam(
ErrorKind::InvalidParam,
"Room alias contained `:` which is not allowed. Please note that this expects a \ "Room alias contained `:` which is not allowed. Please note that this expects a \
localpart, not the full room alias.", localpart, not the full room alias.",
)); )));
} else if room_alias_name.contains(char::is_whitespace) { } else if room_alias_name.contains(char::is_whitespace) {
return Err(Error::BadRequest( return Err!(Request(InvalidParam(
ErrorKind::InvalidParam,
"Room alias contained spaces which is not a valid room alias.", "Room alias contained spaces which is not a valid room alias.",
)); )));
} }
// check if room alias is forbidden // check if room alias is forbidden
@ -552,7 +526,7 @@ async fn room_alias_check(
.forbidden_alias_names() .forbidden_alias_names()
.is_match(room_alias_name) .is_match(room_alias_name)
{ {
return Err(Error::BadRequest(ErrorKind::Unknown, "Room alias name is forbidden.")); return Err!(Request(Unknown("Room alias name is forbidden.")));
} }
let server_name = services.globals.server_name(); let server_name = services.globals.server_name();
@ -572,25 +546,19 @@ async fn room_alias_check(
.await .await
.is_ok() .is_ok()
{ {
return Err(Error::BadRequest(ErrorKind::RoomInUse, "Room alias already exists.")); return Err!(Request(RoomInUse("Room alias already exists.")));
} }
if let Some(info) = appservice_info { if let Some(info) = appservice_info {
if !info.aliases.is_match(full_room_alias.as_str()) { if !info.aliases.is_match(full_room_alias.as_str()) {
return Err(Error::BadRequest( return Err!(Request(Exclusive("Room alias is not in namespace.")));
ErrorKind::Exclusive,
"Room alias is not in namespace.",
));
} }
} else if services } else if services
.appservice .appservice
.is_exclusive_alias(&full_room_alias) .is_exclusive_alias(&full_room_alias)
.await .await
{ {
return Err(Error::BadRequest( return Err!(Request(Exclusive("Room alias reserved by appservice.",)));
ErrorKind::Exclusive,
"Room alias reserved by appservice.",
));
} }
debug_info!("Full room alias: {full_room_alias}"); debug_info!("Full room alias: {full_room_alias}");
@ -606,24 +574,33 @@ fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<Own
.forbidden_alias_names() .forbidden_alias_names()
.is_match(custom_room_id) .is_match(custom_room_id)
{ {
return Err(Error::BadRequest(ErrorKind::Unknown, "Custom room ID is forbidden.")); return Err!(Request(Unknown("Custom room ID is forbidden.")));
}
if custom_room_id.contains(':') {
return Err!(Request(InvalidParam(
"Custom room ID contained `:` which is not allowed. Please note that this expects a \
localpart, not the full room ID.",
)));
} else if custom_room_id.contains(char::is_whitespace) {
return Err!(Request(InvalidParam(
"Custom room ID contained spaces which is not valid."
)));
} }
let server_name = services.globals.server_name(); let server_name = services.globals.server_name();
let mut room_id = custom_room_id.to_owned(); let mut room_id = custom_room_id.to_owned();
if custom_room_id.contains(':') { if custom_room_id.contains(':') {
if !custom_room_id.starts_with('!') { if !custom_room_id.starts_with('!') {
return Err(Error::BadRequest( return Err!(Request(InvalidParam(
ErrorKind::InvalidParam,
"Custom room ID contains an unexpected `:` which is not allowed.", "Custom room ID contains an unexpected `:` which is not allowed.",
)); )));
} }
} else if custom_room_id.starts_with('!') { } else if custom_room_id.starts_with('!') {
return Err(Error::BadRequest( return Err!(Request(InvalidParam(
ErrorKind::InvalidParam,
"Room ID is prefixed with !, but is not fully qualified. You likely did not want \ "Room ID is prefixed with !, but is not fully qualified. You likely did not want \
this.", this.",
)); )));
} else { } else {
room_id = format!("!{custom_room_id}:{server_name}"); room_id = format!("!{custom_room_id}:{server_name}");
} }
@ -635,10 +612,7 @@ fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<Own
.expect("failed to extract server name from room ID") .expect("failed to extract server name from room ID")
!= server_name != server_name
{ {
Err(Error::BadRequest( Err!(Request(InvalidParam("Custom room ID must be on this server.",)))
ErrorKind::InvalidParam,
"Custom room ID must be on this server.",
))
} else { } else {
Ok(full_room_id) Ok(full_room_id)
} }

View file

@ -40,5 +40,5 @@ pub(crate) async fn get_room_event_route(
event.add_age().ok(); event.add_age().ok();
Ok(get_room_event::v3::Response { event: event.into_room_event() }) Ok(get_room_event::v3::Response { event: event.into_format() })
} }

View file

@ -1,9 +1,9 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, PduEvent, Result, at, Err, Event, Result, at,
utils::{BoolExt, stream::TryTools}, utils::{BoolExt, stream::TryTools},
}; };
use futures::TryStreamExt; use futures::{FutureExt, TryStreamExt, future::try_join4};
use ruma::api::client::room::initial_sync::v3::{PaginationChunk, Request, Response}; use ruma::api::client::room::initial_sync::v3::{PaginationChunk, Request, Response};
use crate::Ruma; use crate::Ruma;
@ -25,22 +25,33 @@ pub(crate) async fn room_initial_sync_route(
return Err!(Request(Forbidden("No room preview available."))); return Err!(Request(Forbidden("No room preview available.")));
} }
let membership = services
.rooms
.state_cache
.user_membership(body.sender_user(), room_id)
.map(Ok);
let visibility = services.rooms.directory.visibility(room_id).map(Ok);
let state = services
.rooms
.state_accessor
.room_state_full_pdus(room_id)
.map_ok(Event::into_format)
.try_collect::<Vec<_>>();
let limit = LIMIT_MAX; let limit = LIMIT_MAX;
let events: Vec<_> = services let events = services
.rooms .rooms
.timeline .timeline
.pdus_rev(None, room_id, None) .pdus_rev(None, room_id, None)
.try_take(limit) .try_take(limit)
.try_collect() .try_collect::<Vec<_>>();
.await?;
let state: Vec<_> = services let (membership, visibility, state, events) =
.rooms try_join4(membership, visibility, state, events)
.state_accessor .boxed()
.room_state_full_pdus(room_id) .await?;
.map_ok(PduEvent::into_state_event)
.try_collect()
.await?;
let messages = PaginationChunk { let messages = PaginationChunk {
start: events.last().map(at!(0)).as_ref().map(ToString::to_string), start: events.last().map(at!(0)).as_ref().map(ToString::to_string),
@ -55,7 +66,7 @@ pub(crate) async fn room_initial_sync_route(
chunk: events chunk: events
.into_iter() .into_iter()
.map(at!(1)) .map(at!(1))
.map(PduEvent::into_room_event) .map(Event::into_format)
.collect(), .collect(),
}; };
@ -64,11 +75,7 @@ pub(crate) async fn room_initial_sync_route(
account_data: None, account_data: None,
state: state.into(), state: state.into(),
messages: messages.chunk.is_empty().or_some(messages), messages: messages.chunk.is_empty().or_some(messages),
visibility: services.rooms.directory.visibility(room_id).await.into(), visibility: visibility.into(),
membership: services membership,
.rooms
.state_cache
.user_membership(body.sender_user(), room_id)
.await,
}) })
} }

View file

@ -112,13 +112,15 @@ async fn local_room_summary_response(
) -> Result<get_summary::msc3266::Response> { ) -> Result<get_summary::msc3266::Response> {
trace!(?sender_user, "Sending local room summary response for {room_id:?}"); trace!(?sender_user, "Sending local room summary response for {room_id:?}");
let join_rule = services.rooms.state_accessor.get_join_rules(room_id); let join_rule = services.rooms.state_accessor.get_join_rules(room_id);
let world_readable = services.rooms.state_accessor.is_world_readable(room_id); let world_readable = services.rooms.state_accessor.is_world_readable(room_id);
let guest_can_join = services.rooms.state_accessor.guest_can_join(room_id); let guest_can_join = services.rooms.state_accessor.guest_can_join(room_id);
let (join_rule, world_readable, guest_can_join) = let (join_rule, world_readable, guest_can_join) =
join3(join_rule, world_readable, guest_can_join).await; join3(join_rule, world_readable, guest_can_join).await;
trace!("{join_rule:?}, {world_readable:?}, {guest_can_join:?}");
trace!("{join_rule:?}, {world_readable:?}, {guest_can_join:?}");
user_can_see_summary( user_can_see_summary(
services, services,
room_id, room_id,

View file

@ -2,7 +2,7 @@ use std::cmp::max;
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Error, Result, err, info, Err, Error, Event, Result, err, info,
matrix::{StateKey, pdu::PduBuilder}, matrix::{StateKey, pdu::PduBuilder},
}; };
use futures::StreamExt; use futures::StreamExt;
@ -215,7 +215,7 @@ pub(crate) async fn upgrade_room_route(
.room_state_get(&body.room_id, event_type, "") .room_state_get(&body.room_id, event_type, "")
.await .await
{ {
| Ok(v) => v.content.clone(), | Ok(v) => v.content().to_owned(),
| Err(_) => continue, // Skipping missing events. | Err(_) => continue, // Skipping missing events.
}; };

View file

@ -3,7 +3,7 @@ use std::collections::BTreeMap;
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Result, at, is_true, Err, Result, at, is_true,
matrix::pdu::PduEvent, matrix::Event,
result::FlatOk, result::FlatOk,
utils::{IterStream, stream::ReadyExt}, utils::{IterStream, stream::ReadyExt},
}; };
@ -144,7 +144,7 @@ async fn category_room_events(
.map(at!(2)) .map(at!(2))
.flatten() .flatten()
.stream() .stream()
.map(PduEvent::into_room_event) .map(Event::into_format)
.map(|result| SearchResult { .map(|result| SearchResult {
rank: None, rank: None,
result: Some(result), result: Some(result),
@ -185,7 +185,7 @@ async fn procure_room_state(services: &Services, room_id: &RoomId) -> Result<Roo
.rooms .rooms
.state_accessor .state_accessor
.room_state_full_pdus(room_id) .room_state_full_pdus(room_id)
.map_ok(PduEvent::into_state_event) .map_ok(Event::into_format)
.try_collect() .try_collect()
.await?; .await?;

View file

@ -269,11 +269,9 @@ pub(crate) async fn login_token_route(
return Err!(Request(Forbidden("Login via an existing session is not enabled"))); return Err!(Request(Forbidden("Login via an existing session is not enabled")));
} }
let sender_user = body.sender_user();
let sender_device = body.sender_device();
// This route SHOULD have UIA // This route SHOULD have UIA
// TODO: How do we make only UIA sessions that have not been used before valid? // TODO: How do we make only UIA sessions that have not been used before valid?
let (sender_user, sender_device) = body.sender();
let mut uiaainfo = uiaa::UiaaInfo { let mut uiaainfo = uiaa::UiaaInfo {
flows: vec![uiaa::AuthFlow { stages: vec![uiaa::AuthType::Password] }], flows: vec![uiaa::AuthFlow { stages: vec![uiaa::AuthType::Password] }],
@ -335,12 +333,9 @@ pub(crate) async fn logout_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<logout::v3::Request>, body: Ruma<logout::v3::Request>,
) -> Result<logout::v3::Response> { ) -> Result<logout::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
services services
.users .users
.remove_device(sender_user, sender_device) .remove_device(body.sender_user(), body.sender_device())
.await; .await;
Ok(logout::v3::Response::new()) Ok(logout::v3::Response::new())
@ -365,12 +360,10 @@ pub(crate) async fn logout_all_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<logout_all::v3::Request>, body: Ruma<logout_all::v3::Request>,
) -> Result<logout_all::v3::Response> { ) -> Result<logout_all::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services services
.users .users
.all_device_ids(sender_user) .all_device_ids(body.sender_user())
.for_each(|device_id| services.users.remove_device(sender_user, device_id)) .for_each(|device_id| services.users.remove_device(body.sender_user(), device_id))
.await; .await;
Ok(logout_all::v3::Response::new()) Ok(logout_all::v3::Response::new())

View file

@ -1,11 +1,11 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Result, err, Err, Result, err,
matrix::pdu::{PduBuilder, PduEvent}, matrix::{Event, pdu::PduBuilder},
utils::BoolExt, utils::BoolExt,
}; };
use conduwuit_service::Services; use conduwuit_service::Services;
use futures::TryStreamExt; use futures::{FutureExt, TryStreamExt};
use ruma::{ use ruma::{
OwnedEventId, RoomId, UserId, OwnedEventId, RoomId, UserId,
api::client::state::{get_state_events, get_state_events_for_key, send_state_event}, api::client::state::{get_state_events, get_state_events_for_key, send_state_event},
@ -21,6 +21,7 @@ use ruma::{
}, },
serde::Raw, serde::Raw,
}; };
use serde_json::json;
use crate::{Ruma, RumaResponse}; use crate::{Ruma, RumaResponse};
@ -63,6 +64,7 @@ pub(crate) async fn send_state_event_for_empty_key_route(
body: Ruma<send_state_event::v3::Request>, body: Ruma<send_state_event::v3::Request>,
) -> Result<RumaResponse<send_state_event::v3::Response>> { ) -> Result<RumaResponse<send_state_event::v3::Response>> {
send_state_event_for_key_route(State(services), body) send_state_event_for_key_route(State(services), body)
.boxed()
.await .await
.map(RumaResponse) .map(RumaResponse)
} }
@ -77,7 +79,7 @@ pub(crate) async fn get_state_events_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<get_state_events::v3::Request>, body: Ruma<get_state_events::v3::Request>,
) -> Result<get_state_events::v3::Response> { ) -> Result<get_state_events::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
if !services if !services
.rooms .rooms
@ -93,7 +95,7 @@ pub(crate) async fn get_state_events_route(
.rooms .rooms
.state_accessor .state_accessor
.room_state_full_pdus(&body.room_id) .room_state_full_pdus(&body.room_id)
.map_ok(PduEvent::into_state_event) .map_ok(Event::into_format)
.try_collect() .try_collect()
.await?, .await?,
}) })
@ -144,7 +146,18 @@ pub(crate) async fn get_state_events_for_key_route(
Ok(get_state_events_for_key::v3::Response { Ok(get_state_events_for_key::v3::Response {
content: event_format.or(|| event.get_content_as_value()), content: event_format.or(|| event.get_content_as_value()),
event: event_format.then(|| event.into_state_event_value()), event: event_format.then(|| {
json!({
"content": event.content(),
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"room_id": event.room_id(),
"sender": event.sender(),
"state_key": event.state_key(),
"type": event.kind(),
"unsigned": event.unsigned(),
})
}),
}) })
} }

View file

@ -473,9 +473,7 @@ async fn handle_left_room(
prev_batch: Some(next_batch.to_string()), prev_batch: Some(next_batch.to_string()),
events: Vec::new(), events: Vec::new(),
}, },
state: RoomState { state: RoomState { events: vec![event.into_format()] },
events: vec![event.into_sync_state_event()],
},
})); }));
} }
@ -559,7 +557,7 @@ async fn handle_left_room(
continue; continue;
} }
left_state_events.push(pdu.into_sync_state_event()); left_state_events.push(pdu.into_format());
} }
} }
@ -645,7 +643,7 @@ async fn load_joined_room(
let lazy_loading_context = &lazy_loading::Context { let lazy_loading_context = &lazy_loading::Context {
user_id: sender_user, user_id: sender_user,
device_id: sender_device, device_id: Some(sender_device),
room_id, room_id,
token: Some(since), token: Some(since),
options: Some(&filter.room.state.lazy_load_options), options: Some(&filter.room.state.lazy_load_options),
@ -755,7 +753,7 @@ async fn load_joined_room(
.wide_filter_map(|item| ignored_filter(services, item, sender_user)) .wide_filter_map(|item| ignored_filter(services, item, sender_user))
.map(at!(1)) .map(at!(1))
.chain(joined_sender_member.into_iter().stream()) .chain(joined_sender_member.into_iter().stream())
.map(|pdu| pdu.to_sync_room_event()) .map(Event::into_format)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let account_data_events = services let account_data_events = services
@ -877,10 +875,7 @@ async fn load_joined_room(
events: room_events, events: room_events,
}, },
state: RoomState { state: RoomState {
events: state_events events: state_events.into_iter().map(Event::into_format).collect(),
.into_iter()
.map(PduEvent::into_sync_state_event)
.collect(),
}, },
ephemeral: Ephemeral { events: edus }, ephemeral: Ephemeral { events: edus },
unread_thread_notifications: BTreeMap::new(), unread_thread_notifications: BTreeMap::new(),

View file

@ -6,7 +6,7 @@ use std::{
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Error, PduCount, PduEvent, Result, debug, error, extract_variant, Err, Error, Event, PduCount, Result, at, debug, error, extract_variant,
matrix::TypeStateKey, matrix::TypeStateKey,
utils::{ utils::{
BoolExt, IterStream, ReadyExt, TryFutureExtExt, BoolExt, IterStream, ReadyExt, TryFutureExtExt,
@ -604,7 +604,8 @@ pub(crate) async fn sync_events_v4_route(
.iter() .iter()
.stream() .stream()
.filter_map(|item| ignored_filter(&services, item.clone(), sender_user)) .filter_map(|item| ignored_filter(&services, item.clone(), sender_user))
.map(|(_, pdu)| pdu.to_sync_room_event()) .map(at!(1))
.map(Event::into_format)
.collect() .collect()
.await; .await;
@ -626,7 +627,7 @@ pub(crate) async fn sync_events_v4_route(
.state_accessor .state_accessor
.room_state_get(room_id, &state.0, &state.1) .room_state_get(room_id, &state.0, &state.1)
.await .await
.map(PduEvent::into_sync_state_event) .map(Event::into_format)
.ok() .ok()
}) })
.collect() .collect()

View file

@ -7,11 +7,8 @@ use std::{
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Error, Result, error, extract_variant, is_equal_to, Err, Error, Result, at, error, extract_variant, is_equal_to,
matrix::{ matrix::{Event, TypeStateKey, pdu::PduCount},
TypeStateKey,
pdu::{PduCount, PduEvent},
},
trace, trace,
utils::{ utils::{
BoolExt, FutureBoolExt, IterStream, ReadyExt, TryFutureExtExt, BoolExt, FutureBoolExt, IterStream, ReadyExt, TryFutureExtExt,
@ -515,7 +512,8 @@ where
.iter() .iter()
.stream() .stream()
.filter_map(|item| ignored_filter(services, item.clone(), sender_user)) .filter_map(|item| ignored_filter(services, item.clone(), sender_user))
.map(|(_, pdu)| pdu.to_sync_room_event()) .map(at!(1))
.map(Event::into_format)
.collect() .collect()
.await; .await;
@ -537,7 +535,7 @@ where
.state_accessor .state_accessor
.room_state_get(room_id, &state.0, &state.1) .room_state_get(room_id, &state.0, &state.1)
.await .await
.map(PduEvent::into_sync_state_event) .map(Event::into_format)
.ok() .ok()
}) })
.collect() .collect()

View file

@ -21,7 +21,7 @@ pub(crate) async fn update_tag_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<create_tag::v3::Request>, body: Ruma<create_tag::v3::Request>,
) -> Result<create_tag::v3::Response> { ) -> Result<create_tag::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
let mut tags_event = services let mut tags_event = services
.account_data .account_data
@ -42,7 +42,7 @@ pub(crate) async fn update_tag_route(
Some(&body.room_id), Some(&body.room_id),
sender_user, sender_user,
RoomAccountDataEventType::Tag, RoomAccountDataEventType::Tag,
&serde_json::to_value(tags_event).expect("to json value always works"), &serde_json::to_value(tags_event)?,
) )
.await?; .await?;
@ -58,7 +58,7 @@ pub(crate) async fn delete_tag_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<delete_tag::v3::Request>, body: Ruma<delete_tag::v3::Request>,
) -> Result<delete_tag::v3::Response> { ) -> Result<delete_tag::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
let mut tags_event = services let mut tags_event = services
.account_data .account_data
@ -76,7 +76,7 @@ pub(crate) async fn delete_tag_route(
Some(&body.room_id), Some(&body.room_id),
sender_user, sender_user,
RoomAccountDataEventType::Tag, RoomAccountDataEventType::Tag,
&serde_json::to_value(tags_event).expect("to json value always works"), &serde_json::to_value(tags_event)?,
) )
.await?; .await?;
@ -92,7 +92,7 @@ pub(crate) async fn get_tags_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<get_tags::v3::Request>, body: Ruma<get_tags::v3::Request>,
) -> Result<get_tags::v3::Response> { ) -> Result<get_tags::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
let tags_event = services let tags_event = services
.account_data .account_data

View file

@ -1,7 +1,10 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Result, at, Result, at,
matrix::pdu::{PduCount, PduEvent}, matrix::{
Event,
pdu::{PduCount, PduEvent},
},
}; };
use futures::StreamExt; use futures::StreamExt;
use ruma::{api::client::threads::get_threads, uint}; use ruma::{api::client::threads::get_threads, uint};
@ -56,7 +59,7 @@ pub(crate) async fn get_threads_route(
chunk: threads chunk: threads
.into_iter() .into_iter()
.map(at!(1)) .map(at!(1))
.map(PduEvent::into_room_event) .map(Event::into_format)
.collect(), .collect(),
}) })
} }

View file

@ -21,7 +21,7 @@ pub(crate) async fn send_event_to_device_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<send_event_to_device::v3::Request>, body: Ruma<send_event_to_device::v3::Request>,
) -> Result<send_event_to_device::v3::Response> { ) -> Result<send_event_to_device::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
let sender_device = body.sender_device.as_deref(); let sender_device = body.sender_device.as_deref();
// Check if this is a new transaction id // Check if this is a new transaction id
@ -47,7 +47,7 @@ pub(crate) async fn send_event_to_device_route(
serde_json::to_writer( serde_json::to_writer(
&mut buf, &mut buf,
&federation::transactions::edu::Edu::DirectToDevice(DirectDeviceContent { &federation::transactions::edu::Edu::DirectToDevice(DirectDeviceContent {
sender: sender_user.clone(), sender: sender_user.to_owned(),
ev_type: body.event_type.clone(), ev_type: body.event_type.clone(),
message_id: count.to_string().into(), message_id: count.to_string().into(),
messages, messages,

View file

@ -69,7 +69,7 @@ pub(crate) async fn delete_timezone_key_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<delete_timezone_key::unstable::Request>, body: Ruma<delete_timezone_key::unstable::Request>,
) -> Result<delete_timezone_key::unstable::Response> { ) -> Result<delete_timezone_key::unstable::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
if *sender_user != body.user_id && body.appservice_info.is_none() { if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user"))); return Err!(Request(Forbidden("You cannot update the profile of another user")));
@ -97,7 +97,7 @@ pub(crate) async fn set_timezone_key_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<set_timezone_key::unstable::Request>, body: Ruma<set_timezone_key::unstable::Request>,
) -> Result<set_timezone_key::unstable::Response> { ) -> Result<set_timezone_key::unstable::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
if *sender_user != body.user_id && body.appservice_info.is_none() { if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user"))); return Err!(Request(Forbidden("You cannot update the profile of another user")));
@ -125,7 +125,7 @@ pub(crate) async fn set_profile_key_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<set_profile_key::unstable::Request>, body: Ruma<set_profile_key::unstable::Request>,
) -> Result<set_profile_key::unstable::Response> { ) -> Result<set_profile_key::unstable::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
if *sender_user != body.user_id && body.appservice_info.is_none() { if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user"))); return Err!(Request(Forbidden("You cannot update the profile of another user")));
@ -218,7 +218,7 @@ pub(crate) async fn delete_profile_key_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<delete_profile_key::unstable::Request>, body: Ruma<delete_profile_key::unstable::Request>,
) -> Result<delete_profile_key::unstable::Response> { ) -> Result<delete_profile_key::unstable::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user();
if *sender_user != body.user_id && body.appservice_info.is_none() { if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user"))); return Err!(Request(Forbidden("You cannot update the profile of another user")));

View file

@ -1,10 +1,7 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Result, Result,
utils::{ utils::{future::BoolExt, stream::BroadbandExt},
future::BoolExt,
stream::{BroadbandExt, ReadyExt},
},
}; };
use futures::{FutureExt, StreamExt, pin_mut}; use futures::{FutureExt, StreamExt, pin_mut};
use ruma::{ use ruma::{
@ -37,17 +34,18 @@ pub(crate) async fn search_users_route(
let mut users = services let mut users = services
.users .users
.stream() .stream()
.ready_filter(|user_id| user_id.as_str().to_lowercase().contains(&search_term))
.map(ToOwned::to_owned) .map(ToOwned::to_owned)
.broad_filter_map(async |user_id| { .broad_filter_map(async |user_id| {
let display_name = services.users.displayname(&user_id).await.ok(); let display_name = services.users.displayname(&user_id).await.ok();
let user_id_matches = user_id.as_str().to_lowercase().contains(&search_term);
let display_name_matches = display_name let display_name_matches = display_name
.as_deref() .as_deref()
.map(str::to_lowercase) .map(str::to_lowercase)
.is_some_and(|display_name| display_name.contains(&search_term)); .is_some_and(|display_name| display_name.contains(&search_term));
if !display_name_matches { if !user_id_matches && !display_name_matches {
return None; return None;
} }

View file

@ -2,7 +2,10 @@ use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use base64::{Engine as _, engine::general_purpose}; use base64::{Engine as _, engine::general_purpose};
use conduwuit::{ use conduwuit::{
Err, Error, PduEvent, Result, err, pdu::gen_event_id, utils, utils::hash::sha256, warn, Err, Error, PduEvent, Result, err,
matrix::{Event, event::gen_event_id},
utils::{self, hash::sha256},
warn,
}; };
use ruma::{ use ruma::{
CanonicalJsonValue, OwnedUserId, UserId, CanonicalJsonValue, OwnedUserId, UserId,
@ -56,7 +59,7 @@ pub(crate) async fn create_invite_route(
} }
let mut signed_event = utils::to_canonical_object(&body.event) let mut signed_event = utils::to_canonical_object(&body.event)
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invite event is invalid."))?; .map_err(|_| err!(Request(InvalidParam("Invite event is invalid."))))?;
let invited_user: OwnedUserId = signed_event let invited_user: OwnedUserId = signed_event
.get("state_key") .get("state_key")
@ -111,7 +114,7 @@ pub(crate) async fn create_invite_route(
let pdu: PduEvent = serde_json::from_value(event.into()) let pdu: PduEvent = serde_json::from_value(event.into())
.map_err(|e| err!(Request(BadJson("Invalid invite event PDU: {e}"))))?; .map_err(|e| err!(Request(BadJson("Invalid invite event PDU: {e}"))))?;
invite_state.push(pdu.to_stripped_state_event()); invite_state.push(pdu.to_format());
// If we are active in the room, the remote server will notify us about the // If we are active in the room, the remote server will notify us about the
// join/invite through /send. If we are not in the room, we need to manually // join/invite through /send. If we are not in the room, we need to manually
@ -144,7 +147,7 @@ pub(crate) async fn create_invite_route(
.send_appservice_request( .send_appservice_request(
appservice.registration.clone(), appservice.registration.clone(),
ruma::api::appservice::event::push_events::v1::Request { ruma::api::appservice::event::push_events::v1::Request {
events: vec![pdu.to_room_event()], events: vec![pdu.to_format()],
txn_id: general_purpose::URL_SAFE_NO_PAD txn_id: general_purpose::URL_SAFE_NO_PAD
.encode(sha256::hash(pdu.event_id.as_bytes())) .encode(sha256::hash(pdu.event_id.as_bytes()))
.into(), .into(),

View file

@ -5,7 +5,7 @@ use std::borrow::Borrow;
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Result, at, err, Err, Result, at, err,
pdu::gen_event_id_canonical_json, matrix::event::gen_event_id_canonical_json,
utils::stream::{IterStream, TryBroadbandExt}, utils::stream::{IterStream, TryBroadbandExt},
warn, warn,
}; };

View file

@ -1,7 +1,7 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Result, err, Err, Result, err,
matrix::pdu::{PduEvent, gen_event_id_canonical_json}, matrix::{event::gen_event_id_canonical_json, pdu::PduEvent},
warn, warn,
}; };
use futures::FutureExt; use futures::FutureExt;

View file

@ -1,7 +1,7 @@
#![allow(deprecated)] #![allow(deprecated)]
use axum::extract::State; use axum::extract::State;
use conduwuit::{Err, Result, err, matrix::pdu::gen_event_id_canonical_json}; use conduwuit::{Err, Result, err, matrix::event::gen_event_id_canonical_json};
use conduwuit_service::Services; use conduwuit_service::Services;
use futures::FutureExt; use futures::FutureExt;
use ruma::{ use ruma::{

View file

@ -88,10 +88,7 @@ impl PartialProxyConfig {
} }
} }
match (included_because, excluded_because) { match (included_because, excluded_because) {
| (Some(a), Some(b)) if a.more_specific_than(b) => Some(&self.url), /* included for | (Some(a), Some(b)) if a.more_specific_than(b) => Some(&self.url),
* a more specific
* reason */
// than excluded
| (Some(_), None) => Some(&self.url), | (Some(_), None) => Some(&self.url),
| _ => None, | _ => None,
} }

View file

@ -84,10 +84,12 @@ fn append_features(features: &mut Vec<String>, manifest: &str) -> Result<()> {
fn init_dependencies() -> Result<DepsSet> { fn init_dependencies() -> Result<DepsSet> {
let manifest = Manifest::from_str(WORKSPACE_MANIFEST)?; let manifest = Manifest::from_str(WORKSPACE_MANIFEST)?;
Ok(manifest let deps_set = manifest
.workspace .workspace
.as_ref() .as_ref()
.expect("manifest has workspace section") .expect("manifest has workspace section")
.dependencies .dependencies
.clone()) .clone();
Ok(deps_set)
} }

View file

@ -1,63 +1,188 @@
use ruma::{EventId, MilliSecondsSinceUnixEpoch, RoomId, UserId, events::TimelineEventType}; mod content;
use serde_json::value::RawValue as RawJsonValue; mod filter;
mod format;
mod id;
mod redact;
mod relation;
mod type_ext;
mod unsigned;
use std::fmt::Debug;
use ruma::{
CanonicalJsonObject, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId,
RoomVersionId, UserId, events::TimelineEventType,
};
use serde::Deserialize;
use serde_json::{Value as JsonValue, value::RawValue as RawJsonValue};
pub use self::{filter::Matches, id::*, relation::RelationTypeEqual, type_ext::TypeExt};
use super::{pdu::Pdu, state_key::StateKey};
use crate::{Result, utils};
/// Abstraction of a PDU so users can have their own PDU types. /// Abstraction of a PDU so users can have their own PDU types.
pub trait Event { pub trait Event: Clone + Debug {
/// Serialize into a Ruma JSON format, consuming.
#[inline]
fn into_format<T>(self) -> T
where
T: From<format::Owned<Self>>,
Self: Sized,
{
format::Owned(self).into()
}
/// Serialize into a Ruma JSON format
#[inline]
fn to_format<'a, T>(&'a self) -> T
where
T: From<format::Ref<'a, Self>>,
Self: Sized + 'a,
{
format::Ref(self).into()
}
#[inline]
fn contains_unsigned_property<T>(&self, property: &str, is_type: T) -> bool
where
T: FnOnce(&JsonValue) -> bool,
Self: Sized,
{
unsigned::contains_unsigned_property::<T, _>(self, property, is_type)
}
#[inline]
fn get_unsigned_property<T>(&self, property: &str) -> Result<T>
where
T: for<'de> Deserialize<'de>,
Self: Sized,
{
unsigned::get_unsigned_property::<T, _>(self, property)
}
#[inline]
fn get_unsigned_as_value(&self) -> JsonValue
where
Self: Sized,
{
unsigned::get_unsigned_as_value(self)
}
#[inline]
fn get_unsigned<T>(&self) -> Result<T>
where
T: for<'de> Deserialize<'de>,
Self: Sized,
{
unsigned::get_unsigned::<T, _>(self)
}
#[inline]
fn get_content_as_value(&self) -> JsonValue
where
Self: Sized,
{
content::as_value(self)
}
#[inline]
fn get_content<T>(&self) -> Result<T>
where
for<'de> T: Deserialize<'de>,
Self: Sized,
{
content::get::<T, _>(self)
}
#[inline]
fn redacts_id(&self, room_version: &RoomVersionId) -> Option<OwnedEventId>
where
Self: Sized,
{
redact::redacts_id(self, room_version)
}
#[inline]
fn is_redacted(&self) -> bool
where
Self: Sized,
{
redact::is_redacted(self)
}
#[inline]
fn into_canonical_object(self) -> CanonicalJsonObject
where
Self: Sized,
{
utils::to_canonical_object(self.into_pdu()).expect("failed to create Value::Object")
}
#[inline]
fn to_canonical_object(&self) -> CanonicalJsonObject {
utils::to_canonical_object(self.as_pdu()).expect("failed to create Value::Object")
}
#[inline]
fn into_value(self) -> JsonValue
where
Self: Sized,
{
serde_json::to_value(self.into_pdu()).expect("failed to create JSON Value")
}
#[inline]
fn to_value(&self) -> JsonValue {
serde_json::to_value(self.as_pdu()).expect("failed to create JSON Value")
}
#[inline]
fn as_mut_pdu(&mut self) -> &mut Pdu { unimplemented!("not a mutable Pdu") }
fn as_pdu(&self) -> &Pdu;
fn into_pdu(self) -> Pdu;
fn is_owned(&self) -> bool;
//
// Canonical properties
//
/// All the authenticating events for this event.
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_;
/// The event's content.
fn content(&self) -> &RawJsonValue;
/// The `EventId` of this event. /// The `EventId` of this event.
fn event_id(&self) -> &EventId; fn event_id(&self) -> &EventId;
/// The time of creation on the originating server.
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch;
/// The events before this event.
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_;
/// If this event is a redaction event this is the event it redacts.
fn redacts(&self) -> Option<&EventId>;
/// The `RoomId` of this event. /// The `RoomId` of this event.
fn room_id(&self) -> &RoomId; fn room_id(&self) -> &RoomId;
/// The `UserId` of this event. /// The `UserId` of this event.
fn sender(&self) -> &UserId; fn sender(&self) -> &UserId;
/// The time of creation on the originating server.
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch;
/// The event type.
fn event_type(&self) -> &TimelineEventType;
/// The event's content.
fn content(&self) -> &RawJsonValue;
/// The state key for this event. /// The state key for this event.
fn state_key(&self) -> Option<&str>; fn state_key(&self) -> Option<&str>;
/// The events before this event. /// The event type.
// Requires GATs to avoid boxing (and TAIT for making it convenient). fn kind(&self) -> &TimelineEventType;
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Send + '_;
/// All the authenticating events for this event. /// Metadata container; peer-trusted only.
// Requires GATs to avoid boxing (and TAIT for making it convenient). fn unsigned(&self) -> Option<&RawJsonValue>;
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Send + '_;
/// If this event is a redaction event this is the event it redacts. //#[deprecated]
fn redacts(&self) -> Option<&EventId>; #[inline]
} fn event_type(&self) -> &TimelineEventType { self.kind() }
impl<T: Event> Event for &T {
fn event_id(&self) -> &EventId { (*self).event_id() }
fn room_id(&self) -> &RoomId { (*self).room_id() }
fn sender(&self) -> &UserId { (*self).sender() }
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch { (*self).origin_server_ts() }
fn event_type(&self) -> &TimelineEventType { (*self).event_type() }
fn content(&self) -> &RawJsonValue { (*self).content() }
fn state_key(&self) -> Option<&str> { (*self).state_key() }
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Send + '_ {
(*self).prev_events()
}
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Send + '_ {
(*self).auth_events()
}
fn redacts(&self) -> Option<&EventId> { (*self).redacts() }
} }

View file

@ -0,0 +1,21 @@
use serde::Deserialize;
use serde_json::value::Value as JsonValue;
use super::Event;
use crate::{Result, err};
#[inline]
#[must_use]
pub(super) fn as_value<E: Event>(event: &E) -> JsonValue {
get(event).expect("Failed to represent Event content as JsonValue")
}
#[inline]
pub(super) fn get<T, E>(event: &E) -> Result<T>
where
T: for<'de> Deserialize<'de>,
E: Event,
{
serde_json::from_str(event.content().get())
.map_err(|e| err!(Request(BadJson("Failed to deserialize content into type: {e}"))))
}

View file

@ -0,0 +1,93 @@
use ruma::api::client::filter::{RoomEventFilter, UrlFilter};
use serde_json::Value;
use super::Event;
use crate::is_equal_to;
pub trait Matches<E: Event> {
fn matches(&self, event: &E) -> bool;
}
impl<E: Event> Matches<E> for &RoomEventFilter {
#[inline]
fn matches(&self, event: &E) -> bool {
if !matches_sender(event, self) {
return false;
}
if !matches_room(event, self) {
return false;
}
if !matches_type(event, self) {
return false;
}
if !matches_url(event, self) {
return false;
}
true
}
}
fn matches_room<E: Event>(event: &E, filter: &RoomEventFilter) -> bool {
if filter.not_rooms.iter().any(is_equal_to!(event.room_id())) {
return false;
}
if let Some(rooms) = filter.rooms.as_ref() {
if !rooms.iter().any(is_equal_to!(event.room_id())) {
return false;
}
}
true
}
fn matches_sender<E: Event>(event: &E, filter: &RoomEventFilter) -> bool {
if filter.not_senders.iter().any(is_equal_to!(event.sender())) {
return false;
}
if let Some(senders) = filter.senders.as_ref() {
if !senders.iter().any(is_equal_to!(event.sender())) {
return false;
}
}
true
}
fn matches_type<E: Event>(event: &E, filter: &RoomEventFilter) -> bool {
let kind = event.kind().to_cow_str();
if filter.not_types.iter().any(is_equal_to!(&kind)) {
return false;
}
if let Some(types) = filter.types.as_ref() {
if !types.iter().any(is_equal_to!(&kind)) {
return false;
}
}
true
}
fn matches_url<E: Event>(event: &E, filter: &RoomEventFilter) -> bool {
let Some(url_filter) = filter.url_filter.as_ref() else {
return true;
};
//TODO: might be better to use Ruma's Raw rather than serde here
let url = event
.get_content_as_value()
.get("url")
.is_some_and(Value::is_string);
match url_filter {
| UrlFilter::EventsWithUrl => url,
| UrlFilter::EventsWithoutUrl => !url,
}
}

View file

@ -0,0 +1,219 @@
use ruma::{
events::{
AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent,
AnySyncTimelineEvent, AnyTimelineEvent, StateEvent, room::member::RoomMemberEventContent,
space::child::HierarchySpaceChildEvent,
},
serde::Raw,
};
use serde_json::json;
use super::{Event, redact};
pub struct Owned<E: Event>(pub(super) E);
pub struct Ref<'a, E: Event>(pub(super) &'a E);
impl<E: Event> From<Owned<E>> for Raw<AnySyncTimelineEvent> {
fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
}
impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnySyncTimelineEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let (redacts, content) = redact::copy(event);
let mut json = json!({
"content": content,
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"sender": event.sender(),
"type": event.event_type(),
});
if let Some(redacts) = redacts {
json["redacts"] = json!(redacts);
}
if let Some(state_key) = event.state_key() {
json["state_key"] = json!(state_key);
}
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
}
}
impl<E: Event> From<Owned<E>> for Raw<AnyTimelineEvent> {
fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
}
impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnyTimelineEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let (redacts, content) = redact::copy(event);
let mut json = json!({
"content": content,
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"room_id": event.room_id(),
"sender": event.sender(),
"type": event.kind(),
});
if let Some(redacts) = redacts {
json["redacts"] = json!(redacts);
}
if let Some(state_key) = event.state_key() {
json["state_key"] = json!(state_key);
}
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
}
}
impl<E: Event> From<Owned<E>> for Raw<AnyMessageLikeEvent> {
fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
}
impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnyMessageLikeEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let (redacts, content) = redact::copy(event);
let mut json = json!({
"content": content,
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"room_id": event.room_id(),
"sender": event.sender(),
"type": event.kind(),
});
if let Some(redacts) = &redacts {
json["redacts"] = json!(redacts);
}
if let Some(state_key) = event.state_key() {
json["state_key"] = json!(state_key);
}
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
}
}
impl<E: Event> From<Owned<E>> for Raw<AnyStateEvent> {
fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
}
impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnyStateEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let mut json = json!({
"content": event.content(),
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"room_id": event.room_id(),
"sender": event.sender(),
"state_key": event.state_key(),
"type": event.kind(),
});
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
}
}
impl<E: Event> From<Owned<E>> for Raw<AnySyncStateEvent> {
fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
}
impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnySyncStateEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let mut json = json!({
"content": event.content(),
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"sender": event.sender(),
"state_key": event.state_key(),
"type": event.kind(),
});
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
}
}
impl<E: Event> From<Owned<E>> for Raw<AnyStrippedStateEvent> {
fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
}
impl<'a, E: Event> From<Ref<'a, E>> for Raw<AnyStrippedStateEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let json = json!({
"content": event.content(),
"sender": event.sender(),
"state_key": event.state_key(),
"type": event.kind(),
});
serde_json::from_value(json).expect("Failed to serialize Event value")
}
}
impl<E: Event> From<Owned<E>> for Raw<HierarchySpaceChildEvent> {
fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
}
impl<'a, E: Event> From<Ref<'a, E>> for Raw<HierarchySpaceChildEvent> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let json = json!({
"content": event.content(),
"origin_server_ts": event.origin_server_ts(),
"sender": event.sender(),
"state_key": event.state_key(),
"type": event.kind(),
});
serde_json::from_value(json).expect("Failed to serialize Event value")
}
}
impl<E: Event> From<Owned<E>> for Raw<StateEvent<RoomMemberEventContent>> {
fn from(event: Owned<E>) -> Self { Ref(&event.0).into() }
}
impl<'a, E: Event> From<Ref<'a, E>> for Raw<StateEvent<RoomMemberEventContent>> {
fn from(event: Ref<'a, E>) -> Self {
let event = event.0;
let mut json = json!({
"content": event.content(),
"event_id": event.event_id(),
"origin_server_ts": event.origin_server_ts(),
"redacts": event.redacts(),
"room_id": event.room_id(),
"sender": event.sender(),
"state_key": event.state_key(),
"type": event.kind(),
});
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
serde_json::from_value(json).expect("Failed to serialize Event value")
}
}

View file

@ -0,0 +1,86 @@
use ruma::{
OwnedEventId, RoomVersionId,
events::{TimelineEventType, room::redaction::RoomRedactionEventContent},
};
use serde::Deserialize;
use serde_json::value::{RawValue as RawJsonValue, to_raw_value};
use super::Event;
/// Copies the `redacts` property of the event to the `content` dict and
/// vice-versa.
///
/// This follows the specification's
/// [recommendation](https://spec.matrix.org/v1.10/rooms/v11/#moving-the-redacts-property-of-mroomredaction-events-to-a-content-property):
///
/// > For backwards-compatibility with older clients, servers should add a
/// > redacts property to the top level of m.room.redaction events in when
/// > serving such events over the Client-Server API.
///
/// > For improved compatibility with newer clients, servers should add a
/// > redacts property to the content of m.room.redaction events in older
/// > room versions when serving such events over the Client-Server API.
#[must_use]
pub(super) fn copy<E: Event>(event: &E) -> (Option<OwnedEventId>, Box<RawJsonValue>) {
if *event.event_type() != TimelineEventType::RoomRedaction {
return (event.redacts().map(ToOwned::to_owned), event.content().to_owned());
}
let Ok(mut content) = event.get_content::<RoomRedactionEventContent>() else {
return (event.redacts().map(ToOwned::to_owned), event.content().to_owned());
};
if let Some(redacts) = content.redacts {
return (Some(redacts), event.content().to_owned());
}
if let Some(redacts) = event.redacts().map(ToOwned::to_owned) {
content.redacts = Some(redacts);
return (
event.redacts().map(ToOwned::to_owned),
to_raw_value(&content).expect("Must be valid, we only added redacts field"),
);
}
(event.redacts().map(ToOwned::to_owned), event.content().to_owned())
}
#[must_use]
pub(super) fn is_redacted<E: Event>(event: &E) -> bool {
let Some(unsigned) = event.unsigned() else {
return false;
};
let Ok(unsigned) = ExtractRedactedBecause::deserialize(unsigned) else {
return false;
};
unsigned.redacted_because.is_some()
}
#[must_use]
pub(super) fn redacts_id<E: Event>(
event: &E,
room_version: &RoomVersionId,
) -> Option<OwnedEventId> {
use RoomVersionId::*;
if *event.kind() != TimelineEventType::RoomRedaction {
return None;
}
match *room_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
event.redacts().map(ToOwned::to_owned),
| _ =>
event
.get_content::<RoomRedactionEventContent>()
.ok()?
.redacts,
}
}
#[derive(Deserialize)]
struct ExtractRedactedBecause {
redacted_because: Option<serde::de::IgnoredAny>,
}

View file

@ -0,0 +1,28 @@
use ruma::events::relation::RelationType;
use serde::Deserialize;
use super::Event;
pub trait RelationTypeEqual<E: Event> {
fn relation_type_equal(&self, event: &E) -> bool;
}
#[derive(Clone, Debug, Deserialize)]
struct ExtractRelatesToEventId {
#[serde(rename = "m.relates_to")]
relates_to: ExtractRelType,
}
#[derive(Clone, Debug, Deserialize)]
struct ExtractRelType {
rel_type: RelationType,
}
impl<E: Event> RelationTypeEqual<E> for RelationType {
fn relation_type_equal(&self, event: &E) -> bool {
event
.get_content()
.map(|c: ExtractRelatesToEventId| c.relates_to.rel_type)
.is_ok_and(|r| r == *self)
}
}

View file

@ -0,0 +1,32 @@
use ruma::events::{StateEventType, TimelineEventType};
use super::StateKey;
/// Convenience trait for adding event type plus state key to state maps.
pub trait TypeExt {
fn with_state_key(self, state_key: impl Into<StateKey>) -> (StateEventType, StateKey);
}
impl TypeExt for StateEventType {
fn with_state_key(self, state_key: impl Into<StateKey>) -> (StateEventType, StateKey) {
(self, state_key.into())
}
}
impl TypeExt for &StateEventType {
fn with_state_key(self, state_key: impl Into<StateKey>) -> (StateEventType, StateKey) {
(self.clone(), state_key.into())
}
}
impl TypeExt for TimelineEventType {
fn with_state_key(self, state_key: impl Into<StateKey>) -> (StateEventType, StateKey) {
(self.into(), state_key.into())
}
}
impl TypeExt for &TimelineEventType {
fn with_state_key(self, state_key: impl Into<StateKey>) -> (StateEventType, StateKey) {
(self.clone().into(), state_key.into())
}
}

View file

@ -0,0 +1,51 @@
use serde::Deserialize;
use serde_json::value::Value as JsonValue;
use super::Event;
use crate::{Result, err, is_true};
pub(super) fn contains_unsigned_property<F, E>(event: &E, property: &str, is_type: F) -> bool
where
F: FnOnce(&JsonValue) -> bool,
E: Event,
{
get_unsigned_as_value(event)
.get(property)
.map(is_type)
.is_some_and(is_true!())
}
pub(super) fn get_unsigned_property<T, E>(event: &E, property: &str) -> Result<T>
where
T: for<'de> Deserialize<'de>,
E: Event,
{
get_unsigned_as_value(event)
.get_mut(property)
.map(JsonValue::take)
.map(serde_json::from_value)
.ok_or(err!(Request(NotFound("property not found in unsigned object"))))?
.map_err(|e| err!(Database("Failed to deserialize unsigned.{property} into type: {e}")))
}
#[must_use]
pub(super) fn get_unsigned_as_value<E>(event: &E) -> JsonValue
where
E: Event,
{
get_unsigned::<JsonValue, E>(event).unwrap_or_default()
}
pub(super) fn get_unsigned<T, E>(event: &E) -> Result<T>
where
T: for<'de> Deserialize<'de>,
E: Event,
{
event
.unsigned()
.as_ref()
.map(|raw| raw.get())
.map(serde_json::from_str)
.ok_or(err!(Request(NotFound("\"unsigned\" property not found in pdu"))))?
.map_err(|e| err!(Database("Failed to deserialize \"unsigned\" into value: {e}")))
}

View file

@ -2,8 +2,10 @@
pub mod event; pub mod event;
pub mod pdu; pub mod pdu;
pub mod state_key;
pub mod state_res; pub mod state_res;
pub use event::Event; pub use event::{Event, TypeExt as EventTypeExt};
pub use pdu::{PduBuilder, PduCount, PduEvent, PduId, RawPduId, StateKey}; pub use pdu::{Pdu, PduBuilder, PduCount, PduEvent, PduId, RawPduId, ShortId};
pub use state_res::{EventTypeExt, RoomVersion, StateMap, TypeStateKey}; pub use state_key::StateKey;
pub use state_res::{RoomVersion, StateMap, TypeStateKey};

View file

@ -1,14 +1,8 @@
mod builder; mod builder;
mod content;
mod count; mod count;
mod event_id;
mod filter;
mod id; mod id;
mod raw_id; mod raw_id;
mod redact; mod redact;
mod relation;
mod state_key;
mod strip;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
mod unsigned; mod unsigned;
@ -26,38 +20,50 @@ pub use self::{
Count as PduCount, Id as PduId, Pdu as PduEvent, RawId as RawPduId, Count as PduCount, Id as PduId, Pdu as PduEvent, RawId as RawPduId,
builder::{Builder, Builder as PduBuilder}, builder::{Builder, Builder as PduBuilder},
count::Count, count::Count,
event_id::*, id::{ShortId, *},
id::*,
raw_id::*, raw_id::*,
state_key::{ShortStateKey, StateKey},
}; };
use super::Event; use super::{Event, StateKey};
use crate::Result; use crate::Result;
/// Persistent Data Unit (Event) /// Persistent Data Unit (Event)
#[derive(Clone, Deserialize, Serialize, Debug)] #[derive(Clone, Deserialize, Serialize, Debug)]
pub struct Pdu { pub struct Pdu {
pub event_id: OwnedEventId, pub event_id: OwnedEventId,
pub room_id: OwnedRoomId, pub room_id: OwnedRoomId,
pub sender: OwnedUserId, pub sender: OwnedUserId,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub origin: Option<OwnedServerName>, pub origin: Option<OwnedServerName>,
pub origin_server_ts: UInt, pub origin_server_ts: UInt,
#[serde(rename = "type")] #[serde(rename = "type")]
pub kind: TimelineEventType, pub kind: TimelineEventType,
pub content: Box<RawJsonValue>, pub content: Box<RawJsonValue>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub state_key: Option<StateKey>, pub state_key: Option<StateKey>,
pub prev_events: Vec<OwnedEventId>, pub prev_events: Vec<OwnedEventId>,
pub depth: UInt, pub depth: UInt,
pub auth_events: Vec<OwnedEventId>, pub auth_events: Vec<OwnedEventId>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub redacts: Option<OwnedEventId>, pub redacts: Option<OwnedEventId>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub unsigned: Option<Box<RawJsonValue>>, pub unsigned: Option<Box<RawJsonValue>>,
pub hashes: EventHash, pub hashes: EventHash,
#[serde(default, skip_serializing_if = "Option::is_none")]
// BTreeMap<Box<ServerName>, BTreeMap<ServerSigningKeyId, String>> // BTreeMap<Box<ServerName>, BTreeMap<ServerSigningKeyId, String>>
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signatures: Option<Box<RawJsonValue>>, pub signatures: Option<Box<RawJsonValue>>,
} }
@ -79,31 +85,106 @@ impl Pdu {
} }
impl Event for Pdu { impl Event for Pdu {
fn event_id(&self) -> &EventId { &self.event_id } #[inline]
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
fn room_id(&self) -> &RoomId { &self.room_id } self.auth_events.iter().map(AsRef::as_ref)
}
fn sender(&self) -> &UserId { &self.sender }
fn event_type(&self) -> &TimelineEventType { &self.kind }
#[inline]
fn content(&self) -> &RawJsonValue { &self.content } fn content(&self) -> &RawJsonValue { &self.content }
#[inline]
fn event_id(&self) -> &EventId { &self.event_id }
#[inline]
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch { fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
MilliSecondsSinceUnixEpoch(self.origin_server_ts) MilliSecondsSinceUnixEpoch(self.origin_server_ts)
} }
fn state_key(&self) -> Option<&str> { self.state_key.as_deref() } #[inline]
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Send + '_ {
self.prev_events.iter().map(AsRef::as_ref) self.prev_events.iter().map(AsRef::as_ref)
} }
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Send + '_ { #[inline]
fn redacts(&self) -> Option<&EventId> { self.redacts.as_deref() }
#[inline]
fn room_id(&self) -> &RoomId { &self.room_id }
#[inline]
fn sender(&self) -> &UserId { &self.sender }
#[inline]
fn state_key(&self) -> Option<&str> { self.state_key.as_deref() }
#[inline]
fn kind(&self) -> &TimelineEventType { &self.kind }
#[inline]
fn unsigned(&self) -> Option<&RawJsonValue> { self.unsigned.as_deref() }
#[inline]
fn as_mut_pdu(&mut self) -> &mut Pdu { self }
#[inline]
fn as_pdu(&self) -> &Pdu { self }
#[inline]
fn into_pdu(self) -> Pdu { self }
#[inline]
fn is_owned(&self) -> bool { true }
}
impl Event for &Pdu {
#[inline]
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
self.auth_events.iter().map(AsRef::as_ref) self.auth_events.iter().map(AsRef::as_ref)
} }
#[inline]
fn content(&self) -> &RawJsonValue { &self.content }
#[inline]
fn event_id(&self) -> &EventId { &self.event_id }
#[inline]
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
MilliSecondsSinceUnixEpoch(self.origin_server_ts)
}
#[inline]
fn prev_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
self.prev_events.iter().map(AsRef::as_ref)
}
#[inline]
fn redacts(&self) -> Option<&EventId> { self.redacts.as_deref() } fn redacts(&self) -> Option<&EventId> { self.redacts.as_deref() }
#[inline]
fn room_id(&self) -> &RoomId { &self.room_id }
#[inline]
fn sender(&self) -> &UserId { &self.sender }
#[inline]
fn state_key(&self) -> Option<&str> { self.state_key.as_deref() }
#[inline]
fn kind(&self) -> &TimelineEventType { &self.kind }
#[inline]
fn unsigned(&self) -> Option<&RawJsonValue> { self.unsigned.as_deref() }
#[inline]
fn as_pdu(&self) -> &Pdu { self }
#[inline]
fn into_pdu(self) -> Pdu { self.clone() }
#[inline]
fn is_owned(&self) -> bool { false }
} }
/// Prevent derived equality which wouldn't limit itself to event_id /// Prevent derived equality which wouldn't limit itself to event_id

View file

@ -1,20 +0,0 @@
use serde::Deserialize;
use serde_json::value::Value as JsonValue;
use crate::{Result, err, implement};
#[must_use]
#[implement(super::Pdu)]
pub fn get_content_as_value(&self) -> JsonValue {
self.get_content()
.expect("pdu content must be a valid JSON value")
}
#[implement(super::Pdu)]
pub fn get_content<T>(&self) -> Result<T>
where
T: for<'de> Deserialize<'de>,
{
serde_json::from_str(self.content.get())
.map_err(|e| err!(Database("Failed to deserialize pdu content into type: {e}")))
}

View file

@ -1,90 +0,0 @@
use ruma::api::client::filter::{RoomEventFilter, UrlFilter};
use serde_json::Value;
use crate::{implement, is_equal_to};
#[implement(super::Pdu)]
#[must_use]
pub fn matches(&self, filter: &RoomEventFilter) -> bool {
if !self.matches_sender(filter) {
return false;
}
if !self.matches_room(filter) {
return false;
}
if !self.matches_type(filter) {
return false;
}
if !self.matches_url(filter) {
return false;
}
true
}
#[implement(super::Pdu)]
fn matches_room(&self, filter: &RoomEventFilter) -> bool {
if filter.not_rooms.contains(&self.room_id) {
return false;
}
if let Some(rooms) = filter.rooms.as_ref() {
if !rooms.contains(&self.room_id) {
return false;
}
}
true
}
#[implement(super::Pdu)]
fn matches_sender(&self, filter: &RoomEventFilter) -> bool {
if filter.not_senders.contains(&self.sender) {
return false;
}
if let Some(senders) = filter.senders.as_ref() {
if !senders.contains(&self.sender) {
return false;
}
}
true
}
#[implement(super::Pdu)]
fn matches_type(&self, filter: &RoomEventFilter) -> bool {
let event_type = &self.kind.to_cow_str();
if filter.not_types.iter().any(is_equal_to!(event_type)) {
return false;
}
if let Some(types) = filter.types.as_ref() {
if !types.iter().any(is_equal_to!(event_type)) {
return false;
}
}
true
}
#[implement(super::Pdu)]
fn matches_url(&self, filter: &RoomEventFilter) -> bool {
let Some(url_filter) = filter.url_filter.as_ref() else {
return true;
};
//TODO: might be better to use Ruma's Raw rather than serde here
let url = serde_json::from_str::<Value>(self.content.get())
.expect("parsing content JSON failed")
.get("url")
.is_some_and(Value::is_string);
match url_filter {
| UrlFilter::EventsWithUrl => url,
| UrlFilter::EventsWithoutUrl => !url,
}
}

View file

@ -3,6 +3,7 @@ use crate::utils::u64_from_u8x8;
pub type ShortRoomId = ShortId; pub type ShortRoomId = ShortId;
pub type ShortEventId = ShortId; pub type ShortEventId = ShortId;
pub type ShortStateKey = ShortId;
pub type ShortId = u64; pub type ShortId = u64;
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]

View file

@ -1,117 +1,29 @@
use ruma::{ use ruma::{RoomVersionId, canonical_json::redact_content_in_place};
OwnedEventId, RoomVersionId, use serde_json::{Value as JsonValue, json, value::to_raw_value};
canonical_json::redact_content_in_place,
events::{TimelineEventType, room::redaction::RoomRedactionEventContent},
};
use serde::Deserialize;
use serde_json::{
json,
value::{RawValue as RawJsonValue, to_raw_value},
};
use crate::{Error, Result, implement}; use crate::{Error, Result, err, implement};
#[derive(Deserialize)]
struct ExtractRedactedBecause {
redacted_because: Option<serde::de::IgnoredAny>,
}
#[implement(super::Pdu)] #[implement(super::Pdu)]
pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: &Self) -> Result { pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) -> Result {
self.unsigned = None; self.unsigned = None;
let mut content = serde_json::from_str(self.content.get()) let mut content = serde_json::from_str(self.content.get())
.map_err(|_| Error::bad_database("PDU in db has invalid content."))?; .map_err(|e| err!(Request(BadJson("Failed to deserialize content into type: {e}"))))?;
redact_content_in_place(&mut content, room_version_id, self.kind.to_string()) redact_content_in_place(&mut content, room_version_id, self.kind.to_string())
.map_err(|e| Error::Redaction(self.sender.server_name().to_owned(), e))?; .map_err(|e| Error::Redaction(self.sender.server_name().to_owned(), e))?;
self.unsigned = Some( let reason = serde_json::to_value(reason).expect("Failed to preserialize reason");
to_raw_value(&json!({
"redacted_because": serde_json::to_value(reason).expect("to_value(Pdu) always works")
}))
.expect("to string always works"),
);
self.content = to_raw_value(&content).expect("to string always works"); let redacted_because = json!({
"redacted_because": reason,
});
self.unsigned = to_raw_value(&redacted_because)
.expect("Failed to serialize unsigned")
.into();
self.content = to_raw_value(&content).expect("Failed to serialize content");
Ok(()) Ok(())
} }
#[implement(super::Pdu)]
#[must_use]
pub fn is_redacted(&self) -> bool {
let Some(unsigned) = &self.unsigned else {
return false;
};
let Ok(unsigned) = ExtractRedactedBecause::deserialize(&**unsigned) else {
return false;
};
unsigned.redacted_because.is_some()
}
/// Copies the `redacts` property of the event to the `content` dict and
/// vice-versa.
///
/// This follows the specification's
/// [recommendation](https://spec.matrix.org/v1.10/rooms/v11/#moving-the-redacts-property-of-mroomredaction-events-to-a-content-property):
///
/// > For backwards-compatibility with older clients, servers should add a
/// > redacts
/// > property to the top level of m.room.redaction events in when serving
/// > such events
/// > over the Client-Server API.
///
/// > For improved compatibility with newer clients, servers should add a
/// > redacts property
/// > to the content of m.room.redaction events in older room versions when
/// > serving
/// > such events over the Client-Server API.
#[implement(super::Pdu)]
#[must_use]
pub fn copy_redacts(&self) -> (Option<OwnedEventId>, Box<RawJsonValue>) {
if self.kind == TimelineEventType::RoomRedaction {
if let Ok(mut content) =
serde_json::from_str::<RoomRedactionEventContent>(self.content.get())
{
match content.redacts {
| Some(redacts) => {
return (Some(redacts), self.content.clone());
},
| _ => match self.redacts.clone() {
| Some(redacts) => {
content.redacts = Some(redacts);
return (
self.redacts.clone(),
to_raw_value(&content)
.expect("Must be valid, we only added redacts field"),
);
},
| _ => {},
},
}
}
}
(self.redacts.clone(), self.content.clone())
}
#[implement(super::Pdu)]
#[must_use]
pub fn redacts_id(&self, room_version: &RoomVersionId) -> Option<OwnedEventId> {
use RoomVersionId::*;
if self.kind != TimelineEventType::RoomRedaction {
return None;
}
match *room_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => self.redacts.clone(),
| _ =>
self.get_content::<RoomRedactionEventContent>()
.ok()?
.redacts,
}
}

View file

@ -1,22 +0,0 @@
use ruma::events::relation::RelationType;
use serde::Deserialize;
use crate::implement;
#[derive(Clone, Debug, Deserialize)]
struct ExtractRelType {
rel_type: RelationType,
}
#[derive(Clone, Debug, Deserialize)]
struct ExtractRelatesToEventId {
#[serde(rename = "m.relates_to")]
relates_to: ExtractRelType,
}
#[implement(super::Pdu)]
#[must_use]
pub fn relation_type_equal(&self, rel_type: &RelationType) -> bool {
self.get_content()
.map(|c: ExtractRelatesToEventId| c.relates_to.rel_type)
.is_ok_and(|r| r == *rel_type)
}

View file

@ -1,257 +0,0 @@
use ruma::{
events::{
AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent,
AnySyncTimelineEvent, AnyTimelineEvent, StateEvent, room::member::RoomMemberEventContent,
space::child::HierarchySpaceChildEvent,
},
serde::Raw,
};
use serde_json::{json, value::Value as JsonValue};
use crate::implement;
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_room_event(self) -> Raw<AnyTimelineEvent> { self.to_room_event() }
#[implement(super::Pdu)]
#[must_use]
pub fn to_room_event(&self) -> Raw<AnyTimelineEvent> {
let value = self.to_room_event_value();
serde_json::from_value(value).expect("Failed to serialize Event value")
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn to_room_event_value(&self) -> JsonValue {
let (redacts, content) = self.copy_redacts();
let mut json = json!({
"content": content,
"type": self.kind,
"event_id": self.event_id,
"sender": self.sender,
"origin_server_ts": self.origin_server_ts,
"room_id": self.room_id,
});
if let Some(unsigned) = &self.unsigned {
json["unsigned"] = json!(unsigned);
}
if let Some(state_key) = &self.state_key {
json["state_key"] = json!(state_key);
}
if let Some(redacts) = &redacts {
json["redacts"] = json!(redacts);
}
json
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_message_like_event(self) -> Raw<AnyMessageLikeEvent> { self.to_message_like_event() }
#[implement(super::Pdu)]
#[must_use]
pub fn to_message_like_event(&self) -> Raw<AnyMessageLikeEvent> {
let value = self.to_message_like_event_value();
serde_json::from_value(value).expect("Failed to serialize Event value")
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn to_message_like_event_value(&self) -> JsonValue {
let (redacts, content) = self.copy_redacts();
let mut json = json!({
"content": content,
"type": self.kind,
"event_id": self.event_id,
"sender": self.sender,
"origin_server_ts": self.origin_server_ts,
"room_id": self.room_id,
});
if let Some(unsigned) = &self.unsigned {
json["unsigned"] = json!(unsigned);
}
if let Some(state_key) = &self.state_key {
json["state_key"] = json!(state_key);
}
if let Some(redacts) = &redacts {
json["redacts"] = json!(redacts);
}
json
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_sync_room_event(self) -> Raw<AnySyncTimelineEvent> { self.to_sync_room_event() }
#[implement(super::Pdu)]
#[must_use]
pub fn to_sync_room_event(&self) -> Raw<AnySyncTimelineEvent> {
let value = self.to_sync_room_event_value();
serde_json::from_value(value).expect("Failed to serialize Event value")
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn to_sync_room_event_value(&self) -> JsonValue {
let (redacts, content) = self.copy_redacts();
let mut json = json!({
"content": content,
"type": self.kind,
"event_id": self.event_id,
"sender": self.sender,
"origin_server_ts": self.origin_server_ts,
});
if let Some(unsigned) = &self.unsigned {
json["unsigned"] = json!(unsigned);
}
if let Some(state_key) = &self.state_key {
json["state_key"] = json!(state_key);
}
if let Some(redacts) = &redacts {
json["redacts"] = json!(redacts);
}
json
}
#[implement(super::Pdu)]
#[must_use]
pub fn into_state_event(self) -> Raw<AnyStateEvent> {
let value = self.into_state_event_value();
serde_json::from_value(value).expect("Failed to serialize Event value")
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_state_event_value(self) -> JsonValue {
let mut json = json!({
"content": self.content,
"type": self.kind,
"event_id": self.event_id,
"sender": self.sender,
"origin_server_ts": self.origin_server_ts,
"room_id": self.room_id,
"state_key": self.state_key,
});
if let Some(unsigned) = self.unsigned {
json["unsigned"] = json!(unsigned);
}
json
}
#[implement(super::Pdu)]
#[must_use]
pub fn into_sync_state_event(self) -> Raw<AnySyncStateEvent> {
let value = self.into_sync_state_event_value();
serde_json::from_value(value).expect("Failed to serialize Event value")
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_sync_state_event_value(self) -> JsonValue {
let mut json = json!({
"content": self.content,
"type": self.kind,
"event_id": self.event_id,
"sender": self.sender,
"origin_server_ts": self.origin_server_ts,
"state_key": self.state_key,
});
if let Some(unsigned) = &self.unsigned {
json["unsigned"] = json!(unsigned);
}
json
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_stripped_state_event(self) -> Raw<AnyStrippedStateEvent> {
self.to_stripped_state_event()
}
#[implement(super::Pdu)]
#[must_use]
pub fn to_stripped_state_event(&self) -> Raw<AnyStrippedStateEvent> {
let value = self.to_stripped_state_event_value();
serde_json::from_value(value).expect("Failed to serialize Event value")
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn to_stripped_state_event_value(&self) -> JsonValue {
json!({
"content": self.content,
"type": self.kind,
"sender": self.sender,
"state_key": self.state_key,
})
}
#[implement(super::Pdu)]
#[must_use]
pub fn into_stripped_spacechild_state_event(self) -> Raw<HierarchySpaceChildEvent> {
let value = self.into_stripped_spacechild_state_event_value();
serde_json::from_value(value).expect("Failed to serialize Event value")
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_stripped_spacechild_state_event_value(self) -> JsonValue {
json!({
"content": self.content,
"type": self.kind,
"sender": self.sender,
"state_key": self.state_key,
"origin_server_ts": self.origin_server_ts,
})
}
#[implement(super::Pdu)]
#[must_use]
pub fn into_member_event(self) -> Raw<StateEvent<RoomMemberEventContent>> {
let value = self.into_member_event_value();
serde_json::from_value(value).expect("Failed to serialize Event value")
}
#[implement(super::Pdu)]
#[must_use]
#[inline]
pub fn into_member_event_value(self) -> JsonValue {
let mut json = json!({
"content": self.content,
"type": self.kind,
"event_id": self.event_id,
"sender": self.sender,
"origin_server_ts": self.origin_server_ts,
"redacts": self.redacts,
"room_id": self.room_id,
"state_key": self.state_key,
});
if let Some(unsigned) = self.unsigned {
json["unsigned"] = json!(unsigned);
}
json
}

View file

@ -1,11 +1,10 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use ruma::MilliSecondsSinceUnixEpoch; use ruma::MilliSecondsSinceUnixEpoch;
use serde::Deserialize;
use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue, to_raw_value}; use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue, to_raw_value};
use super::Pdu; use super::Pdu;
use crate::{Result, err, implement, is_true}; use crate::{Result, err, implement};
#[implement(Pdu)] #[implement(Pdu)]
pub fn remove_transaction_id(&mut self) -> Result { pub fn remove_transaction_id(&mut self) -> Result {
@ -74,43 +73,3 @@ pub fn add_relation(&mut self, name: &str, pdu: Option<&Pdu>) -> Result {
Ok(()) Ok(())
} }
#[implement(Pdu)]
pub fn contains_unsigned_property<F>(&self, property: &str, is_type: F) -> bool
where
F: FnOnce(&JsonValue) -> bool,
{
self.get_unsigned_as_value()
.get(property)
.map(is_type)
.is_some_and(is_true!())
}
#[implement(Pdu)]
pub fn get_unsigned_property<T>(&self, property: &str) -> Result<T>
where
T: for<'de> Deserialize<'de>,
{
self.get_unsigned_as_value()
.get_mut(property)
.map(JsonValue::take)
.map(serde_json::from_value)
.ok_or(err!(Request(NotFound("property not found in unsigned object"))))?
.map_err(|e| err!(Database("Failed to deserialize unsigned.{property} into type: {e}")))
}
#[implement(Pdu)]
#[must_use]
pub fn get_unsigned_as_value(&self) -> JsonValue {
self.get_unsigned::<JsonValue>().unwrap_or_default()
}
#[implement(Pdu)]
pub fn get_unsigned<T>(&self) -> Result<JsonValue> {
self.unsigned
.as_ref()
.map(|raw| raw.get())
.map(serde_json::from_str)
.ok_or(err!(Request(NotFound("\"unsigned\" property not found in pdu"))))?
.map_err(|e| err!(Database("Failed to deserialize \"unsigned\" into value: {e}")))
}

View file

@ -1,8 +1,5 @@
use smallstr::SmallString; use smallstr::SmallString;
use super::ShortId;
pub type StateKey = SmallString<[u8; INLINE_SIZE]>; pub type StateKey = SmallString<[u8; INLINE_SIZE]>;
pub type ShortStateKey = ShortId;
const INLINE_SIZE: usize = 48; const INLINE_SIZE: usize = 48;

View file

@ -13,7 +13,6 @@ use ruma::{
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, RoomVersionId, Signatures, UserId, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, RoomVersionId, Signatures, UserId,
events::{ events::{
StateEventType, TimelineEventType, StateEventType, TimelineEventType,
pdu::{EventHash, Pdu, RoomV3Pdu},
room::{ room::{
join_rules::{JoinRule, RoomJoinRulesEventContent}, join_rules::{JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent}, member::{MembershipState, RoomMemberEventContent},
@ -26,8 +25,10 @@ use serde_json::{
value::{RawValue as RawJsonValue, to_raw_value as to_raw_json_value}, value::{RawValue as RawJsonValue, to_raw_value as to_raw_json_value},
}; };
use self::event::PduEvent; use crate::{
use crate::state_res::{self as state_res, Error, Event, Result, StateMap}; matrix::{Event, Pdu, pdu::EventHash},
state_res::{self as state_res, Error, Result, StateMap},
};
static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0); static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0);
@ -60,7 +61,7 @@ fn resolution_shallow_auth_chain(c: &mut test::Bencher) {
c.iter(|| async { c.iter(|| async {
let ev_map = store.0.clone(); let ev_map = store.0.clone();
let state_sets = [&state_at_bob, &state_at_charlie]; let state_sets = [&state_at_bob, &state_at_charlie];
let fetch = |id: OwnedEventId| ready(ev_map.get(&id).clone()); let fetch = |id: OwnedEventId| ready(ev_map.get(&id).map(ToOwned::to_owned));
let exists = |id: OwnedEventId| ready(ev_map.get(&id).is_some()); let exists = |id: OwnedEventId| ready(ev_map.get(&id).is_some());
let auth_chain_sets: Vec<HashSet<_>> = state_sets let auth_chain_sets: Vec<HashSet<_>> = state_sets
.iter() .iter()
@ -142,7 +143,7 @@ fn resolve_deeper_event_set(c: &mut test::Bencher) {
}) })
.collect(); .collect();
let fetch = |id: OwnedEventId| ready(inner.get(&id).clone()); let fetch = |id: OwnedEventId| ready(inner.get(&id).map(ToOwned::to_owned));
let exists = |id: OwnedEventId| ready(inner.get(&id).is_some()); let exists = |id: OwnedEventId| ready(inner.get(&id).is_some());
let _ = match state_res::resolve( let _ = match state_res::resolve(
&RoomVersionId::V6, &RoomVersionId::V6,
@ -246,7 +247,7 @@ impl<E: Event + Clone> TestStore<E> {
} }
} }
impl TestStore<PduEvent> { impl TestStore<Pdu> {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
fn set_up( fn set_up(
&mut self, &mut self,
@ -380,7 +381,7 @@ fn to_pdu_event<S>(
content: Box<RawJsonValue>, content: Box<RawJsonValue>,
auth_events: &[S], auth_events: &[S],
prev_events: &[S], prev_events: &[S],
) -> PduEvent ) -> Pdu
where where
S: AsRef<str>, S: AsRef<str>,
{ {
@ -403,30 +404,28 @@ where
.map(event_id) .map(event_id)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let state_key = state_key.map(ToOwned::to_owned); Pdu {
PduEvent {
event_id: id.try_into().unwrap(), event_id: id.try_into().unwrap(),
rest: Pdu::RoomV3Pdu(RoomV3Pdu { room_id: room_id().to_owned(),
room_id: room_id().to_owned(), sender: sender.to_owned(),
sender: sender.to_owned(), origin_server_ts: ts.try_into().unwrap(),
origin_server_ts: MilliSecondsSinceUnixEpoch(ts.try_into().unwrap()), state_key: state_key.map(Into::into),
state_key, kind: ev_type,
kind: ev_type, content,
content, origin: None,
redacts: None, redacts: None,
unsigned: btreemap! {}, unsigned: None,
auth_events, auth_events,
prev_events, prev_events,
depth: uint!(0), depth: uint!(0),
hashes: EventHash::new(String::new()), hashes: EventHash { sha256: String::new() },
signatures: Signatures::new(), signatures: None,
}),
} }
} }
// all graphs start with these input events // all graphs start with these input events
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn INITIAL_EVENTS() -> HashMap<OwnedEventId, PduEvent> { fn INITIAL_EVENTS() -> HashMap<OwnedEventId, Pdu> {
vec![ vec![
to_pdu_event::<&EventId>( to_pdu_event::<&EventId>(
"CREATE", "CREATE",
@ -508,7 +507,7 @@ fn INITIAL_EVENTS() -> HashMap<OwnedEventId, PduEvent> {
// all graphs start with these input events // all graphs start with these input events
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn BAN_STATE_SET() -> HashMap<OwnedEventId, PduEvent> { fn BAN_STATE_SET() -> HashMap<OwnedEventId, Pdu> {
vec![ vec![
to_pdu_event( to_pdu_event(
"PA", "PA",
@ -551,119 +550,3 @@ fn BAN_STATE_SET() -> HashMap<OwnedEventId, PduEvent> {
.map(|ev| (ev.event_id().to_owned(), ev)) .map(|ev| (ev.event_id().to_owned(), ev))
.collect() .collect()
} }
/// Convenience trait for adding event type plus state key to state maps.
trait EventTypeExt {
fn with_state_key(self, state_key: impl Into<String>) -> (StateEventType, String);
}
impl EventTypeExt for &TimelineEventType {
fn with_state_key(self, state_key: impl Into<String>) -> (StateEventType, String) {
(self.to_string().into(), state_key.into())
}
}
mod event {
use ruma::{
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, UserId,
events::{TimelineEventType, pdu::Pdu},
};
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue as RawJsonValue;
use super::Event;
impl Event for PduEvent {
fn event_id(&self) -> &EventId { &self.event_id }
fn room_id(&self) -> &RoomId {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => &ev.room_id,
| Pdu::RoomV3Pdu(ev) => &ev.room_id,
#[cfg(not(feature = "unstable-exhaustive-types"))]
| _ => unreachable!("new PDU version"),
}
}
fn sender(&self) -> &UserId {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => &ev.sender,
| Pdu::RoomV3Pdu(ev) => &ev.sender,
#[cfg(not(feature = "unstable-exhaustive-types"))]
| _ => unreachable!("new PDU version"),
}
}
fn event_type(&self) -> &TimelineEventType {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => &ev.kind,
| Pdu::RoomV3Pdu(ev) => &ev.kind,
#[cfg(not(feature = "unstable-exhaustive-types"))]
| _ => unreachable!("new PDU version"),
}
}
fn content(&self) -> &RawJsonValue {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => &ev.content,
| Pdu::RoomV3Pdu(ev) => &ev.content,
#[cfg(not(feature = "unstable-exhaustive-types"))]
| _ => unreachable!("new PDU version"),
}
}
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => ev.origin_server_ts,
| Pdu::RoomV3Pdu(ev) => ev.origin_server_ts,
#[cfg(not(feature = "unstable-exhaustive-types"))]
| _ => unreachable!("new PDU version"),
}
}
fn state_key(&self) -> Option<&str> {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => ev.state_key.as_deref(),
| Pdu::RoomV3Pdu(ev) => ev.state_key.as_deref(),
#[cfg(not(feature = "unstable-exhaustive-types"))]
| _ => unreachable!("new PDU version"),
}
}
fn prev_events(&self) -> Box<dyn DoubleEndedIterator<Item = &EventId> + Send + '_> {
match &self.rest {
| Pdu::RoomV1Pdu(ev) =>
Box::new(ev.prev_events.iter().map(|(id, _)| id.as_ref())),
| Pdu::RoomV3Pdu(ev) => Box::new(ev.prev_events.iter().map(AsRef::as_ref)),
#[cfg(not(feature = "unstable-exhaustive-types"))]
| _ => unreachable!("new PDU version"),
}
}
fn auth_events(&self) -> Box<dyn DoubleEndedIterator<Item = &EventId> + Send + '_> {
match &self.rest {
| Pdu::RoomV1Pdu(ev) =>
Box::new(ev.auth_events.iter().map(|(id, _)| id.as_ref())),
| Pdu::RoomV3Pdu(ev) => Box::new(ev.auth_events.iter().map(AsRef::as_ref)),
#[cfg(not(feature = "unstable-exhaustive-types"))]
| _ => unreachable!("new PDU version"),
}
}
fn redacts(&self) -> Option<&EventId> {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => ev.redacts.as_deref(),
| Pdu::RoomV3Pdu(ev) => ev.redacts.as_deref(),
#[cfg(not(feature = "unstable-exhaustive-types"))]
| _ => unreachable!("new PDU version"),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub(crate) struct PduEvent {
pub(crate) event_id: OwnedEventId,
#[serde(flatten)]
pub(crate) rest: Pdu,
}
}

View file

@ -136,17 +136,17 @@ pub fn auth_types_for_event(
event_id = incoming_event.event_id().as_str(), event_id = incoming_event.event_id().as_str(),
) )
)] )]
pub async fn auth_check<F, Fut, Fetched, Incoming>( pub async fn auth_check<E, F, Fut>(
room_version: &RoomVersion, room_version: &RoomVersion,
incoming_event: &Incoming, incoming_event: &E,
current_third_party_invite: Option<&Incoming>, current_third_party_invite: Option<&E>,
fetch_state: F, fetch_state: F,
) -> Result<bool, Error> ) -> Result<bool, Error>
where where
F: Fn(&StateEventType, &str) -> Fut + Send, F: Fn(&StateEventType, &str) -> Fut + Send,
Fut: Future<Output = Option<Fetched>> + Send, Fut: Future<Output = Option<E>> + Send,
Fetched: Event + Send, E: Event + Send + Sync,
Incoming: Event + Send + Sync, for<'a> &'a E: Event + Send,
{ {
debug!( debug!(
event_id = format!("{}", incoming_event.event_id()), event_id = format!("{}", incoming_event.event_id()),
@ -514,20 +514,24 @@ where
/// event and the current State. /// event and the current State.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
fn valid_membership_change( fn valid_membership_change<E>(
room_version: &RoomVersion, room_version: &RoomVersion,
target_user: &UserId, target_user: &UserId,
target_user_membership_event: Option<&impl Event>, target_user_membership_event: Option<&E>,
sender: &UserId, sender: &UserId,
sender_membership_event: Option<&impl Event>, sender_membership_event: Option<&E>,
current_event: impl Event, current_event: &E,
current_third_party_invite: Option<&impl Event>, current_third_party_invite: Option<&E>,
power_levels_event: Option<&impl Event>, power_levels_event: Option<&E>,
join_rules_event: Option<&impl Event>, join_rules_event: Option<&E>,
user_for_join_auth: Option<&UserId>, user_for_join_auth: Option<&UserId>,
user_for_join_auth_membership: &MembershipState, user_for_join_auth_membership: &MembershipState,
create_room: &impl Event, create_room: &E,
) -> Result<bool> { ) -> Result<bool>
where
E: Event + Send + Sync,
for<'a> &'a E: Event + Send,
{
#[derive(Deserialize)] #[derive(Deserialize)]
struct GetThirdPartyInvite { struct GetThirdPartyInvite {
third_party_invite: Option<Raw<ThirdPartyInvite>>, third_party_invite: Option<Raw<ThirdPartyInvite>>,
@ -820,7 +824,7 @@ fn valid_membership_change(
/// ///
/// Does the event have the correct userId as its state_key if it's not the "" /// Does the event have the correct userId as its state_key if it's not the ""
/// state_key. /// state_key.
fn can_send_event(event: impl Event, ple: Option<impl Event>, user_level: Int) -> bool { fn can_send_event(event: &impl Event, ple: Option<&impl Event>, user_level: Int) -> bool {
let event_type_power_level = get_send_level(event.event_type(), event.state_key(), ple); let event_type_power_level = get_send_level(event.event_type(), event.state_key(), ple);
debug!( debug!(
@ -846,8 +850,8 @@ fn can_send_event(event: impl Event, ple: Option<impl Event>, user_level: Int) -
/// Confirm that the event sender has the required power levels. /// Confirm that the event sender has the required power levels.
fn check_power_levels( fn check_power_levels(
room_version: &RoomVersion, room_version: &RoomVersion,
power_event: impl Event, power_event: &impl Event,
previous_power_event: Option<impl Event>, previous_power_event: Option<&impl Event>,
user_level: Int, user_level: Int,
) -> Option<bool> { ) -> Option<bool> {
match power_event.state_key() { match power_event.state_key() {
@ -1010,7 +1014,7 @@ fn get_deserialize_levels(
/// given event. /// given event.
fn check_redaction( fn check_redaction(
_room_version: &RoomVersion, _room_version: &RoomVersion,
redaction_event: impl Event, redaction_event: &impl Event,
user_level: Int, user_level: Int,
redact_level: Int, redact_level: Int,
) -> Result<bool> { ) -> Result<bool> {
@ -1039,7 +1043,7 @@ fn check_redaction(
fn get_send_level( fn get_send_level(
e_type: &TimelineEventType, e_type: &TimelineEventType,
state_key: Option<&str>, state_key: Option<&str>,
power_lvl: Option<impl Event>, power_lvl: Option<&impl Event>,
) -> Int { ) -> Int {
power_lvl power_lvl
.and_then(|ple| { .and_then(|ple| {
@ -1062,7 +1066,7 @@ fn verify_third_party_invite(
target_user: Option<&UserId>, target_user: Option<&UserId>,
sender: &UserId, sender: &UserId,
tp_id: &ThirdPartyInvite, tp_id: &ThirdPartyInvite,
current_third_party_invite: Option<impl Event>, current_third_party_invite: Option<&impl Event>,
) -> bool { ) -> bool {
// 1. Check for user being banned happens before this is called // 1. Check for user being banned happens before this is called
// checking for mxid and token keys is done by ruma when deserializing // checking for mxid and token keys is done by ruma when deserializing
@ -1128,12 +1132,15 @@ mod tests {
}; };
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::state_res::{ use crate::{
Event, EventTypeExt, RoomVersion, StateMap, matrix::{Event, EventTypeExt, Pdu as PduEvent},
event_auth::valid_membership_change, state_res::{
test_utils::{ RoomVersion, StateMap,
INITIAL_EVENTS, INITIAL_EVENTS_CREATE_ROOM, PduEvent, alice, charlie, ella, event_id, event_auth::valid_membership_change,
member_content_ban, member_content_join, room_id, to_pdu_event, test_utils::{
INITIAL_EVENTS, INITIAL_EVENTS_CREATE_ROOM, alice, charlie, ella, event_id,
member_content_ban, member_content_join, room_id, to_pdu_event,
},
}, },
}; };

View file

@ -37,7 +37,7 @@ pub use self::{
}; };
use crate::{ use crate::{
debug, debug_error, debug, debug_error,
matrix::{event::Event, pdu::StateKey}, matrix::{Event, StateKey},
trace, trace,
utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, WidebandExt}, utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, WidebandExt},
warn, warn,
@ -74,7 +74,7 @@ type Result<T, E = Error> = crate::Result<T, E>;
/// event is part of the same room. /// event is part of the same room.
//#[tracing::instrument(level = "debug", skip(state_sets, auth_chain_sets, //#[tracing::instrument(level = "debug", skip(state_sets, auth_chain_sets,
//#[tracing::instrument(level event_fetch))] //#[tracing::instrument(level event_fetch))]
pub async fn resolve<'a, E, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, ExistsFut>( pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, ExistsFut>(
room_version: &RoomVersionId, room_version: &RoomVersionId,
state_sets: Sets, state_sets: Sets,
auth_chain_sets: &'a [HashSet<OwnedEventId, Hasher>], auth_chain_sets: &'a [HashSet<OwnedEventId, Hasher>],
@ -83,14 +83,14 @@ pub async fn resolve<'a, E, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, Exis
) -> Result<StateMap<OwnedEventId>> ) -> Result<StateMap<OwnedEventId>>
where where
Fetch: Fn(OwnedEventId) -> FetchFut + Sync, Fetch: Fn(OwnedEventId) -> FetchFut + Sync,
FetchFut: Future<Output = Option<E>> + Send, FetchFut: Future<Output = Option<Pdu>> + Send,
Exists: Fn(OwnedEventId) -> ExistsFut + Sync, Exists: Fn(OwnedEventId) -> ExistsFut + Sync,
ExistsFut: Future<Output = bool> + Send, ExistsFut: Future<Output = bool> + Send,
Sets: IntoIterator<IntoIter = SetIter> + Send, Sets: IntoIterator<IntoIter = SetIter> + Send,
SetIter: Iterator<Item = &'a StateMap<OwnedEventId>> + Clone + Send, SetIter: Iterator<Item = &'a StateMap<OwnedEventId>> + Clone + Send,
Hasher: BuildHasher + Send + Sync, Hasher: BuildHasher + Send + Sync,
E: Event + Clone + Send + Sync, Pdu: Event + Clone + Send + Sync,
for<'b> &'b E: Send, for<'b> &'b Pdu: Event + Send,
{ {
debug!("State resolution starting"); debug!("State resolution starting");
@ -221,6 +221,7 @@ where
let state_sets_iter = let state_sets_iter =
state_sets_iter.inspect(|_| state_set_count = state_set_count.saturating_add(1)); state_sets_iter.inspect(|_| state_set_count = state_set_count.saturating_add(1));
for (k, v) in state_sets_iter.flatten() { for (k, v) in state_sets_iter.flatten() {
occurrences occurrences
.entry(k) .entry(k)
@ -305,6 +306,7 @@ where
let pl = get_power_level_for_sender(&event_id, fetch_event) let pl = get_power_level_for_sender(&event_id, fetch_event)
.await .await
.ok()?; .ok()?;
Some((event_id, pl)) Some((event_id, pl))
}) })
.inspect(|(event_id, pl)| { .inspect(|(event_id, pl)| {
@ -522,6 +524,7 @@ where
Fut: Future<Output = Option<E>> + Send, Fut: Future<Output = Option<E>> + Send,
S: Stream<Item = &'a EventId> + Send + 'a, S: Stream<Item = &'a EventId> + Send + 'a,
E: Event + Clone + Send + Sync, E: Event + Clone + Send + Sync,
for<'b> &'b E: Event + Send,
{ {
debug!("starting iterative auth check"); debug!("starting iterative auth check");
@ -552,7 +555,7 @@ where
let auth_events = &auth_events; let auth_events = &auth_events;
let mut resolved_state = unconflicted_state; let mut resolved_state = unconflicted_state;
for event in &events_to_check { for event in events_to_check {
let state_key = event let state_key = event
.state_key() .state_key()
.ok_or_else(|| Error::InvalidPdu("State event had no state key".to_owned()))?; .ok_or_else(|| Error::InvalidPdu("State event had no state key".to_owned()))?;
@ -607,11 +610,15 @@ where
}); });
let fetch_state = |ty: &StateEventType, key: &str| { let fetch_state = |ty: &StateEventType, key: &str| {
future::ready(auth_state.get(&ty.with_state_key(key))) future::ready(
auth_state
.get(&ty.with_state_key(key))
.map(ToOwned::to_owned),
)
}; };
let auth_result = let auth_result =
auth_check(room_version, &event, current_third_party.as_ref(), fetch_state).await; auth_check(room_version, &event, current_third_party, fetch_state).await;
match auth_result { match auth_result {
| Ok(true) => { | Ok(true) => {
@ -794,11 +801,11 @@ where
} }
} }
fn is_type_and_key(ev: impl Event, ev_type: &TimelineEventType, state_key: &str) -> bool { fn is_type_and_key(ev: &impl Event, ev_type: &TimelineEventType, state_key: &str) -> bool {
ev.event_type() == ev_type && ev.state_key() == Some(state_key) ev.event_type() == ev_type && ev.state_key() == Some(state_key)
} }
fn is_power_event(event: impl Event) -> bool { fn is_power_event(event: &impl Event) -> bool {
match event.event_type() { match event.event_type() {
| TimelineEventType::RoomPowerLevels | TimelineEventType::RoomPowerLevels
| TimelineEventType::RoomJoinRules | TimelineEventType::RoomJoinRules
@ -859,15 +866,19 @@ mod tests {
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};
use super::{ use super::{
Event, EventTypeExt, StateMap, is_power_event, StateMap, is_power_event,
room_version::RoomVersion, room_version::RoomVersion,
test_utils::{ test_utils::{
INITIAL_EVENTS, PduEvent, TestStore, alice, bob, charlie, do_check, ella, event_id, INITIAL_EVENTS, TestStore, alice, bob, charlie, do_check, ella, event_id,
member_content_ban, member_content_join, room_id, to_init_pdu_event, to_pdu_event, member_content_ban, member_content_join, room_id, to_init_pdu_event, to_pdu_event,
zara, zara,
}, },
}; };
use crate::{debug, utils::stream::IterStream}; use crate::{
debug,
matrix::{Event, EventTypeExt, Pdu as PduEvent},
utils::stream::IterStream,
};
async fn test_event_sort() { async fn test_event_sort() {
use futures::future::ready; use futures::future::ready;

View file

@ -10,7 +10,6 @@ use ruma::{
UserId, event_id, UserId, event_id,
events::{ events::{
TimelineEventType, TimelineEventType,
pdu::{EventHash, Pdu, RoomV3Pdu},
room::{ room::{
join_rules::{JoinRule, RoomJoinRulesEventContent}, join_rules::{JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent}, member::{MembershipState, RoomMemberEventContent},
@ -23,17 +22,16 @@ use serde_json::{
value::{RawValue as RawJsonValue, to_raw_value as to_raw_json_value}, value::{RawValue as RawJsonValue, to_raw_value as to_raw_json_value},
}; };
pub(crate) use self::event::PduEvent;
use super::auth_types_for_event; use super::auth_types_for_event;
use crate::{ use crate::{
Result, info, Result, info,
matrix::{Event, EventTypeExt, StateMap}, matrix::{Event, EventTypeExt, Pdu, StateMap, pdu::EventHash},
}; };
static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0); static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0);
pub(crate) async fn do_check( pub(crate) async fn do_check(
events: &[PduEvent], events: &[Pdu],
edges: Vec<Vec<OwnedEventId>>, edges: Vec<Vec<OwnedEventId>>,
expected_state_ids: Vec<OwnedEventId>, expected_state_ids: Vec<OwnedEventId>,
) { ) {
@ -81,8 +79,8 @@ pub(crate) async fn do_check(
} }
} }
// event_id -> PduEvent // event_id -> Pdu
let mut event_map: HashMap<OwnedEventId, PduEvent> = HashMap::new(); let mut event_map: HashMap<OwnedEventId, Pdu> = HashMap::new();
// event_id -> StateMap<OwnedEventId> // event_id -> StateMap<OwnedEventId>
let mut state_at_event: HashMap<OwnedEventId, StateMap<OwnedEventId>> = HashMap::new(); let mut state_at_event: HashMap<OwnedEventId, StateMap<OwnedEventId>> = HashMap::new();
@ -265,7 +263,7 @@ impl<E: Event + Clone> TestStore<E> {
// A StateStore implementation for testing // A StateStore implementation for testing
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
impl TestStore<PduEvent> { impl TestStore<Pdu> {
pub(crate) fn set_up( pub(crate) fn set_up(
&mut self, &mut self,
) -> (StateMap<OwnedEventId>, StateMap<OwnedEventId>, StateMap<OwnedEventId>) { ) -> (StateMap<OwnedEventId>, StateMap<OwnedEventId>, StateMap<OwnedEventId>) {
@ -390,7 +388,7 @@ pub(crate) fn to_init_pdu_event(
ev_type: TimelineEventType, ev_type: TimelineEventType,
state_key: Option<&str>, state_key: Option<&str>,
content: Box<RawJsonValue>, content: Box<RawJsonValue>,
) -> PduEvent { ) -> Pdu {
let ts = SERVER_TIMESTAMP.fetch_add(1, SeqCst); let ts = SERVER_TIMESTAMP.fetch_add(1, SeqCst);
let id = if id.contains('$') { let id = if id.contains('$') {
id.to_owned() id.to_owned()
@ -398,24 +396,22 @@ pub(crate) fn to_init_pdu_event(
format!("${id}:foo") format!("${id}:foo")
}; };
let state_key = state_key.map(ToOwned::to_owned); Pdu {
PduEvent {
event_id: id.try_into().unwrap(), event_id: id.try_into().unwrap(),
rest: Pdu::RoomV3Pdu(RoomV3Pdu { room_id: room_id().to_owned(),
room_id: room_id().to_owned(), sender: sender.to_owned(),
sender: sender.to_owned(), origin_server_ts: ts.try_into().unwrap(),
origin_server_ts: MilliSecondsSinceUnixEpoch(ts.try_into().unwrap()), state_key: state_key.map(Into::into),
state_key, kind: ev_type,
kind: ev_type, content,
content, origin: None,
redacts: None, redacts: None,
unsigned: BTreeMap::new(), unsigned: None,
auth_events: vec![], auth_events: vec![],
prev_events: vec![], prev_events: vec![],
depth: uint!(0), depth: uint!(0),
hashes: EventHash::new("".to_owned()), hashes: EventHash { sha256: "".to_owned() },
signatures: ServerSignatures::default(), signatures: None,
}),
} }
} }
@ -427,7 +423,7 @@ pub(crate) fn to_pdu_event<S>(
content: Box<RawJsonValue>, content: Box<RawJsonValue>,
auth_events: &[S], auth_events: &[S],
prev_events: &[S], prev_events: &[S],
) -> PduEvent ) -> Pdu
where where
S: AsRef<str>, S: AsRef<str>,
{ {
@ -448,30 +444,28 @@ where
.map(event_id) .map(event_id)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let state_key = state_key.map(ToOwned::to_owned); Pdu {
PduEvent {
event_id: id.try_into().unwrap(), event_id: id.try_into().unwrap(),
rest: Pdu::RoomV3Pdu(RoomV3Pdu { room_id: room_id().to_owned(),
room_id: room_id().to_owned(), sender: sender.to_owned(),
sender: sender.to_owned(), origin_server_ts: ts.try_into().unwrap(),
origin_server_ts: MilliSecondsSinceUnixEpoch(ts.try_into().unwrap()), state_key: state_key.map(Into::into),
state_key, kind: ev_type,
kind: ev_type, content,
content, origin: None,
redacts: None, redacts: None,
unsigned: BTreeMap::new(), unsigned: None,
auth_events, auth_events,
prev_events, prev_events,
depth: uint!(0), depth: uint!(0),
hashes: EventHash::new("".to_owned()), hashes: EventHash { sha256: "".to_owned() },
signatures: ServerSignatures::default(), signatures: None,
}),
} }
} }
// all graphs start with these input events // all graphs start with these input events
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub(crate) fn INITIAL_EVENTS() -> HashMap<OwnedEventId, PduEvent> { pub(crate) fn INITIAL_EVENTS() -> HashMap<OwnedEventId, Pdu> {
vec![ vec![
to_pdu_event::<&EventId>( to_pdu_event::<&EventId>(
"CREATE", "CREATE",
@ -553,7 +547,7 @@ pub(crate) fn INITIAL_EVENTS() -> HashMap<OwnedEventId, PduEvent> {
// all graphs start with these input events // all graphs start with these input events
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub(crate) fn INITIAL_EVENTS_CREATE_ROOM() -> HashMap<OwnedEventId, PduEvent> { pub(crate) fn INITIAL_EVENTS_CREATE_ROOM() -> HashMap<OwnedEventId, Pdu> {
vec![to_pdu_event::<&EventId>( vec![to_pdu_event::<&EventId>(
"CREATE", "CREATE",
alice(), alice(),
@ -575,111 +569,3 @@ pub(crate) fn INITIAL_EDGES() -> Vec<OwnedEventId> {
.map(event_id) .map(event_id)
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
pub(crate) mod event {
use ruma::{
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, UserId,
events::{TimelineEventType, pdu::Pdu},
};
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue as RawJsonValue;
use crate::Event;
impl Event for PduEvent {
fn event_id(&self) -> &EventId { &self.event_id }
fn room_id(&self) -> &RoomId {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => &ev.room_id,
| Pdu::RoomV3Pdu(ev) => &ev.room_id,
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
fn sender(&self) -> &UserId {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => &ev.sender,
| Pdu::RoomV3Pdu(ev) => &ev.sender,
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
fn event_type(&self) -> &TimelineEventType {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => &ev.kind,
| Pdu::RoomV3Pdu(ev) => &ev.kind,
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
fn content(&self) -> &RawJsonValue {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => &ev.content,
| Pdu::RoomV3Pdu(ev) => &ev.content,
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => ev.origin_server_ts,
| Pdu::RoomV3Pdu(ev) => ev.origin_server_ts,
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
fn state_key(&self) -> Option<&str> {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => ev.state_key.as_deref(),
| Pdu::RoomV3Pdu(ev) => ev.state_key.as_deref(),
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
#[allow(refining_impl_trait)]
fn prev_events(&self) -> Box<dyn DoubleEndedIterator<Item = &EventId> + Send + '_> {
match &self.rest {
| Pdu::RoomV1Pdu(ev) =>
Box::new(ev.prev_events.iter().map(|(id, _)| id.as_ref())),
| Pdu::RoomV3Pdu(ev) => Box::new(ev.prev_events.iter().map(AsRef::as_ref)),
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
#[allow(refining_impl_trait)]
fn auth_events(&self) -> Box<dyn DoubleEndedIterator<Item = &EventId> + Send + '_> {
match &self.rest {
| Pdu::RoomV1Pdu(ev) =>
Box::new(ev.auth_events.iter().map(|(id, _)| id.as_ref())),
| Pdu::RoomV3Pdu(ev) => Box::new(ev.auth_events.iter().map(AsRef::as_ref)),
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
fn redacts(&self) -> Option<&EventId> {
match &self.rest {
| Pdu::RoomV1Pdu(ev) => ev.redacts.as_deref(),
| Pdu::RoomV3Pdu(ev) => ev.redacts.as_deref(),
#[allow(unreachable_patterns)]
| _ => unreachable!("new PDU version"),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[allow(clippy::exhaustive_structs)]
pub(crate) struct PduEvent {
pub(crate) event_id: OwnedEventId,
#[serde(flatten)]
pub(crate) rest: Pdu,
}
}

View file

@ -25,7 +25,9 @@ pub use info::{
rustc_flags_capture, version, rustc_flags_capture, version,
version::{name, version}, version::{name, version},
}; };
pub use matrix::{Event, EventTypeExt, PduCount, PduEvent, PduId, RoomVersion, pdu, state_res}; pub use matrix::{
Event, EventTypeExt, Pdu, PduCount, PduEvent, PduId, RoomVersion, pdu, state_res,
};
pub use server::Server; pub use server::Server;
pub use utils::{ctor, dtor, implement, result, result::Result}; pub use utils::{ctor, dtor, implement, result, result::Result};

View file

@ -44,6 +44,7 @@ impl Module {
.handle .handle
.as_ref() .as_ref()
.expect("backing library loaded by this instance"); .expect("backing library loaded by this instance");
// SAFETY: Calls dlsym(3) on unix platforms. This might not have to be unsafe // SAFETY: Calls dlsym(3) on unix platforms. This might not have to be unsafe
// if wrapped in libloading with_dlerror(). // if wrapped in libloading with_dlerror().
let sym = unsafe { handle.get::<Prototype>(cname.as_bytes()) }; let sym = unsafe { handle.get::<Prototype>(cname.as_bytes()) };

View file

@ -27,6 +27,7 @@ pub fn to_name(path: &OsStr) -> Result<String> {
.expect("path file stem") .expect("path file stem")
.to_str() .to_str()
.expect("name string"); .expect("name string");
let name = name.strip_prefix("lib").unwrap_or(name).to_owned(); let name = name.strip_prefix("lib").unwrap_or(name).to_owned();
Ok(name) Ok(name)

View file

@ -23,8 +23,10 @@ impl fmt::Display for Escape<'_> {
| '"' => "&quot;", | '"' => "&quot;",
| _ => continue, | _ => continue,
}; };
fmt.write_str(&pile_o_bits[last..i])?; fmt.write_str(&pile_o_bits[last..i])?;
fmt.write_str(s)?; fmt.write_str(s)?;
// NOTE: we only expect single byte characters here - which is fine as long as // NOTE: we only expect single byte characters here - which is fine as long as
// we only match single byte characters // we only match single byte characters
last = i.saturating_add(1); last = i.saturating_add(1);

View file

@ -1,4 +1,4 @@
use std::{fmt, 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::try_from_json_map};
@ -11,25 +11,28 @@ 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 serde::ser::Error; use serde::ser::Error;
match serde_json::to_value(value).map_err(CanonicalJsonError::SerDe)? { match serde_json::to_value(value).map_err(SerDe)? {
| serde_json::Value::Object(map) => try_from_json_map(map), | serde_json::Value::Object(map) => try_from_json_map(map),
| _ => | _ => Err(SerDe(serde_json::Error::custom("Value must be an object"))),
Err(CanonicalJsonError::SerDe(serde_json::Error::custom("Value must be an object"))),
} }
} }
pub fn deserialize_from_str< pub fn deserialize_from_str<'de, D, T, E>(deserializer: D) -> Result<T, D::Error>
'de, where
D: serde::de::Deserializer<'de>, D: serde::de::Deserializer<'de>,
T: FromStr<Err = E>, T: FromStr<Err = E>,
E: fmt::Display, E: fmt::Display,
>( {
deserializer: D, struct Visitor<T: FromStr<Err = E>, E>(PhantomData<T>);
) -> Result<T, D::Error> {
struct Visitor<T: FromStr<Err = E>, E>(std::marker::PhantomData<T>); impl<T, Err> serde::de::Visitor<'_> for Visitor<T, Err>
impl<T: FromStr<Err = Err>, Err: fmt::Display> serde::de::Visitor<'_> for Visitor<T, Err> { where
T: FromStr<Err = Err>,
Err: fmt::Display,
{
type Value = T; type Value = T;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -43,5 +46,6 @@ pub fn deserialize_from_str<
v.parse().map_err(serde::de::Error::custom) v.parse().map_err(serde::de::Error::custom)
} }
} }
deserializer.deserialize_str(Visitor(std::marker::PhantomData))
deserializer.deserialize_str(Visitor(PhantomData))
} }

View file

@ -105,14 +105,11 @@ pub fn whole_unit(d: Duration) -> Unit {
| 86_400.. => Days(d.as_secs() / 86_400), | 86_400.. => Days(d.as_secs() / 86_400),
| 3_600..=86_399 => Hours(d.as_secs() / 3_600), | 3_600..=86_399 => Hours(d.as_secs() / 3_600),
| 60..=3_599 => Mins(d.as_secs() / 60), | 60..=3_599 => Mins(d.as_secs() / 60),
| _ => match d.as_micros() { | _ => match d.as_micros() {
| 1_000_000.. => Secs(d.as_secs()), | 1_000_000.. => Secs(d.as_secs()),
| 1_000..=999_999 => Millis(d.subsec_millis().into()), | 1_000..=999_999 => Millis(d.subsec_millis().into()),
| _ => match d.as_nanos() { | _ => match d.as_nanos() {
| 1_000.. => Micros(d.subsec_micros().into()), | 1_000.. => Micros(d.subsec_micros().into()),
| _ => Nanos(d.subsec_nanos().into()), | _ => Nanos(d.subsec_nanos().into()),
}, },
}, },

View file

@ -37,7 +37,6 @@ impl Watchers {
pub(crate) fn wake(&self, key: &[u8]) { pub(crate) fn wake(&self, key: &[u8]) {
let watchers = self.watchers.read().unwrap(); let watchers = self.watchers.read().unwrap();
let mut triggered = Vec::new(); let mut triggered = Vec::new();
for length in 0..=key.len() { for length in 0..=key.len() {
if watchers.contains_key(&key[..length]) { if watchers.contains_key(&key[..length]) {
triggered.push(&key[..length]); triggered.push(&key[..length]);

View file

@ -22,10 +22,12 @@ pub(crate) fn init(
let reload_handles = LogLevelReloadHandles::default(); let reload_handles = LogLevelReloadHandles::default();
let console_span_events = fmt_span::from_str(&config.log_span_events).unwrap_or_err(); let console_span_events = fmt_span::from_str(&config.log_span_events).unwrap_or_err();
let console_filter = EnvFilter::builder() let console_filter = EnvFilter::builder()
.with_regex(config.log_filter_regex) .with_regex(config.log_filter_regex)
.parse(&config.log) .parse(&config.log)
.map_err(|e| err!(Config("log", "{e}.")))?; .map_err(|e| err!(Config("log", "{e}.")))?;
let console_layer = fmt::Layer::new() let console_layer = fmt::Layer::new()
.with_span_events(console_span_events) .with_span_events(console_span_events)
.event_format(ConsoleFormat::new(config)) .event_format(ConsoleFormat::new(config))
@ -34,6 +36,7 @@ pub(crate) fn init(
let (console_reload_filter, console_reload_handle) = let (console_reload_filter, console_reload_handle) =
reload::Layer::new(console_filter.clone()); reload::Layer::new(console_filter.clone());
reload_handles.add("console", Box::new(console_reload_handle)); reload_handles.add("console", Box::new(console_reload_handle));
let cap_state = Arc::new(capture::State::new()); let cap_state = Arc::new(capture::State::new());
@ -47,8 +50,10 @@ pub(crate) fn init(
let subscriber = { let subscriber = {
let sentry_filter = EnvFilter::try_new(&config.sentry_filter) let sentry_filter = EnvFilter::try_new(&config.sentry_filter)
.map_err(|e| err!(Config("sentry_filter", "{e}.")))?; .map_err(|e| err!(Config("sentry_filter", "{e}.")))?;
let sentry_layer = sentry_tracing::layer(); let sentry_layer = sentry_tracing::layer();
let (sentry_reload_filter, sentry_reload_handle) = reload::Layer::new(sentry_filter); let (sentry_reload_filter, sentry_reload_handle) = reload::Layer::new(sentry_filter);
reload_handles.add("sentry", Box::new(sentry_reload_handle)); reload_handles.add("sentry", Box::new(sentry_reload_handle));
subscriber.with(sentry_layer.with_filter(sentry_reload_filter)) subscriber.with(sentry_layer.with_filter(sentry_reload_filter))
}; };
@ -58,12 +63,15 @@ pub(crate) fn init(
let (flame_layer, flame_guard) = if config.tracing_flame { let (flame_layer, flame_guard) = if config.tracing_flame {
let flame_filter = EnvFilter::try_new(&config.tracing_flame_filter) let flame_filter = EnvFilter::try_new(&config.tracing_flame_filter)
.map_err(|e| err!(Config("tracing_flame_filter", "{e}.")))?; .map_err(|e| err!(Config("tracing_flame_filter", "{e}.")))?;
let (flame_layer, flame_guard) = let (flame_layer, flame_guard) =
tracing_flame::FlameLayer::with_file(&config.tracing_flame_output_path) tracing_flame::FlameLayer::with_file(&config.tracing_flame_output_path)
.map_err(|e| err!(Config("tracing_flame_output_path", "{e}.")))?; .map_err(|e| err!(Config("tracing_flame_output_path", "{e}.")))?;
let flame_layer = flame_layer let flame_layer = flame_layer
.with_empty_samples(false) .with_empty_samples(false)
.with_filter(flame_filter); .with_filter(flame_filter);
(Some(flame_layer), Some(flame_guard)) (Some(flame_layer), Some(flame_guard))
} else { } else {
(None, None) (None, None)
@ -71,19 +79,24 @@ pub(crate) fn init(
let jaeger_filter = EnvFilter::try_new(&config.jaeger_filter) let jaeger_filter = EnvFilter::try_new(&config.jaeger_filter)
.map_err(|e| err!(Config("jaeger_filter", "{e}.")))?; .map_err(|e| err!(Config("jaeger_filter", "{e}.")))?;
let jaeger_layer = config.allow_jaeger.then(|| { let jaeger_layer = config.allow_jaeger.then(|| {
opentelemetry::global::set_text_map_propagator( opentelemetry::global::set_text_map_propagator(
opentelemetry_jaeger::Propagator::new(), opentelemetry_jaeger::Propagator::new(),
); );
let tracer = opentelemetry_jaeger::new_agent_pipeline() let tracer = opentelemetry_jaeger::new_agent_pipeline()
.with_auto_split_batch(true) .with_auto_split_batch(true)
.with_service_name(conduwuit_core::name()) .with_service_name(conduwuit_core::name())
.install_batch(opentelemetry_sdk::runtime::Tokio) .install_batch(opentelemetry_sdk::runtime::Tokio)
.expect("jaeger agent pipeline"); .expect("jaeger agent pipeline");
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
let (jaeger_reload_filter, jaeger_reload_handle) = let (jaeger_reload_filter, jaeger_reload_handle) =
reload::Layer::new(jaeger_filter.clone()); reload::Layer::new(jaeger_filter.clone());
reload_handles.add("jaeger", Box::new(jaeger_reload_handle)); reload_handles.add("jaeger", Box::new(jaeger_reload_handle));
Some(telemetry.with_filter(jaeger_reload_filter)) Some(telemetry.with_filter(jaeger_reload_filter))
}); });

View file

@ -51,7 +51,9 @@ pub(crate) async fn run(server: &Arc<Server>, starts: bool) -> Result<(bool, boo
}, },
}; };
} }
server.server.stopping.store(false, Ordering::Release); server.server.stopping.store(false, Ordering::Release);
let run = main_mod.get::<RunFuncProto>("run")?; let run = main_mod.get::<RunFuncProto>("run")?;
if let Err(error) = run(server if let Err(error) = run(server
.services .services
@ -64,7 +66,9 @@ pub(crate) async fn run(server: &Arc<Server>, starts: bool) -> Result<(bool, boo
error!("Running server: {error}"); error!("Running server: {error}");
return Err(error); return Err(error);
} }
let reloads = server.server.reloading.swap(false, Ordering::AcqRel); let reloads = server.server.reloading.swap(false, Ordering::AcqRel);
let stops = !reloads || stale(server).await? <= restart_thresh(); let stops = !reloads || stale(server).await? <= restart_thresh();
let starts = reloads && stops; let starts = reloads && stops;
if stops { if stops {

View file

@ -35,11 +35,13 @@ fn options(config: &Config) -> ClientOptions {
.expect("init_sentry should only be called if sentry is enabled and this is not None") .expect("init_sentry should only be called if sentry is enabled and this is not None")
.as_str(); .as_str();
let server_name = config
.sentry_send_server_name
.then(|| config.server_name.to_string().into());
ClientOptions { ClientOptions {
dsn: Some(Dsn::from_str(dsn).expect("sentry_endpoint must be a valid URL")), dsn: Some(Dsn::from_str(dsn).expect("sentry_endpoint must be a valid URL")),
server_name: config server_name,
.sentry_send_server_name
.then(|| config.server_name.to_string().into()),
traces_sample_rate: config.sentry_traces_sample_rate, traces_sample_rate: config.sentry_traces_sample_rate,
debug: cfg!(debug_assertions), debug: cfg!(debug_assertions),
release: sentry::release_name!(), release: sentry::release_name!(),

View file

@ -98,8 +98,8 @@ async fn execute(
fn handle_result(method: &Method, uri: &Uri, result: Response) -> Result<Response, StatusCode> { fn handle_result(method: &Method, uri: &Uri, result: Response) -> Result<Response, StatusCode> {
let status = result.status(); let status = result.status();
let reason = status.canonical_reason().unwrap_or("Unknown Reason");
let code = status.as_u16(); let code = status.as_u16();
let reason = status.canonical_reason().unwrap_or("Unknown Reason");
if status.is_server_error() { if status.is_server_error() {
error!(method = ?method, uri = ?uri, "{code} {reason}"); error!(method = ?method, uri = ?uri, "{code} {reason}");

View file

@ -170,3 +170,56 @@ async fn set_room_tag(&self, room_id: &RoomId, user_id: &UserId, tag: &str) -> R
) )
.await .await
} }
/// Demote an admin, removing its rights.
#[implement(super::Service)]
pub async fn revoke_admin(&self, user_id: &UserId) -> Result {
use MembershipState::{Invite, Join, Knock, Leave};
let Ok(room_id) = self.get_admin_room().await else {
return Err!(error!("No admin room available or created."));
};
let state_lock = self.services.state.mutex.lock(&room_id).await;
let event = match self
.services
.state_accessor
.get_member(&room_id, user_id)
.await
{
| Err(e) if e.is_not_found() => return Err!("{user_id} was never an admin."),
| Err(e) => return Err!(error!(?e, "Failure occurred while attempting revoke.")),
| Ok(event) if !matches!(event.membership, Invite | Knock | Join) =>
return Err!("Cannot revoke {user_id} in membership state {:?}.", event.membership),
| Ok(event) => {
assert!(
matches!(event.membership, Invite | Knock | Join),
"Incorrect membership state to remove user."
);
event
},
};
self.services
.timeline
.build_and_append_pdu(
PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
membership: Leave,
reason: Some("Admin Revoked".into()),
is_direct: None,
join_authorized_via_users_server: None,
third_party_invite: None,
..event
}),
self.services.globals.server_user.as_ref(),
&room_id,
&state_lock,
)
.await
.map(|_| ())
}

View file

@ -4,17 +4,16 @@ mod execute;
mod grant; mod grant;
use std::{ use std::{
future::Future,
pin::Pin, pin::Pin,
sync::{Arc, RwLock as StdRwLock, Weak}, sync::{Arc, RwLock as StdRwLock, Weak},
}; };
use async_trait::async_trait; use async_trait::async_trait;
use conduwuit::{ use conduwuit_core::{
Error, PduEvent, Result, Server, debug, err, error, error::default_log, pdu::PduBuilder, Error, Event, Result, Server, debug, err, error, error::default_log, pdu::PduBuilder,
}; };
pub use create::create_admin_room; pub use create::create_admin_room;
use futures::{FutureExt, TryFutureExt}; use futures::{Future, FutureExt, TryFutureExt};
use loole::{Receiver, Sender}; use loole::{Receiver, Sender};
use ruma::{ use ruma::{
OwnedEventId, OwnedRoomId, RoomId, UserId, OwnedEventId, OwnedRoomId, RoomId, UserId,
@ -143,6 +142,13 @@ impl crate::Service for Service {
} }
impl Service { impl Service {
/// Sends markdown notice to the admin room as the admin user.
pub async fn notice(&self, body: &str) {
self.send_message(RoomMessageEventContent::notice_markdown(body))
.await
.ok();
}
/// Sends markdown message (not an m.notice for notification reasons) to the /// Sends markdown message (not an m.notice for notification reasons) to the
/// admin room as the admin user. /// admin room as the admin user.
pub async fn send_text(&self, body: &str) { pub async fn send_text(&self, body: &str) {
@ -299,13 +305,13 @@ impl Service {
return Ok(()); return Ok(());
}; };
let response_sender = if self.is_admin_room(&pdu.room_id).await { let response_sender = if self.is_admin_room(pdu.room_id()).await {
&self.services.globals.server_user &self.services.globals.server_user
} else { } else {
&pdu.sender pdu.sender()
}; };
self.respond_to_room(content, &pdu.room_id, response_sender) self.respond_to_room(content, pdu.room_id(), response_sender)
.boxed() .boxed()
.await .await
} }
@ -355,7 +361,10 @@ impl Service {
Ok(()) Ok(())
} }
pub async fn is_admin_command(&self, pdu: &PduEvent, body: &str) -> bool { pub async fn is_admin_command<E>(&self, event: &E, body: &str) -> bool
where
E: Event + Send + Sync,
{
// Server-side command-escape with public echo // Server-side command-escape with public echo
let is_escape = body.starts_with('\\'); let is_escape = body.starts_with('\\');
let is_public_escape = is_escape && body.trim_start_matches('\\').starts_with("!admin"); let is_public_escape = is_escape && body.trim_start_matches('\\').starts_with("!admin");
@ -370,8 +379,10 @@ impl Service {
return false; return false;
} }
let user_is_local = self.services.globals.user_is_local(event.sender());
// only allow public escaped commands by local admins // only allow public escaped commands by local admins
if is_public_escape && !self.services.globals.user_is_local(&pdu.sender) { if is_public_escape && !user_is_local {
return false; return false;
} }
@ -381,20 +392,20 @@ impl Service {
} }
// Prevent unescaped !admin from being used outside of the admin room // Prevent unescaped !admin from being used outside of the admin room
if is_public_prefix && !self.is_admin_room(&pdu.room_id).await { if is_public_prefix && !self.is_admin_room(event.room_id()).await {
return false; return false;
} }
// Only senders who are admin can proceed // Only senders who are admin can proceed
if !self.user_is_admin(&pdu.sender).await { if !self.user_is_admin(event.sender()).await {
return false; return false;
} }
// This will evaluate to false if the emergency password is set up so that // This will evaluate to false if the emergency password is set up so that
// the administrator can execute commands as the server user // the administrator can execute commands as the server user
let emergency_password_set = self.services.server.config.emergency_password.is_some(); let emergency_password_set = self.services.server.config.emergency_password.is_some();
let from_server = pdu.sender == *server_user && !emergency_password_set; let from_server = event.sender() == server_user && !emergency_password_set;
if from_server && self.is_admin_room(&pdu.room_id).await { if from_server && self.is_admin_room(event.room_id()).await {
return false; return false;
} }

View file

@ -242,12 +242,14 @@ async fn db_lt_12(services: &Services) -> Result<()> {
[".m.rules.contains_user_name", ".m.rule.contains_user_name"]; [".m.rules.contains_user_name", ".m.rule.contains_user_name"];
let rule = rules_list.content.get(content_rule_transformation[0]); let rule = rules_list.content.get(content_rule_transformation[0]);
if rule.is_some() {
let mut rule = rule.unwrap().clone(); if let Some(rule) = rule {
let mut rule = rule.clone();
content_rule_transformation[1].clone_into(&mut rule.rule_id); content_rule_transformation[1].clone_into(&mut rule.rule_id);
rules_list rules_list
.content .content
.shift_remove(content_rule_transformation[0]); .shift_remove(content_rule_transformation[0]);
rules_list.content.insert(rule); rules_list.content.insert(rule);
} }
} }

View file

@ -1,12 +1,12 @@
use std::{fmt::Debug, mem, sync::Arc}; use std::{fmt::Debug, mem, sync::Arc};
use bytes::BytesMut; use bytes::BytesMut;
use conduwuit::{ use conduwuit_core::{
Err, PduEvent, Result, debug_warn, err, trace, Err, Event, Result, debug_warn, err, trace,
utils::{stream::TryIgnore, string_from_bytes}, utils::{stream::TryIgnore, string_from_bytes},
warn, warn,
}; };
use database::{Deserialized, Ignore, Interfix, Json, Map}; 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::{
@ -272,32 +272,33 @@ impl Service {
} }
} }
#[tracing::instrument(skip(self, user, unread, pusher, ruleset, pdu))] #[tracing::instrument(skip(self, user, unread, pusher, ruleset, event))]
pub async fn send_push_notice( pub async fn send_push_notice<E>(
&self, &self,
user: &UserId, user: &UserId,
unread: UInt, unread: UInt,
pusher: &Pusher, pusher: &Pusher,
ruleset: Ruleset, ruleset: Ruleset,
pdu: &PduEvent, event: &E,
) -> Result<()> { ) -> Result
where
E: Event + Send + Sync,
for<'a> &'a E: Event + Send,
{
let mut notify = None; let mut notify = None;
let mut tweaks = Vec::new(); let mut tweaks = Vec::new();
let power_levels: RoomPowerLevelsEventContent = self let power_levels: RoomPowerLevelsEventContent = self
.services .services
.state_accessor .state_accessor
.room_state_get(&pdu.room_id, &StateEventType::RoomPowerLevels, "") .room_state_get(event.room_id(), &StateEventType::RoomPowerLevels, "")
.await .await
.and_then(|ev| { .and_then(|event| event.get_content())
serde_json::from_str(ev.content.get()).map_err(|e| {
err!(Database(error!("invalid m.room.power_levels event: {e:?}")))
})
})
.unwrap_or_default(); .unwrap_or_default();
let serialized = event.to_format();
for action in self for action in self
.get_actions(user, &ruleset, &power_levels, &pdu.to_sync_room_event(), &pdu.room_id) .get_actions(user, &ruleset, &power_levels, &serialized, event.room_id())
.await .await
{ {
let n = match action { let n = match action {
@ -319,7 +320,7 @@ impl Service {
} }
if notify == Some(true) { if notify == Some(true) {
self.send_notice(unread, pusher, tweaks, pdu).await?; self.send_notice(unread, pusher, tweaks, event).await?;
} }
// Else the event triggered no actions // Else the event triggered no actions
@ -369,13 +370,16 @@ impl Service {
} }
#[tracing::instrument(skip(self, unread, pusher, tweaks, event))] #[tracing::instrument(skip(self, unread, pusher, tweaks, event))]
async fn send_notice( async fn send_notice<E>(
&self, &self,
unread: UInt, unread: UInt,
pusher: &Pusher, pusher: &Pusher,
tweaks: Vec<Tweak>, tweaks: Vec<Tweak>,
event: &PduEvent, event: &E,
) -> Result { ) -> Result
where
E: Event + Send + Sync,
{
// TODO: email // TODO: email
match &pusher.kind { match &pusher.kind {
| PusherKind::Http(http) => { | PusherKind::Http(http) => {
@ -421,8 +425,8 @@ impl Service {
let d = vec![device]; let d = vec![device];
let mut notifi = Notification::new(d); let mut notifi = Notification::new(d);
notifi.event_id = Some((*event.event_id).to_owned()); notifi.event_id = Some(event.event_id().to_owned());
notifi.room_id = Some((*event.room_id).to_owned()); notifi.room_id = Some(event.room_id().to_owned());
if http if http
.data .data
.get("org.matrix.msc4076.disable_badge_count") .get("org.matrix.msc4076.disable_badge_count")
@ -442,7 +446,7 @@ impl Service {
) )
.await?; .await?;
} else { } else {
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(true) | Tweak::Sound(_)))
@ -451,29 +455,29 @@ impl Service {
} else { } else {
notifi.prio = NotificationPriority::Low; notifi.prio = NotificationPriority::Low;
} }
notifi.sender = Some(event.sender.clone()); notifi.sender = Some(event.sender().to_owned());
notifi.event_type = Some(event.kind.clone()); notifi.event_type = Some(event.kind().to_owned());
notifi.content = serde_json::value::to_raw_value(&event.content).ok(); notifi.content = serde_json::value::to_raw_value(event.content()).ok();
if event.kind == TimelineEventType::RoomMember { if *event.kind() == TimelineEventType::RoomMember {
notifi.user_is_target = notifi.user_is_target =
event.state_key.as_deref() == Some(event.sender.as_str()); event.state_key() == Some(event.sender().as_str());
} }
notifi.sender_display_name = notifi.sender_display_name =
self.services.users.displayname(&event.sender).await.ok(); self.services.users.displayname(event.sender()).await.ok();
notifi.room_name = self notifi.room_name = self
.services .services
.state_accessor .state_accessor
.get_name(&event.room_id) .get_name(event.room_id())
.await .await
.ok(); .ok();
notifi.room_alias = self notifi.room_alias = self
.services .services
.state_accessor .state_accessor
.get_canonical_alias(&event.room_id) .get_canonical_alias(event.room_id())
.await .await
.ok(); .ok();

View file

@ -3,7 +3,7 @@ mod remote;
use std::sync::Arc; use std::sync::Arc;
use conduwuit::{ use conduwuit::{
Err, Result, Server, err, Err, Event, Result, Server, err,
utils::{ReadyExt, stream::TryIgnore}, utils::{ReadyExt, stream::TryIgnore},
}; };
use database::{Deserialized, Ignore, Interfix, Map}; use database::{Deserialized, Ignore, Interfix, Map};
@ -241,7 +241,7 @@ impl Service {
.room_state_get(&room_id, &StateEventType::RoomCreate, "") .room_state_get(&room_id, &StateEventType::RoomCreate, "")
.await .await
{ {
return Ok(event.sender == user_id); return Ok(event.sender() == user_id);
} }
Err!(Database("Room has no m.room.create event")) Err!(Database("Room has no m.room.create event"))

View file

@ -4,11 +4,13 @@ use std::{
}; };
use conduwuit::{ use conduwuit::{
PduEvent, debug, debug_error, debug_warn, implement, pdu, trace, Event, PduEvent, debug, debug_error, debug_warn, implement,
utils::continue_exponential_backoff_secs, warn, matrix::event::gen_event_id_canonical_json, trace, utils::continue_exponential_backoff_secs,
warn,
}; };
use ruma::{ use ruma::{
CanonicalJsonValue, OwnedEventId, RoomId, ServerName, api::federation::event::get_event, CanonicalJsonValue, EventId, OwnedEventId, RoomId, ServerName,
api::federation::event::get_event,
}; };
use super::get_room_version_id; use super::get_room_version_id;
@ -23,13 +25,17 @@ use super::get_room_version_id;
/// c. Ask origin server over federation /// c. Ask origin server over federation
/// d. TODO: Ask other servers over federation? /// d. TODO: Ask other servers over federation?
#[implement(super::Service)] #[implement(super::Service)]
pub(super) async fn fetch_and_handle_outliers<'a>( pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
&self, &self,
origin: &'a ServerName, origin: &'a ServerName,
events: &'a [OwnedEventId], events: Events,
create_event: &'a PduEvent, create_event: &'a Pdu,
room_id: &'a RoomId, room_id: &'a RoomId,
) -> Vec<(PduEvent, Option<BTreeMap<String, CanonicalJsonValue>>)> { ) -> Vec<(PduEvent, Option<BTreeMap<String, CanonicalJsonValue>>)>
where
Pdu: Event + Send + Sync,
Events: Iterator<Item = &'a EventId> + Clone + Send,
{
let back_off = |id| match self let back_off = |id| match self
.services .services
.globals .globals
@ -46,22 +52,23 @@ pub(super) async fn fetch_and_handle_outliers<'a>(
}, },
}; };
let mut events_with_auth_events = Vec::with_capacity(events.len()); let mut events_with_auth_events = Vec::with_capacity(events.clone().count());
for id in events { for id in events {
// a. Look in the main timeline (pduid_pdu tree) // a. Look in the main timeline (pduid_pdu tree)
// b. Look at outlier pdu tree // b. Look at outlier pdu tree
// (get_pdu_json checks both) // (get_pdu_json checks both)
if let Ok(local_pdu) = self.services.timeline.get_pdu(id).await { if let Ok(local_pdu) = self.services.timeline.get_pdu(id).await {
trace!("Found {id} in db"); events_with_auth_events.push((id.to_owned(), Some(local_pdu), vec![]));
events_with_auth_events.push((id, Some(local_pdu), vec![]));
continue; continue;
} }
// c. Ask origin server over federation // c. Ask origin server over federation
// We also handle its auth chain here so we don't get a stack overflow in // We also handle its auth chain here so we don't get a stack overflow in
// handle_outlier_pdu. // handle_outlier_pdu.
let mut todo_auth_events: VecDeque<_> = [id.clone()].into(); let mut todo_auth_events: VecDeque<_> = [id.to_owned()].into();
let mut events_in_reverse_order = Vec::with_capacity(todo_auth_events.len()); let mut events_in_reverse_order = Vec::with_capacity(todo_auth_events.len());
let mut events_all = HashSet::with_capacity(todo_auth_events.len()); let mut events_all = HashSet::with_capacity(todo_auth_events.len());
while let Some(next_id) = todo_auth_events.pop_front() { while let Some(next_id) = todo_auth_events.pop_front() {
if let Some((time, tries)) = self if let Some((time, tries)) = self
@ -117,7 +124,7 @@ pub(super) async fn fetch_and_handle_outliers<'a>(
}; };
let Ok((calculated_event_id, value)) = let Ok((calculated_event_id, value)) =
pdu::gen_event_id_canonical_json(&res.pdu, &room_version_id) gen_event_id_canonical_json(&res.pdu, &room_version_id)
else { else {
back_off((*next_id).to_owned()); back_off((*next_id).to_owned());
continue; continue;
@ -160,7 +167,8 @@ pub(super) async fn fetch_and_handle_outliers<'a>(
}, },
} }
} }
events_with_auth_events.push((id, None, events_in_reverse_order));
events_with_auth_events.push((id.to_owned(), None, events_in_reverse_order));
} }
let mut pdus = Vec::with_capacity(events_with_auth_events.len()); let mut pdus = Vec::with_capacity(events_with_auth_events.len());
@ -217,5 +225,6 @@ pub(super) async fn fetch_and_handle_outliers<'a>(
} }
} }
} }
pdus pdus
} }

View file

@ -1,13 +1,16 @@
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; use std::{
collections::{BTreeMap, HashMap, HashSet, VecDeque},
iter::once,
};
use conduwuit::{ use conduwuit::{
PduEvent, Result, debug_warn, err, implement, Event, PduEvent, Result, debug_warn, err, implement,
state_res::{self}, state_res::{self},
}; };
use futures::{FutureExt, future}; use futures::{FutureExt, future};
use ruma::{ use ruma::{
CanonicalJsonValue, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, ServerName, UInt, int, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, ServerName,
uint, int, uint,
}; };
use super::check_room_id; use super::check_room_id;
@ -19,20 +22,26 @@ use super::check_room_id;
fields(%origin), fields(%origin),
)] )]
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub(super) async fn fetch_prev( pub(super) async fn fetch_prev<'a, Pdu, Events>(
&self, &self,
origin: &ServerName, origin: &ServerName,
create_event: &PduEvent, create_event: &Pdu,
room_id: &RoomId, room_id: &RoomId,
first_ts_in_room: UInt, first_ts_in_room: MilliSecondsSinceUnixEpoch,
initial_set: Vec<OwnedEventId>, initial_set: Events,
) -> Result<( ) -> Result<(
Vec<OwnedEventId>, Vec<OwnedEventId>,
HashMap<OwnedEventId, (PduEvent, BTreeMap<String, CanonicalJsonValue>)>, HashMap<OwnedEventId, (PduEvent, BTreeMap<String, CanonicalJsonValue>)>,
)> { )>
let mut graph: HashMap<OwnedEventId, _> = HashMap::with_capacity(initial_set.len()); where
Pdu: Event + Send + Sync,
Events: Iterator<Item = &'a EventId> + Clone + Send,
{
let num_ids = initial_set.clone().count();
let mut eventid_info = HashMap::new(); let mut eventid_info = HashMap::new();
let mut todo_outlier_stack: VecDeque<OwnedEventId> = initial_set.into(); let mut graph: HashMap<OwnedEventId, _> = HashMap::with_capacity(num_ids);
let mut todo_outlier_stack: VecDeque<OwnedEventId> =
initial_set.map(ToOwned::to_owned).collect();
let mut amount = 0; let mut amount = 0;
@ -40,7 +49,12 @@ pub(super) async fn fetch_prev(
self.services.server.check_running()?; self.services.server.check_running()?;
match self match self
.fetch_and_handle_outliers(origin, &[prev_event_id.clone()], create_event, room_id) .fetch_and_handle_outliers(
origin,
once(prev_event_id.as_ref()),
create_event,
room_id,
)
.boxed() .boxed()
.await .await
.pop() .pop()
@ -65,17 +79,17 @@ pub(super) async fn fetch_prev(
} }
if let Some(json) = json_opt { if let Some(json) = json_opt {
if pdu.origin_server_ts > first_ts_in_room { if pdu.origin_server_ts() > first_ts_in_room {
amount = amount.saturating_add(1); amount = amount.saturating_add(1);
for prev_prev in &pdu.prev_events { for prev_prev in pdu.prev_events() {
if !graph.contains_key(prev_prev) { if !graph.contains_key(prev_prev) {
todo_outlier_stack.push_back(prev_prev.clone()); todo_outlier_stack.push_back(prev_prev.to_owned());
} }
} }
graph.insert( graph.insert(
prev_event_id.clone(), prev_event_id.clone(),
pdu.prev_events.iter().cloned().collect(), pdu.prev_events().map(ToOwned::to_owned).collect(),
); );
} else { } else {
// Time based check failed // Time based check failed
@ -98,8 +112,7 @@ pub(super) async fn fetch_prev(
let event_fetch = |event_id| { let event_fetch = |event_id| {
let origin_server_ts = eventid_info let origin_server_ts = eventid_info
.get(&event_id) .get(&event_id)
.cloned() .map_or_else(|| uint!(0), |info| info.0.origin_server_ts().get());
.map_or_else(|| uint!(0), |info| info.0.origin_server_ts);
// This return value is the key used for sorting events, // This return value is the key used for sorting events,
// events are then sorted by power level, time, // events are then sorted by power level, time,

View file

@ -1,6 +1,6 @@
use std::collections::{HashMap, hash_map}; use std::collections::{HashMap, hash_map};
use conduwuit::{Err, Error, PduEvent, Result, debug, debug_warn, implement}; use conduwuit::{Err, Event, Result, debug, debug_warn, err, implement};
use futures::FutureExt; use futures::FutureExt;
use ruma::{ use ruma::{
EventId, OwnedEventId, RoomId, ServerName, api::federation::event::get_room_state_ids, EventId, OwnedEventId, RoomId, ServerName, api::federation::event::get_room_state_ids,
@ -18,13 +18,16 @@ use crate::rooms::short::ShortStateKey;
skip_all, skip_all,
fields(%origin), fields(%origin),
)] )]
pub(super) async fn fetch_state( pub(super) async fn fetch_state<Pdu>(
&self, &self,
origin: &ServerName, origin: &ServerName,
create_event: &PduEvent, create_event: &Pdu,
room_id: &RoomId, room_id: &RoomId,
event_id: &EventId, event_id: &EventId,
) -> Result<Option<HashMap<u64, OwnedEventId>>> { ) -> Result<Option<HashMap<u64, OwnedEventId>>>
where
Pdu: Event + Send + Sync,
{
let res = self let res = self
.services .services
.sending .sending
@ -36,27 +39,27 @@ pub(super) async fn fetch_state(
.inspect_err(|e| debug_warn!("Fetching state for event failed: {e}"))?; .inspect_err(|e| debug_warn!("Fetching state for event failed: {e}"))?;
debug!("Fetching state events"); debug!("Fetching state events");
let state_ids = res.pdu_ids.iter().map(AsRef::as_ref);
let state_vec = self let state_vec = self
.fetch_and_handle_outliers(origin, &res.pdu_ids, create_event, room_id) .fetch_and_handle_outliers(origin, state_ids, create_event, room_id)
.boxed() .boxed()
.await; .await;
let mut state: HashMap<ShortStateKey, OwnedEventId> = HashMap::with_capacity(state_vec.len()); let mut state: HashMap<ShortStateKey, OwnedEventId> = HashMap::with_capacity(state_vec.len());
for (pdu, _) in state_vec { for (pdu, _) in state_vec {
let state_key = pdu let state_key = pdu
.state_key .state_key()
.clone() .ok_or_else(|| err!(Database("Found non-state pdu in state events.")))?;
.ok_or_else(|| Error::bad_database("Found non-state pdu in state events."))?;
let shortstatekey = self let shortstatekey = self
.services .services
.short .short
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), &state_key) .get_or_create_shortstatekey(&pdu.kind().to_string().into(), state_key)
.await; .await;
match state.entry(shortstatekey) { match state.entry(shortstatekey) {
| hash_map::Entry::Vacant(v) => { | hash_map::Entry::Vacant(v) => {
v.insert(pdu.event_id.clone()); v.insert(pdu.event_id().to_owned());
}, },
| hash_map::Entry::Occupied(_) => { | hash_map::Entry::Occupied(_) => {
return Err!(Database( return Err!(Database(
@ -73,7 +76,7 @@ pub(super) async fn fetch_state(
.get_shortstatekey(&StateEventType::RoomCreate, "") .get_shortstatekey(&StateEventType::RoomCreate, "")
.await?; .await?;
if state.get(&create_shortstatekey) != Some(&create_event.event_id) { if state.get(&create_shortstatekey).map(AsRef::as_ref) != Some(create_event.event_id()) {
return Err!(Database("Incoming event refers to wrong create event.")); return Err!(Database("Incoming event refers to wrong create event."));
} }

View file

@ -4,7 +4,7 @@ use std::{
}; };
use conduwuit::{ use conduwuit::{
Err, Result, debug, debug::INFO_SPAN_LEVEL, defer, err, implement, utils::stream::IterStream, Err, Event, Result, debug::INFO_SPAN_LEVEL, defer, err, implement, utils::stream::IterStream,
warn, warn,
}; };
use futures::{ use futures::{
@ -12,6 +12,7 @@ use futures::{
future::{OptionFuture, try_join5}, future::{OptionFuture, try_join5},
}; };
use ruma::{CanonicalJsonValue, EventId, RoomId, ServerName, UserId, events::StateEventType}; use ruma::{CanonicalJsonValue, EventId, RoomId, ServerName, UserId, events::StateEventType};
use tracing::debug;
use crate::rooms::timeline::RawPduId; use crate::rooms::timeline::RawPduId;
@ -121,22 +122,16 @@ pub async fn handle_incoming_pdu<'a>(
.timeline .timeline
.first_pdu_in_room(room_id) .first_pdu_in_room(room_id)
.await? .await?
.origin_server_ts; .origin_server_ts();
if incoming_pdu.origin_server_ts < first_ts_in_room { if incoming_pdu.origin_server_ts() < first_ts_in_room {
return Ok(None); return Ok(None);
} }
// 9. Fetch any missing prev events doing all checks listed here starting at 1. // 9. Fetch any missing prev events doing all checks listed here starting at 1.
// These are timeline events // These are timeline events
let (sorted_prev_events, mut eventid_info) = self let (sorted_prev_events, mut eventid_info) = self
.fetch_prev( .fetch_prev(origin, create_event, room_id, first_ts_in_room, incoming_pdu.prev_events())
origin,
create_event,
room_id,
first_ts_in_room,
incoming_pdu.prev_events.clone(),
)
.await?; .await?;
debug!( debug!(

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