refactor: Ruma upstraming, bake a little more

This commit is contained in:
Jade Ellis
2026-04-07 14:40:10 +01:00
committed by Ginger
parent 204bc1367e
commit a4e64383b7
115 changed files with 1907 additions and 1504 deletions
+1
View File
@@ -107,6 +107,7 @@ nonzero_ext.workspace = true
rand.workspace = true
regex.workspace = true
reqwest.workspace = true
assign.workspace = true
ruma.workspace = true
ruminuwuity.workspace = true
rustyline-async.workspace = true
+9 -5
View File
@@ -7,10 +7,12 @@ use conduwuit::{
use database::{Deserialized, Handle, Ignore, Json, Map};
use futures::{Stream, StreamExt, TryFutureExt};
use ruma::{
OwnedRoomId, OwnedUserId, RoomId, UserId, events::{
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent,
GlobalAccountDataEventType, RoomAccountDataEventType,
}, serde::Raw
OwnedRoomId, OwnedUserId, RoomId, UserId,
events::{
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, GlobalAccountDataEventType,
RoomAccountDataEventType,
},
serde::Raw,
};
use serde::Deserialize;
@@ -147,7 +149,9 @@ pub fn changes_since<'a>(
.stream_from(&first_possible)
.ignore_err()
.ready_take_while(move |((room_id_, user_id_, count, _), _): &(Key, _)| {
room_id == room_id_.as_deref() && user_id == user_id_ && to.is_none_or(|to| *count <= to)
room_id == room_id_.as_deref()
&& user_id == user_id_
&& to.is_none_or(|to| *count <= to)
})
.map(move |(_, v)| {
match room_id {
+2 -1
View File
@@ -85,7 +85,8 @@ pub async fn create_admin_room(services: &Services) -> Result {
// 3. Power levels
let users = BTreeMap::from_iter([(server_user.into(), 69420.into())]);
let mut power_levels_content = RoomPowerLevelsEventContent::new(&room_version.rules().unwrap().authorization);
let mut power_levels_content =
RoomPowerLevelsEventContent::new(&room_version.rules().unwrap().authorization);
power_levels_content.users = users;
services
+14 -7
View File
@@ -30,7 +30,11 @@ use ruma::{
};
use tokio::sync::RwLock;
use crate::{Dep, account_data, globals, media::{MXC_LENGTH, mxc::Mxc}, rooms::{self, state::RoomMutexGuard}};
use crate::{
Dep, account_data, globals,
media::{MXC_LENGTH, mxc::Mxc},
rooms::{self, state::RoomMutexGuard},
};
pub struct Service {
services: Services,
@@ -61,7 +65,7 @@ pub struct CommandInput {
pub command: String,
pub reply_id: Option<OwnedEventId>,
pub source: InvocationSource,
pub sender: Option<Box<UserId>>,
pub sender: Option<OwnedUserId>,
}
/// Where a command is being invoked from.
@@ -205,7 +209,10 @@ impl Service {
metadata.mimetype = Some("text/markdown".to_owned());
metadata.size = Some(UInt::new_saturating(size_u64));
let mut content = FileMessageEventContent::plain("Output was too large to send as text.".to_owned(), file);
let mut content = FileMessageEventContent::plain(
"Output was too large to send as text.".to_owned(),
file,
);
content.filename = Some("output.md".to_owned());
content.info = Some(Box::new(metadata));
@@ -313,7 +320,7 @@ impl Service {
command: String,
reply_id: Option<OwnedEventId>,
source: InvocationSource,
sender: Box<UserId>,
sender: OwnedUserId,
) -> Result<()> {
self.channel
.0
@@ -448,13 +455,13 @@ impl Service {
}
async fn handle_response(&self, content: RoomMessageEventContent) -> Result<()> {
let Some(Relation::Reply { in_reply_to }) = content.relates_to.as_ref() else {
let Some(Relation::Reply(reply )) = content.relates_to.as_ref() else {
return Ok(());
};
let Ok(pdu) = self.services.timeline.get_pdu(&in_reply_to.event_id).await else {
let Ok(pdu) = self.services.timeline.get_pdu(&reply.in_reply_to.event_id).await else {
error!(
event_id = ?in_reply_to.event_id,
event_id = ?reply.in_reply_to.event_id,
"Missing admin command in_reply_to event"
);
return Ok(());
+9 -2
View File
@@ -2,7 +2,10 @@ use std::{fmt::Debug, sync::Arc};
use async_trait::async_trait;
use conduwuit::{Result, config::Antispam, debug};
use ruma::{OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, path_builder::VersionHistory}};
use ruma::{
OwnedRoomId, OwnedUserId,
api::{auth_scheme::AppserviceToken, path_builder::VersionHistory},
};
use ruminuwuity::{draupnir_antispam, meowlnir_antispam};
use crate::{client, config, sending, service::Dep};
@@ -38,7 +41,11 @@ impl Service {
request: T,
) -> Result<T::IncomingResponse>
where
T: ruma::api::OutgoingRequest<Authentication = AppserviceToken, PathBuilder = VersionHistory> + Debug + Send,
T: ruma::api::OutgoingRequest<
Authentication = AppserviceToken,
PathBuilder = VersionHistory,
> + Debug
+ Send,
{
sending::antispam::send_antispam_request(
&self.services.client.appservice,
+3 -1
View File
@@ -72,7 +72,9 @@ impl Service {
None,
server_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(&GlobalAccountDataEvent::new(PushRulesEventContent::new(ruleset)))
&serde_json::to_value(&GlobalAccountDataEvent::new(PushRulesEventContent::new(
ruleset,
)))
.expect("to json value always works"),
)
.await?;
+74 -43
View File
@@ -2,13 +2,23 @@ use std::{any::Any, borrow::Cow, fmt::Debug, mem, sync::LazyLock};
use bytes::Bytes;
use conduwuit::{
Err, Error, Result, debug, debug_error, debug_warn, err, implement, trace, utils::response::LimitReadExt, matrix::versions::{unstable_features, versions}, };
Err, Error, Result, debug, debug_error, debug_warn, err, implement,
matrix::versions::{unstable_features, versions},
trace,
utils::response::LimitReadExt,
};
use ipaddress::IPAddress;
use reqwest::{Client, Method, Request, Response, Url};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, ServerName, ServerSigningKeyId, api::{
EndpointError, IncomingResponse, Metadata, OutgoingRequest, SupportedVersions, auth_scheme::{AuthScheme, NoAuthentication, SendAccessToken}, client::error::Error as RumaError, federation::authentication::{ServerSignatures, ServerSignaturesInput, XMatrix}, path_builder::{PathBuilder, SinglePath, VersionHistory}
}, serde::Base64
CanonicalJsonObject, CanonicalJsonValue, ServerName, ServerSigningKeyId,
api::{
EndpointError, IncomingResponse, Metadata, OutgoingRequest, SupportedVersions,
auth_scheme::{AuthScheme, NoAuthentication, SendAccessToken},
error::Error as RumaError,
federation::authentication::{ServerSignatures, ServerSignaturesInput, XMatrix},
path_builder::{PathBuilder, SinglePath, VersionHistory},
},
serde::Base64,
};
use crate::{SUPPORTED_VERSIONS, resolver::actual::ActualDest};
@@ -18,7 +28,11 @@ use crate::{SUPPORTED_VERSIONS, resolver::actual::ActualDest};
#[tracing::instrument(skip_all, name = "request", level = "debug")]
pub async fn execute<'i, T>(&self, dest: &ServerName, request: T) -> Result<T::IncomingResponse>
where
T: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send,
T: OutgoingRequest<
Authentication = ServerSignatures,
PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>,
> + Debug
+ Send,
{
let client = &self.services.client.federation;
self.execute_signed(client, dest, request).await
@@ -33,27 +47,46 @@ pub async fn execute_synapse<'i, T>(
request: T,
) -> Result<T::IncomingResponse>
where
T: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send,
T: OutgoingRequest<
Authentication = ServerSignatures,
PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>,
> + Debug
+ Send,
{
let client = &self.services.client.synapse;
self.execute_signed(client, dest, request).await
}
#[implement(super::Service)]
pub async fn execute_unauthenticated<'i, T>(&self, dest: &ServerName, request: T) -> Result<T::IncomingResponse>
where
T: OutgoingRequest::<Authentication = NoAuthentication, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send,
pub async fn execute_unauthenticated<'i, T>(
&self,
dest: &ServerName,
request: T,
) -> Result<T::IncomingResponse>
where
T: OutgoingRequest<
Authentication = NoAuthentication,
PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>,
> + Debug
+ Send,
{
let client = &self.services.client.federation;
let authentication = SendAccessToken::None;
self.execute_on(client, dest, request, authentication).await
self.execute_on(client, dest, request, ()).await
}
#[implement(super::Service)]
pub async fn execute_signed<'i, T>(&self, client: &Client, dest: &ServerName, request: T) -> Result<T::IncomingResponse>
pub async fn execute_signed<'i, T>(
&self,
client: &Client,
dest: &ServerName,
request: T,
) -> Result<T::IncomingResponse>
where
T: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Send,
T: OutgoingRequest<
Authentication = ServerSignatures,
PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>,
> + Send,
{
let authentication = ServerSignaturesInput::new(
self.services.server.name.clone(),
@@ -65,11 +98,7 @@ where
}
#[implement(super::Service)]
#[tracing::instrument(
name = "fed",
level = "info",
skip(self, client, request, authentication),
)]
#[tracing::instrument(name = "fed", level = "info", skip(self, client, request, authentication))]
pub async fn execute_on<'i, T, PathBuilderInput>(
&self,
client: &Client,
@@ -78,8 +107,8 @@ pub async fn execute_on<'i, T, PathBuilderInput>(
authentication: <T::Authentication as AuthScheme>::Input<'_>,
) -> Result<T::IncomingResponse>
where
T: OutgoingRequest::<PathBuilder: PathBuilder<Input<'i> = PathBuilderInput>> + Send,
PathBuilderInput: FederationPathBuilderInput
T: OutgoingRequest<PathBuilder: PathBuilder<Input<'i> = PathBuilderInput>> + Send,
PathBuilderInput: FederationPathBuilderInput,
{
if !self.services.server.config.allow_federation {
return Err!(Config("allow_federation", "Federation is disabled."));
@@ -90,14 +119,12 @@ where
}
let actual = self.services.resolver.get_actual_dest(dest).await?;
let request = Request::try_from(
request.try_into_http_request::<Vec<u8>>(
actual.string().as_str(),
authentication,
PathBuilderInput::create(),
)?
)?;
let request = Request::try_from(request.try_into_http_request::<Vec<u8>>(
actual.string().as_str(),
authentication,
PathBuilderInput::create(),
)?)?;
self.validate_url(request.url())?;
self.services.server.check_running()?;
@@ -248,17 +275,23 @@ fn handle_error(
Err(e.into())
}
/// A trait for the input types of acceptable path builders for outgoing federation requests.
///
/// Ruma uses Rust's type system to encode the versioning scheme of endpoints in the Matrix spec.
/// Every endpoint has a `PathBuilder` associated type, which has an `Input` associated type.
/// Endpoints with multiple versions have `VersionHistory` as their `PathBuilder`, which has `SupportedVersions`
/// as its `Input` type. Endpoints with no version have `SinglePath` as their `PathBuilder`, which has `()` as its `Input` type.
/// Both `SupportedVersions` and `()` can be created out of thin air using static data (or no data at all). This property
/// is what the `FederationPathBuilderInput` trait represents.
///
/// This trait allows the federation sender service's functions to accept requests for either versioned or unversioned endpoints,
/// by requiring that the `Input` of the `PathBuilder` of the endpoint implements `FederationPathBuilderInput`.
/// A trait for the input types of acceptable path builders for outgoing
/// federation requests.
///
/// Ruma uses Rust's type system to encode the versioning scheme of endpoints in
/// the Matrix spec. Every endpoint has a `PathBuilder` associated type, which
/// has an `Input` associated type. Endpoints with multiple versions have
/// `VersionHistory` as their `PathBuilder`, which has `SupportedVersions`
/// as its `Input` type. Endpoints with no version have `SinglePath` as their
/// `PathBuilder`, which has `()` as its `Input` type. Both `SupportedVersions`
/// and `()` can be created out of thin air using static data (or no data at
/// all). This property is what the `FederationPathBuilderInput` trait
/// represents.
///
/// This trait allows the federation sender service's functions to accept
/// requests for either versioned or unversioned endpoints, by requiring that
/// the `Input` of the `PathBuilder` of the endpoint implements
/// `FederationPathBuilderInput`.
pub(crate) trait FederationPathBuilderInput {
fn create() -> Self;
}
@@ -268,7 +301,5 @@ impl FederationPathBuilderInput for () {
}
impl FederationPathBuilderInput for Cow<'_, SupportedVersions> {
fn create() -> Self {
Cow::Borrowed(&SUPPORTED_VERSIONS)
}
fn create() -> Self { Cow::Borrowed(&SUPPORTED_VERSIONS) }
}
+1 -2
View File
@@ -1,9 +1,8 @@
mod execute;
pub(crate) use execute::FederationPathBuilderInput;
use std::sync::Arc;
use conduwuit::{Result, Server};
pub(crate) use execute::FederationPathBuilderInput;
use crate::{Dep, client, moderation, resolver, server_keys};
+3 -1
View File
@@ -7,7 +7,9 @@ use conduwuit::{
use database::{Deserialized, Ignore, Interfix, Json, Map};
use futures::StreamExt;
use ruma::{
OwnedRoomId, OwnedUserId, RoomId, UserId, api::client::backup::{BackupAlgorithm, KeyBackupData, RoomKeyBackup}, serde::Raw
OwnedRoomId, OwnedUserId, RoomId, UserId,
api::client::backup::{BackupAlgorithm, KeyBackupData, RoomKeyBackup},
serde::Raw,
};
use crate::{Dep, globals};
+1 -2
View File
@@ -8,9 +8,8 @@ use database::{Database, Interfix, Map};
use futures::StreamExt;
use ruma::{OwnedMxcUri, OwnedUserId, UserId, http_headers::ContentDisposition};
use crate::media::mxc::Mxc;
use super::{preview::UrlPreviewData, thumbnail::Dim};
use crate::media::mxc::Mxc;
pub(crate) struct Data {
mediaid_file: Arc<Map>,
+1 -1
View File
@@ -1,7 +1,7 @@
pub mod blurhash;
pub mod mxc;
mod data;
pub(super) mod migrations;
pub mod mxc;
mod preview;
mod remote;
mod tests;
+26 -26
View File
@@ -6,49 +6,49 @@ use serde::{Serialize, Serializer};
/// A structured, valid MXC URI
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Mxc<'a> {
/// ServerName part of the MXC URI
pub server_name: &'a ServerName,
/// ServerName part of the MXC URI
pub server_name: &'a ServerName,
/// MediaId part of the MXC URI
pub media_id: &'a str,
/// MediaId part of the MXC URI
pub media_id: &'a str,
}
impl fmt::Display for Mxc<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "mxc://{}/{}", self.server_name, self.media_id)
}
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "mxc://{}/{}", self.server_name, self.media_id)
}
}
impl<'a> TryFrom<&'a MxcUri> for Mxc<'a> {
type Error = MxcUriError;
type Error = MxcUriError;
fn try_from(s: &'a MxcUri) -> Result<Self, Self::Error> {
let (server_name, media_id) = s.parts()?;
fn try_from(s: &'a MxcUri) -> Result<Self, Self::Error> {
let (server_name, media_id) = s.parts()?;
Ok(Self { server_name, media_id })
}
Ok(Self { server_name, media_id })
}
}
impl<'a> TryFrom<&'a str> for Mxc<'a> {
type Error = MxcUriError;
type Error = MxcUriError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
let s: &MxcUri = s.into();
s.try_into()
}
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
let s: &MxcUri = s.into();
s.try_into()
}
}
impl<'a> TryFrom<&'a OwnedMxcUri> for Mxc<'a> {
type Error = MxcUriError;
type Error = MxcUriError;
fn try_from(s: &'a OwnedMxcUri) -> Result<Self, Self::Error> {
let s: &MxcUri = s.as_ref();
s.try_into()
}
fn try_from(s: &'a OwnedMxcUri) -> Result<Self, Self::Error> {
let s: &MxcUri = s.as_ref();
s.try_into()
}
}
impl Serialize for Mxc<'_> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.to_string().as_str())
}
}
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.to_string().as_str())
}
}
+48 -19
View File
@@ -6,17 +6,23 @@ use conduwuit::{
};
use http::header::{CONTENT_DISPOSITION, CONTENT_TYPE, HeaderValue};
use ruma::{
ServerName, UserId, api::{
Metadata, OutgoingRequest, auth_scheme::NoAuthentication, client::{
error::ErrorKind::{NotFound, Unrecognized},
media,
}, federation::{self, authenticated_media::{Content, FileOrLocation}, authentication::ServerSignatures}, path_builder::PathBuilder
}
ServerName, UserId,
api::{
Metadata, OutgoingRequest,
auth_scheme::{NoAccessToken, NoAuthentication},
client::media,
error::ErrorKind::{NotFound, Unrecognized},
federation::{
self,
authenticated_media::{Content, FileOrLocation},
authentication::ServerSignatures,
},
path_builder::PathBuilder,
},
};
use crate::{federation::FederationPathBuilderInput, media::mxc::Mxc};
use super::{Dim, FileMeta};
use crate::{federation::FederationPathBuilderInput, media::mxc::Mxc};
#[implement(super::Service)]
pub async fn fetch_remote_thumbnail(
@@ -134,7 +140,12 @@ async fn fetch_thumbnail_unauthenticated(
) -> Result<FileMeta> {
use media::get_content_thumbnail::v3::{Request, Response};
let mut request = Request::new(mxc.media_id.into(), mxc.server_name.into(), dim.width.into(), dim.height.into());
let mut request = Request::new(
mxc.media_id.into(),
mxc.server_name.into(),
dim.width.into(),
dim.height.into(),
);
request.allow_redirect = true;
request.allow_remote = true;
request.animated = Some(true);
@@ -143,7 +154,9 @@ async fn fetch_thumbnail_unauthenticated(
let Response {
file, content_type, content_disposition, ..
} = self.federation_request_unauthenticated(mxc, server, request).await?;
} = self
.federation_request_legacy_media(mxc, server, request)
.await?;
let content = Content::new(file, content_type.unwrap(), content_disposition.unwrap());
@@ -168,7 +181,9 @@ async fn fetch_content_unauthenticated(
let Response {
file, content_type, content_disposition, ..
} = self.federation_request_unauthenticated(mxc, server, request).await?;
} = self
.federation_request_legacy_media(mxc, server, request)
.await?;
let content = Content::new(file, content_type.unwrap(), content_disposition.unwrap());
@@ -300,7 +315,11 @@ async fn federation_request<'i, Request>(
request: Request,
) -> Result<Request::IncomingResponse>
where
Request: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send,
Request: OutgoingRequest<
Authentication = ServerSignatures,
PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>,
> + Debug
+ Send,
{
self.services
.sending
@@ -309,18 +328,22 @@ where
}
#[implement(super::Service)]
async fn federation_request_unauthenticated<'i, Request>(
async fn federation_request_legacy_media<'i, Request>(
&self,
mxc: &Mxc<'_>,
server: Option<&ServerName>,
request: Request,
) -> Result<Request::IncomingResponse>
where
Request: OutgoingRequest::<Authentication = NoAuthentication, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send,
Request: OutgoingRequest<
Authentication = NoAccessToken,
PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>,
> + Debug
+ Send,
{
self.services
.sending
.send_unauthenticated_request(server.unwrap_or(mxc.server_name), request)
.send_legacy_media_request(server.unwrap_or(mxc.server_name), request)
.await
}
@@ -335,7 +358,12 @@ pub async fn fetch_remote_thumbnail_legacy(
media_id: &body.media_id,
};
let mut request = media::get_content_thumbnail::v3::Request::new(body.media_id.clone(), body.server_name.clone(), body.width, body.height);
let mut request = media::get_content_thumbnail::v3::Request::new(
body.media_id.clone(),
body.server_name.clone(),
body.width,
body.height,
);
request.method = body.method.clone();
request.allow_remote = body.allow_remote;
request.allow_redirect = body.allow_redirect;
@@ -347,7 +375,7 @@ pub async fn fetch_remote_thumbnail_legacy(
let response = self
.services
.sending
.send_unauthenticated_request(mxc.server_name, request)
.send_legacy_media_request(mxc.server_name, request)
.await?;
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
@@ -372,7 +400,8 @@ pub async fn fetch_remote_content_legacy(
allow_redirect: bool,
timeout_ms: Duration,
) -> Result<media::get_content::v3::Response, Error> {
let mut request = media::get_content::v3::Request::new(mxc.media_id.into(), mxc.server_name.into());
let mut request =
media::get_content::v3::Request::new(mxc.media_id.into(), mxc.server_name.into());
request.allow_remote = true;
request.allow_redirect = allow_redirect;
request.timeout_ms = timeout_ms;
@@ -382,7 +411,7 @@ pub async fn fetch_remote_content_legacy(
let response = self
.services
.sending
.send_unauthenticated_request(mxc.server_name, request)
.send_legacy_media_request(mxc.server_name, request)
.await?;
let content_disposition = make_content_disposition(
+1 -2
View File
@@ -14,9 +14,8 @@ use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
};
use crate::media::mxc::Mxc;
use super::{FileMeta, data::Metadata};
use crate::media::mxc::Mxc;
/// Dimension specification for a thumbnail.
#[derive(Debug)]
+2 -13
View File
@@ -270,13 +270,7 @@ async fn migrate(services: &Services) -> Result<()> {
{
let patterns = services.globals.forbidden_alias_names();
if !patterns.is_empty() {
for room_id in services
.rooms
.metadata
.iter_ids()
.collect::<Vec<_>>()
.await
{
for room_id in services.rooms.metadata.iter_ids().collect::<Vec<_>>().await {
services
.rooms
.alias
@@ -479,12 +473,7 @@ async fn retroactively_fix_bad_data_from_roomuserid_joined(services: &Services)
let db = &services.db;
let _cork = db.cork_and_sync();
let room_ids = services
.rooms
.metadata
.iter_ids()
.collect::<Vec<_>>()
.await;
let room_ids = services.rooms.metadata.iter_ids().collect::<Vec<_>>().await;
for room_id in &room_ids {
debug_info!("Fixing room {room_id}");
+3 -3
View File
@@ -49,9 +49,9 @@ conduwuit::mod_ctor! {}
conduwuit::mod_dtor! {}
use std::sync::LazyLock;
use conduwuit::matrix::versions::{unstable_features, versions};
use ruma::api::SupportedVersions;
pub static SUPPORTED_VERSIONS: LazyLock<SupportedVersions> = LazyLock::new(|| {
SupportedVersions::from_parts(&versions(), &unstable_features())
});
pub static SUPPORTED_VERSIONS: LazyLock<SupportedVersions> =
LazyLock::new(|| SupportedVersions::from_parts(&versions(), &unstable_features()));
+1 -4
View File
@@ -193,10 +193,7 @@ impl Service {
| _ => continue,
};
if matches!(
presence.presence,
PresenceState::Offline
) {
if matches!(presence.presence, PresenceState::Offline) {
trace!(%user_id, ?presence, "Skipping user");
continue;
}
+1 -4
View File
@@ -54,9 +54,6 @@ impl Presence {
content.displayname = users.displayname(user_id).await.ok();
content.avatar_url = users.avatar_url(user_id).await.ok();
PresenceEvent {
sender: user_id.to_owned(),
content,
}
PresenceEvent { sender: user_id.to_owned(), content }
}
}
+40 -13
View File
@@ -11,17 +11,30 @@ use conduwuit_database::{Deserialized, Ignore, Interfix, Json, Map};
use futures::{Stream, StreamExt};
use ipaddress::IPAddress;
use ruma::{
DeviceId, OwnedDeviceId, RoomId, UInt, UserId, api::{
IncomingResponse, MatrixVersion, OutgoingRequest, auth_scheme::{NoAuthentication, SendAccessToken}, client::push::{Pusher, PusherKind, set_pusher}, path_builder::SinglePath, push_gateway::send_event_notification::{
DeviceId, OwnedDeviceId, RoomId, UInt, UserId,
api::{
IncomingResponse, MatrixVersion, OutgoingRequest,
auth_scheme::{NoAccessToken, NoAuthentication, SendAccessToken},
client::push::{Pusher, PusherKind, set_pusher},
path_builder::SinglePath,
push_gateway::send_event_notification::{
self,
v1::{Device, Notification, NotificationCounts, NotificationPriority},
}
}, events::{
},
},
events::{
AnySyncTimelineEvent, StateEventType, TimelineEventType,
room::{create::RoomCreateEventContent, power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent}},
}, push::{
Action, PushConditionPowerLevelsCtx, PushConditionRoomCtx, PushFormat, Ruleset, Tweak,
}, room_version_rules::{AuthorizationRules, RoomPowerLevelsRules, RoomVersionRules}, serde::Raw, uint
room::{
create::RoomCreateEventContent,
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
},
push::{
Action, HighlightTweakValue, PushConditionPowerLevelsCtx, PushConditionRoomCtx, PushFormat, Ruleset, Tweak
},
room_version_rules::{AuthorizationRules, RoomPowerLevelsRules, RoomVersionRules},
serde::Raw,
uint,
};
use crate::{Dep, client, config, globals, rooms, sending, users};
@@ -189,7 +202,9 @@ impl Service {
#[tracing::instrument(skip(self, dest, request))]
pub async fn send_request<T>(&self, dest: &str, request: T) -> Result<T::IncomingResponse>
where
T: OutgoingRequest<Authentication = NoAuthentication, PathBuilder = SinglePath> + Debug + Send,
T: OutgoingRequest<Authentication = NoAuthentication, PathBuilder = SinglePath>
+ Debug
+ Send,
{
const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_0];
@@ -197,7 +212,7 @@ impl Service {
trace!("Push gateway destination: {dest}");
let http_request = request
.try_into_http_request::<BytesMut>(&dest, SendAccessToken::None, ())
.try_into_http_request::<BytesMut>(&dest, (), ())
.map_err(|e| {
err!(BadServerResponse(warn!(
"Failed to find destination {dest} for push gateway: {e}"
@@ -307,7 +322,13 @@ impl Service {
let serialized = event.to_format();
for action in self
.get_actions(user, &ruleset, power_levels.clone(), &serialized, event.room_id().unwrap())
.get_actions(
user,
&ruleset,
power_levels.clone(),
&serialized,
event.room_id().unwrap(),
)
.await
{
let n = match action {
@@ -363,7 +384,13 @@ impl Service {
.await
.unwrap_or_else(|_| user.localpart().to_owned());
let ctx = PushConditionRoomCtx::new(room_id.to_owned(), room_joined_count, user.to_owned(), user_display_name).with_power_levels(power_levels);
let ctx = PushConditionRoomCtx::new(
room_id.to_owned(),
room_joined_count,
user.to_owned(),
user_display_name,
)
.with_power_levels(power_levels);
ruleset.get_actions(pdu, &ctx).await
}
@@ -442,7 +469,7 @@ impl Service {
if *event.kind() == TimelineEventType::RoomEncrypted
|| tweaks
.iter()
.any(|t| matches!(t, Tweak::Highlight(true) | Tweak::Sound(_)))
.any(|t| matches!(t, Tweak::Highlight(HighlightTweakValue::Yes) | Tweak::Sound(_)))
{
notify.prio = NotificationPriority::High;
} else {
+4 -2
View File
@@ -9,10 +9,12 @@ use conduwuit::{
use database::{Deserialized, Ignore, Interfix, Map};
use futures::{Stream, StreamExt, TryFutureExt};
use ruma::{
OwnedRoomAliasId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId, UserId, events::{
OwnedRoomAliasId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomAliasId, RoomId,
RoomOrAliasId, UserId,
events::{
StateEventType,
room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
}
},
};
use crate::{Dep, admin, appservice, appservice::RegistrationInfo, globals, rooms, sending};
@@ -109,7 +109,10 @@ where
match self
.services
.sending
.send_federation_request(origin, get_event::v1::Request::new((*next_id).to_owned()))
.send_federation_request(
origin,
get_event::v1::Request::new((*next_id).to_owned()),
)
.await
{
| Ok(res) => {
@@ -31,7 +31,10 @@ where
let res = self
.services
.sending
.send_federation_request(origin, get_room_state_ids::v1::Request::new(event_id.to_owned(), room_id.to_owned()))
.send_federation_request(
origin,
get_room_state_ids::v1::Request::new(event_id.to_owned(), room_id.to_owned()),
)
.await
.inspect_err(|e| debug_warn!("Fetching state for event failed: {e}"))?;
@@ -182,7 +182,8 @@ pub async fn handle_incoming_pdu<'a>(
// copied from https://github.com/element-hq/synapse/blob/7e4588a/synapse/handlers/federation_event.py#L255-L300
if value.get("type").and_then(|t| t.as_str()) == Some("m.room.member") {
if let Some(pdu) =
should_rescind_invite(&self.services, &mut value.clone(), &sender, room_id).await?
should_rescind_invite(&self.services, &mut value.clone(), &sender, room_id)
.await?
{
debug_info!(
"Invite to {room_id} appears to have been rescinded by {sender}, marking as \
@@ -42,7 +42,9 @@ where
// 2. Check signatures, otherwise drop
// 3. check content hash, redact if doesn't match
let room_version = get_room_version(create_event)?;
let room_rules = room_version.rules().expect("room version should have defined rules");
let room_rules = room_version
.rules()
.expect("room version should have defined rules");
let mut incoming_pdu = match self
.services
.server_keys
+1 -1
View File
@@ -119,4 +119,4 @@ fn get_room_version<Pdu: Event>(create_event: &Pdu) -> Result<RoomVersionId> {
let room_version = content.room_version;
Ok(room_version)
}
}
@@ -5,7 +5,8 @@ use conduwuit::{
matrix::event::{gen_event_id, gen_event_id_canonical_json},
};
use itertools::Itertools;
use ruma::{CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, RoomVersionId};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, RoomVersionId, };
use serde_json::value::RawValue as RawJsonValue;
type Parsed = (OwnedRoomId, OwnedEventId, CanonicalJsonObject);
@@ -10,16 +10,17 @@ use conduwuit::{
warn,
};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, KeyId, OwnedKeyId, RoomId, ServerName, SigningKeyId, events::StateEventType
CanonicalJsonObject, CanonicalJsonValue, KeyId, OwnedKeyId, RoomId, ServerName, SigningKeyId,
events::StateEventType,
};
use ruminuwuity::policy::{
policy_check::unstable::Request as PolicyCheckRequest,
event::RoomPolicyEventContent, policy_check::unstable::Request as PolicyCheckRequest,
policy_sign::unstable::Request as PolicySignRequest,
event::RoomPolicyEventContent
};
use serde_json::value::RawValue;
static POLICY_EVENT_TYPE_UNSTABLE: LazyLock<StateEventType> = LazyLock::new(|| StateEventType::from("org.matrix.msc4284.policy"));
static POLICY_EVENT_TYPE_UNSTABLE: LazyLock<StateEventType> =
LazyLock::new(|| StateEventType::from("org.matrix.msc4284.policy"));
/// Asks a remote policy server if the event is allowed.
///
@@ -88,7 +89,12 @@ pub async fn ask_policy_server(
return Ok(true);
},
};
if !self.services.state_cache.server_in_room(&via, room_id).await {
if !self
.services
.state_cache
.server_in_room(&via, room_id)
.await
{
debug!(
via = %via,
"Policy server is not in the room, skipping spam check"
@@ -132,15 +138,13 @@ pub async fn ask_policy_server(
via = %via,
"Checking event for spam with policy server via legacy check"
);
let mut request = PolicyCheckRequest::new(pdu.event_id().to_owned());
request.pdu = Some(outgoing);
let response = tokio::time::timeout(
Duration::from_secs(self.services.server.config.policy_server_request_timeout),
self.services
.sending
.send_federation_request(&via, request),
self.services.sending.send_federation_request(&via, request),
)
.await;
let response = match response {
@@ -112,7 +112,13 @@ where
{
let event_fetch = |event_id| self.event_fetch(event_id);
let event_exists = |event_id| self.event_exists(event_id);
state_res::resolve(room_version_rules, state_sets, auth_chain_sets, &event_fetch, &event_exists)
.map_err(|e| err!(error!("State resolution failed: {e:?}")))
.await
state_res::resolve(
room_version_rules,
state_sets,
auth_chain_sets,
&event_fetch,
&event_exists,
)
.map_err(|e| err!(error!("State resolution failed: {e:?}")))
.await
}
@@ -53,7 +53,9 @@ where
);
let timer = Instant::now();
let room_version_id = get_room_version(create_event)?;
let room_version_rules = room_version_id.rules().expect("room version should have defined rules");
let room_version_rules = room_version_id
.rules()
.expect("room version should have defined rules");
// 10. Fetch missing state and auth chain events by calling /state_ids at
// backwards extremities doing all the checks in this list starting at 1.
+3 -1
View File
@@ -7,7 +7,9 @@ use conduwuit::{
use database::{Deserialized, Json, Map};
use futures::{Stream, StreamExt};
use ruma::{
CanonicalJsonObject, OwnedRoomId, OwnedUserId, RoomId, UserId, events::{AnySyncEphemeralRoomEvent, receipt::ReceiptEvent}, serde::Raw
CanonicalJsonObject, OwnedRoomId, OwnedUserId, RoomId, UserId,
events::{AnySyncEphemeralRoomEvent, receipt::ReceiptEvent},
serde::Raw,
};
use crate::{Dep, globals};
+25 -24
View File
@@ -17,16 +17,17 @@ use conduwuit_core::{
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, pin_mut, stream::FuturesUnordered};
use lru_cache::LruCache;
use ruma::{
OwnedEventId, OwnedRoomId, OwnedServerName, RoomId, ServerName, UserId, api::{
OwnedEventId, OwnedRoomId, OwnedServerName, RoomId, ServerName, UserId,
api::{
client::space::SpaceHierarchyRoomsChunk,
federation::{
self,
space::SpaceHierarchyParentSummary,
},
}, events::{
federation::{self, space::SpaceHierarchyParentSummary},
},
events::{
StateEventType,
space::child::{HierarchySpaceChildEvent, SpaceChildEventContent},
}, room::{JoinRuleSummary, RoomSummary}, serde::Raw,
},
room::{JoinRuleSummary, RoomSummary},
serde::Raw,
};
use tokio::sync::{Mutex, MutexGuard};
@@ -299,11 +300,7 @@ async fn get_room_summary(
let join_rule = self.services.state_accessor.get_join_rules(room_id).await;
let is_accessible_child = self
.is_accessible_child(
room_id,
&join_rule.clone().into(),
identifier,
)
.is_accessible_child(room_id, &join_rule.clone().into(), identifier)
.await;
if !is_accessible_child {
@@ -375,7 +372,7 @@ async fn get_room_summary(
join_rule.clone().into(),
guest_can_join,
num_joined_members.try_into().unwrap_or_default(),
world_readable
world_readable,
);
summary.canonical_alias = canonical_alias;
summary.name = name;
@@ -426,13 +423,17 @@ async fn is_accessible_child(
| JoinRuleSummary::Public
| JoinRuleSummary::Knock
| JoinRuleSummary::KnockRestricted(_) => true,
| JoinRuleSummary::Restricted(restricted_summary) => {
(&restricted_summary.allowed_room_ids).stream().any(async |room| match identifier {
| Identifier::UserId(user) => self.services.state_cache.is_joined(user, room).await,
| Identifier::ServerName(server) => self.services.state_cache.server_in_room(server, room).await,
}).await
},
_ => false
| JoinRuleSummary::Restricted(restricted_summary) =>
(&restricted_summary.allowed_room_ids)
.stream()
.any(async |room| match identifier {
| Identifier::UserId(user) =>
self.services.state_cache.is_joined(user, room).await,
| Identifier::ServerName(server) =>
self.services.state_cache.server_in_room(server, room).await,
})
.await,
| _ => false,
}
}
@@ -463,10 +464,10 @@ async fn cache_insert(
summary: RoomSummary,
) {
let children_state = self
.get_space_child_events(&summary.room_id)
.map(Event::into_format)
.collect()
.await;
.get_space_child_events(&summary.room_id)
.map(Event::into_format)
.collect()
.await;
let summary = SpaceHierarchyParentSummary::new(summary, children_state);
cache.insert(current_room.to_owned(), Some(CachedSpaceHierarchySummary { summary }));
+1 -1
View File
@@ -4,7 +4,7 @@ use std::{
};
use conduwuit::{Error, Result};
use ruma::{UInt, api::client::error::ErrorKind};
use ruma::{UInt, api::error::ErrorKind};
use crate::rooms::short::ShortRoomId;
+12 -3
View File
@@ -1,7 +1,10 @@
use std::str::FromStr;
use ruma::{
UInt, api::federation::space::SpaceHierarchyParentSummary, owned_room_id, owned_server_name, room::{JoinRuleSummary, RoomSummary},
UInt,
api::federation::space::SpaceHierarchyParentSummary,
owned_room_id, owned_server_name,
room::{JoinRuleSummary, RoomSummary},
};
use crate::rooms::spaces::{PaginationToken, get_parent_children_via};
@@ -9,7 +12,13 @@ use crate::rooms::spaces::{PaginationToken, get_parent_children_via};
#[test]
fn get_summary_children() {
let summary = SpaceHierarchyParentSummary::new(
RoomSummary::new(owned_room_id!("!root:example.org"), JoinRuleSummary::Public, true, UInt::from(1_u32), true),
RoomSummary::new(
owned_room_id!("!root:example.org"),
JoinRuleSummary::Public,
true,
UInt::from(1_u32),
true,
),
vec![
serde_json::from_str(
r#"{
@@ -55,7 +64,7 @@ fn get_summary_children() {
}"#,
)
.unwrap(),
]
],
);
assert_eq!(
+13 -5
View File
@@ -17,10 +17,13 @@ use futures::{
FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future::join_all, pin_mut,
};
use ruma::{
EventId, OwnedEventId, OwnedRoomId, RoomId, RoomVersionId, UserId, events::{
EventId, OwnedEventId, OwnedRoomId, RoomId, RoomVersionId, UserId,
events::{
AnyStrippedStateEvent, StateEventType, TimelineEventType,
room::create::RoomCreateEventContent,
}, room_version_rules::RoomVersionRules, serde::Raw
},
room_version_rules::RoomVersionRules,
serde::Raw,
};
use crate::{
@@ -107,7 +110,7 @@ impl Service {
.then(|shorteventid| {
self.services
.short
.get_eventid_from_short::<Box<_>>(shorteventid)
.get_eventid_from_short::<OwnedEventId>(shorteventid)
})
.ignore_err();
@@ -426,8 +429,13 @@ impl Service {
return Ok(HashMap::new());
};
let auth_types =
state_res::auth_types_for_event(kind, sender, state_key, content, room_version_rules)?;
let auth_types = state_res::auth_types_for_event(
kind,
sender,
state_key,
content,
room_version_rules,
)?;
debug!(?auth_types, "Auth types for event");
let sauthevents: HashMap<_, _> = auth_types
.iter()
+55 -14
View File
@@ -9,12 +9,25 @@ use async_trait::async_trait;
use conduwuit::{Event, Result, err};
use database::Map;
use ruma::{
EventEncryptionAlgorithm, JsOption, OwnedRoomAliasId, OwnedUserId, RoomId, UserId, events::{
EventEncryptionAlgorithm, JsOption, OwnedRoomAliasId, OwnedUserId, RoomId, UserId,
events::{
StateEventType,
room::{
avatar::RoomAvatarEventContent, canonical_alias::RoomCanonicalAliasEventContent, create::{RoomCreateEvent, RoomCreateEventContent}, encryption::RoomEncryptionEventContent, guest_access::{GuestAccess, RoomGuestAccessEventContent}, history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent}, join_rules::{JoinRule, RoomJoinRulesEventContent}, member::RoomMemberEventContent, name::RoomNameEventContent, pinned_events::RoomPinnedEventsEventContent, power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent}, topic::RoomTopicEventContent
avatar::RoomAvatarEventContent,
canonical_alias::RoomCanonicalAliasEventContent,
create::{RoomCreateEvent, RoomCreateEventContent},
encryption::RoomEncryptionEventContent,
guest_access::{GuestAccess, RoomGuestAccessEventContent},
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
join_rules::{JoinRule, RoomJoinRulesEventContent},
member::RoomMemberEventContent,
name::RoomNameEventContent,
pinned_events::RoomPinnedEventsEventContent,
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
topic::RoomTopicEventContent,
},
}, room::RoomType
},
room::RoomType,
};
use crate::{Dep, rooms};
@@ -152,12 +165,25 @@ impl Service {
.is_ok()
}
/// Get a set of the room's creators. This will always contain a single user for room versions 11 and earlier.
/// Get a set of the room's creators. This will always contain a single user
/// for room versions 11 and earlier.
pub async fn get_room_creators(&self, room_id: &RoomId) -> HashSet<OwnedUserId> {
let room_version_rules = self.services.state.get_room_version(room_id).await.expect("room should have a version").rules().expect("room version should be known");
let room_version_rules = self
.services
.state
.get_room_version(room_id)
.await
.expect("room should have a version")
.rules()
.expect("room version should be known");
let create_event = self.room_state_get(room_id, &StateEventType::RoomCreate, "").await.expect("room should have a create event");
let create_content: RoomCreateEventContent = create_event.get_content().expect("create event content should be valid");
let create_event = self
.room_state_get(room_id, &StateEventType::RoomCreate, "")
.await
.expect("room should have a create event");
let create_content: RoomCreateEventContent = create_event
.get_content()
.expect("create event content should be valid");
let mut creators = HashSet::new();
if room_version_rules.authorization.use_room_create_sender {
@@ -174,15 +200,30 @@ impl Service {
creators
}
/// Get the room's power levels. This will never fail -- if the room has no power level state event,
/// the default power levels for the room's version will be returned.
/// Get the room's power levels. This will never fail -- if the room has no
/// power level state event, the default power levels for the room's
/// version will be returned.
pub async fn get_room_power_levels(&self, room_id: &RoomId) -> RoomPowerLevels {
let room_version_rules = self.services.state.get_room_version(room_id).await.expect("room should have a version").rules().expect("room version should be known");
let creators = self.get_room_creators(room_id).await;
let power_levels_event: RoomPowerLevelsEventContent = self.room_state_get_content(room_id, &StateEventType::RoomPowerLevels, "")
let room_version_rules = self
.services
.state
.get_room_version(room_id)
.await
.unwrap_or_else(|_| RoomPowerLevelsEventContent::new(&room_version_rules.authorization));
.expect("room should have a version")
.rules()
.expect("room version should be known");
let creators = self.get_room_creators(room_id).await;
let power_levels_event: RoomPowerLevelsEventContent = self
.room_state_get_content(room_id, &StateEventType::RoomPowerLevels, "")
.await
.unwrap_or_else(|_| {
RoomPowerLevelsEventContent::new(&room_version_rules.authorization)
});
RoomPowerLevels::new(power_levels_event.into(), &room_version_rules.authorization, creators)
RoomPowerLevels::new(
power_levels_event.into(),
&room_version_rules.authorization,
creators,
)
}
}
@@ -44,17 +44,13 @@ pub async fn server_can_see_event(
| HistoryVisibility::Invited => {
// Allow if any member on requesting server was AT LEAST invited, else deny
current_server_members
.any(async |member| {
self.user_was_invited(shortstatehash, &member).await
})
.any(async |member| self.user_was_invited(shortstatehash, &member).await)
.await
},
| HistoryVisibility::Joined => {
// Allow if any member on requested server was joined, else deny
current_server_members
.any(async |member| {
self.user_was_joined(shortstatehash, &member).await
})
.any(async |member| self.user_was_joined(shortstatehash, &member).await)
.await
},
| HistoryVisibility::WorldReadable | HistoryVisibility::Shared | _ => true,
+3 -1
View File
@@ -316,7 +316,9 @@ pub fn state_full(
shortstatehash: ShortStateHash,
) -> impl Stream<Item = ((StateEventType, StateKey), impl Event)> + Send + '_ {
self.state_full_pdus(shortstatehash)
.ready_filter_map(|pdu| Some(((pdu.kind().to_string().into(), pdu.state_key()?.into()), pdu)))
.ready_filter_map(|pdu| {
Some(((pdu.kind().to_string().into(), pdu.state_key()?.into()), pdu))
})
}
#[implement(super::Service)]
+16 -9
View File
@@ -44,13 +44,22 @@ pub async fn user_can_redact(
)));
}
let create_event = self.room_state_get(room_id, &StateEventType::RoomCreate, "").await?;
let create_event = self
.room_state_get(room_id, &StateEventType::RoomCreate, "")
.await?;
let create_event_content: RoomCreateEventContent = create_event.get_content().unwrap();
let room_version_rules = create_event_content.room_version.rules().expect("room version should have defined rules");
if room_version_rules.authorization.explicitly_privilege_room_creators {
let room_version_rules = create_event_content
.room_version
.rules()
.expect("room version should have defined rules");
if room_version_rules
.authorization
.explicitly_privilege_room_creators
{
let sender_owned = sender.to_owned();
// NOTE: we don't check the pre-v11 `creator` field because no room version has
// `explicitly_privilege_room_creators` and `use_room_create_sender` set at the same time
// `explicitly_privilege_room_creators` and `use_room_create_sender` set at the
// same time
if sender == create_event.sender()
|| create_event_content
.additional_creators
@@ -60,7 +69,6 @@ pub async fn user_can_redact(
}
}
let power_levels = self.get_room_power_levels(room_id).await;
if power_levels.user_can_redact_event_of_other(sender) {
@@ -69,14 +77,13 @@ pub async fn user_can_redact(
if power_levels.user_can_redact_own_event(sender) {
let is_own_event = match redacting_event {
Ok(redacting_event) => {
| Ok(redacting_event) =>
if federation {
redacting_event.sender().server_name() == sender.server_name()
} else {
redacting_event.sender() == sender
}
},
_ => false
},
| _ => false,
};
return Ok(is_own_event);
+9 -3
View File
@@ -12,7 +12,9 @@ use conduwuit::{
use database::{Deserialized, Ignore, Interfix, Map};
use futures::{Stream, StreamExt, future::join5, pin_mut};
use ruma::{
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId, events::{AnyStrippedStateEvent, room::member::MembershipState}, serde::Raw
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
events::{AnyStrippedStateEvent, room::member::MembershipState},
serde::Raw,
};
use crate::{Dep, account_data, appservice::RegistrationInfo, config, globals, rooms, users};
@@ -415,7 +417,9 @@ pub async fn invite_state(
.qry(&key)
.await
.deserialized()
.and_then(|val: Raw<Vec<AnyStrippedStateEvent>>| val.deserialize_as_unchecked().map_err(Into::into))
.and_then(|val: Raw<Vec<AnyStrippedStateEvent>>| {
val.deserialize_as_unchecked().map_err(Into::into)
})
}
#[implement(Service)]
@@ -431,7 +435,9 @@ pub async fn knock_state(
.qry(&key)
.await
.deserialized()
.and_then(|val: Raw<Vec<AnyStrippedStateEvent>>| val.deserialize_as_unchecked().map_err(Into::into))
.and_then(|val: Raw<Vec<AnyStrippedStateEvent>>| {
val.deserialize_as_unchecked().map_err(Into::into)
})
}
#[implement(Service)]
+8 -2
View File
@@ -11,7 +11,9 @@ use conduwuit_core::{
use conduwuit_database::{Deserialized, Map};
use futures::{Stream, StreamExt};
use ruma::{
CanonicalJsonValue, EventId, OwnedUserId, RoomId, UserId, api::client::threads::get_threads::v1::IncludeThreads, events::relation::BundledThread, serde::Raw, uint
CanonicalJsonValue, EventId, OwnedUserId, RoomId, UserId,
api::client::threads::get_threads::v1::IncludeThreads, events::relation::BundledThread,
serde::Raw, uint,
};
use serde_json::json;
@@ -100,7 +102,11 @@ impl Service {
);
} else {
// New thread
let relations = BundledThread::new(Raw::from_json(event.content().to_owned()), uint!(1), true);
let relations = BundledThread::new(
Raw::from_json(event.content().to_owned()),
uint!(1),
true,
);
let content = serde_json::to_value(relations).expect("to_value always works");
+11 -8
View File
@@ -18,10 +18,7 @@ use ruma::{
events::{
GlobalAccountDataEventType, TimelineEventType,
push_rules::PushRulesEvent,
room::{
encrypted::Relation,
redaction::RoomRedactionEventContent,
},
room::{encrypted::Relation, redaction::RoomRedactionEventContent},
},
push::{Action, Ruleset, Tweak},
};
@@ -204,7 +201,11 @@ where
drop(insert_lock);
// See if the event matches any known pushers via power level
let power_levels = self.services.state_accessor.get_room_power_levels(room_id).await;
let power_levels = self
.services
.state_accessor
.get_room_power_levels(room_id)
.await;
let mut push_target: HashSet<_> = self
.services
.state_cache
@@ -251,7 +252,7 @@ where
{
match action {
| Action::Notify => notify = true,
| Action::SetTweak(Tweak::Highlight(true)) => {
| Action::SetTweak(Tweak::Highlight(ruma::push::HighlightTweakValue::Yes)) => {
highlight = true;
},
| _ => {},
@@ -366,10 +367,12 @@ where
if let Ok(content) = pdu.get_content::<ExtractRelatesTo>() {
match content.relates_to {
| Relation::Reply { in_reply_to } => {
| Relation::Reply(in_reply_to) => {
// We need to do it again here, because replies don't have
// event_id as a top level field
if let Ok(related_pducount) = self.get_pdu_count(&in_reply_to.event_id).await {
if let Ok(related_pducount) =
self.get_pdu_count(&in_reply_to.in_reply_to.event_id).await
{
self.services
.pdu_metadata
.add_relation(count2, related_pducount);
+37 -9
View File
@@ -12,10 +12,16 @@ use conduwuit_core::{
};
use futures::{FutureExt, Stream, StreamExt};
use ruma::{
CanonicalJsonObject, EventId, Int, OwnedServerName, RoomId, ServerName, api::federation, events::{
CanonicalJsonObject, EventId, Int, OwnedServerName, RoomId, ServerName,
api::federation,
events::{
StateEventType, TimelineEventType,
room::{create::RoomCreateEventContent, power_levels::{RoomPowerLevelsEventContent, UserPowerLevel}},
}, uint
room::{
create::RoomCreateEventContent,
power_levels::{RoomPowerLevelsEventContent, UserPowerLevel},
},
},
uint,
};
use serde_json::value::RawValue as RawJsonValue;
@@ -66,7 +72,11 @@ pub async fn backfill_if_required(&self, room_id: &RoomId, from: PduCount) -> Re
.sending
.send_federation_request(
&backfill_server,
federation::backfill::get_backfill::v1::Request::new(room_id.to_owned(), vec![first_pdu.1.event_id().to_owned()], uint!(100))
federation::backfill::get_backfill::v1::Request::new(
room_id.to_owned(),
vec![first_pdu.1.event_id().to_owned()],
uint!(100),
),
)
.await;
match response {
@@ -125,7 +135,10 @@ pub async fn get_remote_pdu(&self, room_id: &RoomId, event_id: &EventId) -> Resu
let value = self
.services
.sending
.send_federation_request(&backfill_server, federation::event::get_event::v1::Request::new(event_id.to_owned()))
.send_federation_request(
&backfill_server,
federation::event::get_event::v1::Request::new(event_id.to_owned()),
)
.await
.and_then(|response| {
serde_json::from_str::<CanonicalJsonObject>(response.pdu.get()).map_err(|e| {
@@ -220,7 +233,11 @@ pub async fn backfill_pdu(&self, origin: &ServerName, pdu: Box<RawJsonValue>) ->
async fn candidate_backfill_servers(&self, room_id: &RoomId) -> HashSet<OwnedServerName> {
let mut candidate_backfill_servers = HashSet::new();
let power_levels = self.services.state_accessor.get_room_power_levels(room_id).await;
let power_levels = self
.services
.state_accessor
.get_room_power_levels(room_id)
.await;
// Insert servers of room creators
if let Some(creators) = &power_levels.rules.privileged_creators {
@@ -237,19 +254,30 @@ async fn candidate_backfill_servers(&self, room_id: &RoomId) -> HashSet<OwnedSer
}
// Insert the canonical room alias server
if let Ok(canonical_alias) = self.services.state_accessor.get_canonical_alias(room_id).await {
if let Ok(canonical_alias) = self
.services
.state_accessor
.get_canonical_alias(room_id)
.await
{
candidate_backfill_servers.insert(canonical_alias.server_name().to_owned());
}
// Insert all trusted servers in the config
candidate_backfill_servers.extend(self.services.server.config.trusted_servers.iter().cloned());
candidate_backfill_servers
.extend(self.services.server.config.trusted_servers.iter().cloned());
// Remove our own name, we can't request backfill from ourselves
candidate_backfill_servers.remove(self.services.globals.server_name());
// Remove all servers that aren't in the room
for server in candidate_backfill_servers.clone() {
if !self.services.state_cache.server_in_room(&server, room_id).await {
if !self
.services
.state_cache
.server_in_room(&server, room_id)
.await
{
candidate_backfill_servers.remove(&server);
}
}
+1 -1
View File
@@ -140,7 +140,7 @@ pub async fn build_and_append_pdu(
body,
Some(pdu.event_id().into()),
source,
pdu.sender.clone().into(),
pdu.sender.clone(),
)?;
}
}
+5 -5
View File
@@ -96,7 +96,7 @@ pub async fn create_event(
event_type,
room_id.as_ref().map_or("None", |id| id.as_str())
);
let room_version_id = match room_id {
let room_version = match room_id {
| Some(room_id) => {
trace!(%room_id, "Looking up existing room ID");
self.services
@@ -114,7 +114,7 @@ pub async fn create_event(
| None => {
trace!("No room ID, assuming room creation");
room_version_from_event(
RoomId::new(self.services.globals.server_name()),
RoomId::new_v1(self.services.globals.server_name()),
&event_type.clone(),
&content.clone(),
)?
@@ -261,7 +261,7 @@ pub async fn create_event(
pdu.event_id,
pdu.room_id.as_ref().map_or("None", |id| id.as_str())
);
Ok((pdu, room_version_id))
Ok((pdu, room_version))
}
#[implement(super::Service)]
@@ -276,7 +276,7 @@ pub async fn create_hash_and_sign_event(
if !self.services.globals.user_is_local(sender) {
return Err!(Request(Forbidden("Sender must be a local user")));
}
let (mut pdu, room_version_id) = self
let (mut pdu, room_version) = self
.create_event(pdu_builder, sender, room_id, mutex_lock)
.await?;
// Hash and sign
@@ -292,7 +292,7 @@ pub async fn create_hash_and_sign_event(
.hash_and_sign_event(&mut pdu_json, &room_version)
{
return match e {
| Error::Signatures(ruma::signatures::Error::PduSize) => {
| Error::SignatureJson(ruma::signatures::JsonError::PduTooLarge) => {
Err!(Request(TooLarge("Message/PDU is too long (exceeds 65535 bytes)")))
},
| _ => Err!(Request(Unknown(warn!("Signing event failed: {e}")))),
+8 -2
View File
@@ -3,7 +3,11 @@ use std::{borrow::Cow, fmt::Debug, mem};
use bytes::BytesMut;
use conduwuit::{Err, Result, debug_error, err, utils, utils::response::LimitReadExt, warn};
use reqwest::Client;
use ruma::api::{IncomingResponse, MatrixVersion, OutgoingRequest, auth_scheme::{AppserviceToken, SendAccessToken}, path_builder::VersionHistory};
use ruma::api::{
IncomingResponse, MatrixVersion, OutgoingRequest,
auth_scheme::{AppserviceToken, SendAccessToken},
path_builder::VersionHistory,
};
use crate::SUPPORTED_VERSIONS;
@@ -15,7 +19,9 @@ pub(crate) async fn send_antispam_request<T>(
request: T,
) -> Result<T::IncomingResponse>
where
T: OutgoingRequest::<Authentication = AppserviceToken, PathBuilder = VersionHistory> + Debug + Send,
T: OutgoingRequest<Authentication = AppserviceToken, PathBuilder = VersionHistory>
+ Debug
+ Send,
{
const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_15];
let http_request = request
+5 -6
View File
@@ -5,7 +5,10 @@ use conduwuit::{
Err, Result, debug_error, err, implement, trace, utils, utils::response::LimitReadExt, warn,
};
use ruma::api::{
IncomingResponse, MatrixVersion, OutgoingRequest, appservice::Registration, auth_scheme::{AccessToken, SendAccessToken}, path_builder::SinglePath,
IncomingResponse, MatrixVersion, OutgoingRequest,
appservice::Registration,
auth_scheme::{AccessToken, SendAccessToken},
path_builder::SinglePath,
};
/// Sends a request to an appservice
@@ -33,11 +36,7 @@ where
let hs_token = registration.hs_token.as_str();
let mut http_request = request
.try_into_http_request::<BytesMut>(
&dest,
SendAccessToken::Appservice(hs_token),
(),
)
.try_into_http_request::<BytesMut>(&dest, SendAccessToken::Appservice(hs_token), ())
.map_err(|e| {
err!(BadServerResponse(
warn!(appservice = %registration.id, "Failed to find destination {dest}: {e:?}")
+47 -9
View File
@@ -19,7 +19,13 @@ use conduwuit::{
warn,
};
use futures::{FutureExt, Stream, StreamExt};
use ruma::{OwnedServerName, RoomId, ServerName, UserId, api::{OutgoingRequest, auth_scheme::NoAuthentication, federation::authentication::ServerSignatures, path_builder::PathBuilder}};
use ruma::{
OwnedServerName, RoomId, ServerName, UserId,
api::{
OutgoingRequest, auth_scheme::{NoAuthentication, NoAccessToken, SendAccessToken},
federation::authentication::ServerSignatures, path_builder::PathBuilder,
},
};
use tokio::{task, task::JoinSet};
use self::data::Data;
@@ -28,7 +34,10 @@ pub use self::{
sender::{EDU_LIMIT, PDU_LIMIT},
};
use crate::{
Dep, account_data, client, federation::{self, FederationPathBuilderInput}, globals, presence, pusher, rooms::{self, timeline::RawPduId},
Dep, account_data, client,
federation::{self, FederationPathBuilderInput},
globals, presence, pusher,
rooms::{self, timeline::RawPduId},
users,
};
@@ -239,10 +248,7 @@ impl Service {
{
let requests = servers
.map(|server| {
(
Destination::Federation(server),
SendingEvent::Edu(serialized.clone()),
)
(Destination::Federation(server), SendingEvent::Edu(serialized.clone()))
})
.collect::<Vec<_>>()
.await;
@@ -294,7 +300,11 @@ impl Service {
request: T,
) -> Result<T::IncomingResponse>
where
T: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send,
T: OutgoingRequest<
Authentication = ServerSignatures,
PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>,
> + Debug
+ Send,
{
self.services.federation.execute(dest, request).await
}
@@ -307,7 +317,11 @@ impl Service {
request: T,
) -> Result<T::IncomingResponse>
where
T: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send,
T: OutgoingRequest<
Authentication = ServerSignatures,
PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>,
> + Debug
+ Send,
{
self.services
.federation
@@ -323,13 +337,37 @@ impl Service {
request: T,
) -> Result<T::IncomingResponse>
where
T: OutgoingRequest::<Authentication = NoAuthentication, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send,
T: OutgoingRequest<
Authentication = NoAuthentication,
PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>,
> + Debug
+ Send,
{
self.services
.federation
.execute_unauthenticated(dest, request)
.await
}
/// Send an unauthenticated federation request with no X-Matrix header.
#[inline]
pub async fn send_legacy_media_request<'i, T>(
&self,
dest: &ServerName,
request: T,
) -> Result<T::IncomingResponse>
where
T: OutgoingRequest<
Authentication = NoAccessToken,
PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>,
> + Debug
+ Send,
{
self.services
.federation
.execute_on(&self.services.client.federation, dest, request, SendAccessToken::None)
.await
}
/// Clean up queued sending event data
///
+23 -12
View File
@@ -438,7 +438,11 @@ impl Service {
// Empty prev id forces synapse to resync; because synapse resyncs,
// we can just insert placeholder data
let edu = Edu::DeviceListUpdate(DeviceListUpdateContent::new(user_id, device_id!("placeholder").to_owned(), uint!(1)));
let edu = Edu::DeviceListUpdate(DeviceListUpdateContent::new(
user_id,
device_id!("placeholder").to_owned(),
uint!(1),
));
let mut buf = EduBuf::new();
serde_json::to_writer(&mut buf, &edu)
@@ -605,7 +609,14 @@ impl Service {
continue;
};
let mut update = PresenceUpdate::new(user_id.to_owned(), presence_event.content.presence, presence_event.content.last_active_ago.unwrap_or_else(|| uint!(0)));
let mut update = PresenceUpdate::new(
user_id.to_owned(),
presence_event.content.presence,
presence_event
.content
.last_active_ago
.unwrap_or_else(|| uint!(0)),
);
update.currently_active = presence_event.content.currently_active.unwrap_or_default();
update.status_msg = presence_event.content.status_msg;
@@ -619,7 +630,8 @@ impl Service {
return None;
}
let presence_content = Edu::Presence(PresenceContent::new(presence_updates.into_values().collect()));
let presence_content =
Edu::Presence(PresenceContent::new(presence_updates.into_values().collect()));
let mut buf = EduBuf::new();
serde_json::to_writer(&mut buf, &presence_content)
@@ -699,17 +711,12 @@ impl Service {
//debug_assert!(pdu_jsons.len() + edu_jsons.len() > 0, "sending empty
// transaction");
let mut request = ruma::api::appservice::event::push_events::v1::Request::new(txn_id.into(), pdu_jsons);
let mut request =
ruma::api::appservice::event::push_events::v1::Request::new(txn_id.into(), pdu_jsons);
request.ephemeral = edu_jsons;
request.to_device = Vec::new(); // TODO
match self
.send_appservice_request(
appservice,
request,
)
.await
{
match self.send_appservice_request(appservice, request).await {
| Ok(_) => Ok(Destination::Appservice(id)),
| Err(e) => Err((Destination::Appservice(id), e)),
}
@@ -829,7 +836,11 @@ impl Service {
let txn_hash = calculate_hash(preimage);
let txn_id = &*URL_SAFE_NO_PAD.encode(txn_hash);
let mut request = send_transaction_message::v1::Request::new(txn_id.into(), self.server.name.clone(), MilliSecondsSinceUnixEpoch::now());
let mut request = send_transaction_message::v1::Request::new(
txn_id.into(),
self.server.name.clone(),
MilliSecondsSinceUnixEpoch::now(),
);
request.pdus = pdus;
request.edus = edus;
+1 -2
View File
@@ -6,9 +6,8 @@ use ruma::{
api::federation::discovery::VerifyKey,
};
use crate::server_keys::util::required_keys;
use super::{PubKeyMap, PubKeys, extract_key};
use crate::server_keys::util::required_keys;
#[implement(super::Service)]
pub async fn get_event_keys(
+1 -1
View File
@@ -3,8 +3,8 @@ mod get;
mod keypair;
mod request;
mod sign;
mod verify;
mod util;
mod verify;
use std::{collections::BTreeMap, sync::Arc, time::Duration};
+7 -1
View File
@@ -18,5 +18,11 @@ pub fn hash_and_sign_event(
use ruma::signatures::hash_and_sign_event;
let server_name = self.services.globals.server_name().as_str();
hash_and_sign_event(server_name, self.keypair(), object, &room_version.rules().unwrap().redaction).map_err(Into::into)
hash_and_sign_event(
server_name,
self.keypair(),
object,
&room_version.rules().unwrap().redaction,
)
.map_err(Into::into)
}
+127 -84
View File
@@ -1,117 +1,160 @@
use std::collections::{BTreeMap, BTreeSet};
use ruma::{CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedServerName, OwnedServerSigningKeyId, RoomVersionId, UserId, canonical_json::JsonType, signatures::{Error, JsonError, ParseError}};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, IdParseError, OwnedEventId, OwnedServerName, OwnedServerSigningKeyId, RoomVersionId, UserId, canonical_json::JsonType, signatures::{JsonError, VerificationError}
};
/// Whether the given event is an `m.room.member` invite that was created as the result of a
/// third-party invite.
/// Whether the given event is an `m.room.member` invite that was created as the
/// result of a third-party invite.
///
/// Returns an error if the object has not the expected format of an `m.room.member` event.
pub(super) fn is_invite_via_third_party_id(object: &CanonicalJsonObject) -> Result<bool, Error> {
let Some(CanonicalJsonValue::String(raw_type)) = object.get("type") else {
return Err(JsonError::NotOfType { target: "type".to_owned(), of_type: JsonType::String }.into());
};
/// Returns an error if the object has not the expected format of an
/// `m.room.member` event.
pub(super) fn is_invite_via_third_party_id(object: &CanonicalJsonObject) -> Result<bool, JsonError> {
let Some(CanonicalJsonValue::String(raw_type)) = object.get("type") else {
return Err(JsonError::NotOfType {
target: "type".to_owned(),
of_type: JsonType::String,
}
.into());
};
if raw_type != "m.room.member" {
return Ok(false);
}
if raw_type != "m.room.member" {
return Ok(false);
}
let Some(CanonicalJsonValue::Object(content)) = object.get("content") else {
return Err(JsonError::NotOfType { target: "content".to_owned(), of_type: JsonType::Object }.into());
};
let Some(CanonicalJsonValue::Object(content)) = object.get("content") else {
return Err(JsonError::NotOfType {
target: "content".to_owned(),
of_type: JsonType::Object,
}
.into());
};
let Some(CanonicalJsonValue::String(membership)) = content.get("membership") else {
return Err(JsonError::NotOfType { target: "membership".to_owned(), of_type: JsonType::String }.into());
};
let Some(CanonicalJsonValue::String(membership)) = content.get("membership") else {
return Err(JsonError::NotOfType {
target: "membership".to_owned(),
of_type: JsonType::String,
}
.into());
};
if membership != "invite" {
return Ok(false);
}
if membership != "invite" {
return Ok(false);
}
match content.get("third_party_invite") {
Some(CanonicalJsonValue::Object(_)) => Ok(true),
None => Ok(false),
_ => Err(JsonError::NotOfType { target: "third_party_invite".to_owned(), of_type: JsonType::Object }.into()),
}
match content.get("third_party_invite") {
| Some(CanonicalJsonValue::Object(_)) => Ok(true),
| None => Ok(false),
| _ => Err(JsonError::NotOfType {
target: "third_party_invite".to_owned(),
of_type: JsonType::Object,
}
.into()),
}
}
/// Extracts the server names to check signatures for given event.
///
/// Respects the rules for [validating signatures on received events] for populating the result:
/// Respects the rules for [validating signatures on received events] for
/// populating the result:
///
/// - Add the server of the sender, except if it's an invite event that results from a third-party
/// invite.
/// - Add the server of the sender, except if it's an invite event that results
/// from a third-party invite.
/// - For room versions 1 and 2, add the server of the `event_id`.
/// - For room versions that support restricted join rules, if it's a join event with a
/// `join_authorised_via_users_server`, add the server of that user.
/// - For room versions that support restricted join rules, if it's a join event
/// with a `join_authorised_via_users_server`, add the server of that user.
///
/// [validating signatures on received events]: https://spec.matrix.org/latest/server-server-api/#validating-hashes-and-signatures-on-received-events
pub fn servers_to_check_signatures(
object: &CanonicalJsonObject,
version: &RoomVersionId,
) -> Result<BTreeSet<OwnedServerName>, Error> {
let mut servers_to_check = BTreeSet::new();
object: &CanonicalJsonObject,
version: &RoomVersionId,
) -> Result<BTreeSet<OwnedServerName>, VerificationError> {
let mut servers_to_check = BTreeSet::new();
if !is_invite_via_third_party_id(object)? {
match object.get("sender") {
Some(CanonicalJsonValue::String(raw_sender)) => {
let user_id = <&UserId>::try_from(raw_sender.as_str())
.map_err(|e| Error::from(ParseError::UserId(e)))?;
if !is_invite_via_third_party_id(object)? {
match object.get("sender") {
| Some(CanonicalJsonValue::String(raw_sender)) => {
let user_id = <&UserId>::try_from(raw_sender.as_str()).map_err(|source| {VerificationError::ParseIdentifier {
identifier_type: "user ID",
source,
}
})?;
servers_to_check.insert(user_id.server_name().to_owned());
}
_ => return Err(JsonError::NotOfType { target: "sender".to_owned(), of_type: JsonType::String }.into()),
};
}
servers_to_check.insert(user_id.server_name().to_owned());
},
| _ =>
return Err(JsonError::NotOfType {
target: "sender".to_owned(),
of_type: JsonType::String,
}
.into()),
};
}
match version {
RoomVersionId::V1 | RoomVersionId::V2 => match object.get("event_id") {
Some(CanonicalJsonValue::String(raw_event_id)) => {
let event_id: OwnedEventId =
raw_event_id.parse().map_err(|e| Error::from(ParseError::EventId(e)))?;
match version {
| RoomVersionId::V1 | RoomVersionId::V2 => match object.get("event_id") {
| Some(CanonicalJsonValue::String(raw_event_id)) => {
let event_id: OwnedEventId = raw_event_id.parse().map_err(|source| {
VerificationError::ParseIdentifier {
identifier_type: "event ID",
source,
}
})?;
let server_name = event_id
.server_name()
.ok_or_else(|| ParseError::ServerNameFromEventId(event_id.to_owned()))?
.to_owned();
let server_name = event_id
.server_name()
.ok_or_else(|| VerificationError::ParseIdentifier {
identifier_type: "event ID",
source: IdParseError::InvalidServerName,
})?
.to_owned();
servers_to_check.insert(server_name);
}
_ => {
return Err(JsonError::JsonFieldMissingFromObject("event_id".to_owned()).into());
}
},
RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5
| RoomVersionId::V6
| RoomVersionId::V7 => {}
// TODO: And for all future versions that have join_authorised_via_users_server
RoomVersionId::V8 | RoomVersionId::V9 | RoomVersionId::V10 | RoomVersionId::V11 | RoomVersionId::V12 => {
if let Some(authorized_user) = object
.get("content")
.and_then(|c| c.as_object())
.and_then(|c| c.get("join_authorised_via_users_server"))
{
let authorized_user = authorized_user.as_str().ok_or_else(|| -> Error {
JsonError::NotOfType { target: "join_authorised_via_users_server".to_owned(), of_type: JsonType::String }.into()
})?;
let authorized_user = <&UserId>::try_from(authorized_user)
.map_err(|e| Error::from(ParseError::UserId(e)))?;
servers_to_check.insert(server_name);
},
| _ => {
return Err(JsonError::MissingField { path: "event_id".to_owned() }.into());
},
},
| RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5
| RoomVersionId::V6
| RoomVersionId::V7 => {},
// TODO: And for all future versions that have join_authorised_via_users_server
| RoomVersionId::V8
| RoomVersionId::V9
| RoomVersionId::V10
| RoomVersionId::V11
| RoomVersionId::V12 => {
if let Some(authorized_user) = object
.get("content")
.and_then(|c| c.as_object())
.and_then(|c| c.get("join_authorised_via_users_server"))
{
let authorized_user = authorized_user.as_str().ok_or_else(|| -> JsonError {
JsonError::NotOfType {
target: "join_authorised_via_users_server".to_owned(),
of_type: JsonType::String,
}
.into()
})?;
let authorized_user = <&UserId>::try_from(authorized_user)
.map_err(|source| VerificationError::ParseIdentifier { identifier_type: "user ID", source })?;
servers_to_check.insert(authorized_user.server_name().to_owned());
}
}
_ => unimplemented!(),
}
servers_to_check.insert(authorized_user.server_name().to_owned());
}
},
| _ => unimplemented!(),
}
Ok(servers_to_check)
Ok(servers_to_check)
}
/// Extracts the server names and key ids to check signatures for given event.
pub fn required_keys(
object: &CanonicalJsonObject,
version: &RoomVersionId,
) -> Result<BTreeMap<OwnedServerName, Vec<OwnedServerSigningKeyId>>, Error> {
) -> Result<BTreeMap<OwnedServerName, Vec<OwnedServerSigningKeyId>>, VerificationError> {
use CanonicalJsonValue::Object;
let mut map = BTreeMap::<OwnedServerName, Vec<OwnedServerSigningKeyId>>::new();
let Some(Object(signatures)) = object.get("signatures") else {
@@ -132,4 +175,4 @@ pub fn required_keys(
}
Ok(map)
}
}
+2 -1
View File
@@ -63,7 +63,8 @@ pub async fn verify_event(
) -> Result<Verified> {
let room_version = room_version.unwrap_or(&RoomVersionId::V12);
let keys = self.get_event_keys(event, room_version).await?;
ruma::signatures::verify_event(&keys, event, &room_version.rules().unwrap()).map_err(Into::into)
ruma::signatures::verify_event(&keys, event, &room_version.rules().unwrap())
.map_err(Into::into)
}
#[implement(super::Service)]
+1 -6
View File
@@ -7,12 +7,7 @@ use std::{
use conduwuit::{Result, Server, SyncMutex};
use database::Map;
use ruma::{
OwnedDeviceId, OwnedRoomId, OwnedUserId,
api::client::sync::sync_events::{
v5,
},
};
use ruma::{OwnedDeviceId, OwnedRoomId, OwnedUserId, api::client::sync::sync_events::v5};
use crate::{Dep, rooms};
+1 -3
View File
@@ -5,9 +5,7 @@ use database::{Deserialized, Map};
use governor::{DefaultKeyedRateLimiter, Quota, RateLimiter};
use lettre::{Address, message::Mailbox};
use nonzero_ext::nonzero;
use ruma::{
ClientSecret, OwnedClientSecret, OwnedSessionId, SessionId, api::client::error::ErrorKind,
};
use ruma::{ClientSecret, OwnedClientSecret, OwnedSessionId, SessionId, api::error::ErrorKind};
mod session;
+1 -4
View File
@@ -13,10 +13,7 @@ use conduwuit::{Error, Result, SyncRwLock, debug_warn, warn};
use database::{Handle, Map};
use ruma::{
DeviceId, OwnedServerName, OwnedTransactionId, TransactionId, UserId,
api::{
client::error::ErrorKind::LimitExceeded,
federation::transactions::send_transaction_message,
},
api::{error::ErrorKind::LimitExceeded, federation::transactions::send_transaction_message},
};
use tokio::sync::watch::{Receiver, Sender};
+17 -44
View File
@@ -8,18 +8,18 @@ use conduwuit::{Err, Error, Result, error, utils, utils::hash};
use lettre::Address;
use ruma::{
UserId,
api::client::{
error::{ErrorKind, StandardErrorBody},
uiaa::{
api::{
client::uiaa::{
AuthData, AuthFlow, AuthType, EmailIdentity, Password, ReCaptcha, RegistrationToken,
ThirdpartyIdCredentials, UiaaInfo, UserIdentifier,
ThirdpartyIdCredentials, UiaaInfo, UserIdentifier, MatrixUserIdentifier
},
error::{ErrorKind, StandardErrorBody},
},
};
use serde_json::value::RawValue;
use tokio::sync::Mutex;
use crate::{Dep, config, globals, registration_tokens, threepid, users};
use crate::{Dep, config, globals, registration_tokens, rooms::user, threepid, users};
pub struct Service {
services: Services,
@@ -190,7 +190,7 @@ impl Service {
let mut uiaa_sessions = self.uiaa_sessions.lock().await;
let session_id = utils::random_string(Self::SESSION_ID_LENGTH);
let mut info = UiaaInfo::new(flows, params);
let mut info = assign::assign!(UiaaInfo::new(flows), {params: Some(params)});
info.session = Some(session_id.clone());
uiaa_sessions.insert(session_id, UiaaSession {
@@ -339,13 +339,10 @@ impl Service {
#[allow(clippy::useless_let_if_seq)]
| AuthData::Password(Password { identifier, password, .. }) => {
let user_id_or_localpart = match identifier {
| Some(UserIdentifier::UserIdOrLocalpart(username)) => username.to_owned(),
| Some(UserIdentifier::Email { address }) => {
| Some(UserIdentifier::Matrix(MatrixUserIdentifier{user})) => user.to_owned(),
| Some(UserIdentifier::Email(address)) => {
let Ok(email) = Address::try_from(address.to_owned()) else {
return Err(StandardErrorBody {
kind: ErrorKind::InvalidParam,
message: "Email is malformed".to_owned(),
});
return Err(StandardErrorBody::new(ErrorKind::InvalidParam, "Email is malformed".to_owned()));
};
if let Some(localpart) =
@@ -355,27 +352,18 @@ impl Service {
localpart
} else {
return Err(StandardErrorBody {
kind: ErrorKind::forbidden(),
message: "Invalid identifier or password".to_owned(),
});
return Err(StandardErrorBody::new(ErrorKind::Forbidden, "Invalid identifier or password".to_owned()));
}
},
| _ =>
return Err(StandardErrorBody {
kind: ErrorKind::Unrecognized,
message: "Identifier type not recognized".to_owned(),
}),
return Err(StandardErrorBody::new(ErrorKind::Unrecognized, "Identifier type not recognized".to_owned())),
};
let Ok(user_id) = UserId::parse_with_server_name(
user_id_or_localpart,
self.services.globals.server_name(),
) else {
return Err(StandardErrorBody {
kind: ErrorKind::InvalidParam,
message: "User ID is malformed".to_owned(),
});
return Err(StandardErrorBody::new(ErrorKind::InvalidParam, "User ID is malformed".to_owned()));
};
// Check if password is correct
@@ -408,29 +396,20 @@ impl Service {
Ok(AuthType::Password)
} else {
Err(StandardErrorBody {
kind: ErrorKind::forbidden(),
message: "Invalid identifier or password".to_owned(),
})
Err(StandardErrorBody::new(ErrorKind::Forbidden, "Invalid identifier or password".to_owned()))
}
},
| AuthData::ReCaptcha(ReCaptcha { response, .. }) => {
let Some(ref private_site_key) = self.services.config.recaptcha_private_site_key
else {
return Err(StandardErrorBody {
kind: ErrorKind::forbidden(),
message: "ReCaptcha is not configured".to_owned(),
});
return Err(StandardErrorBody::new(ErrorKind::Forbidden, "ReCaptcha is not configured".to_owned()));
};
match recaptcha_verify::verify_v3(private_site_key, response, None).await {
| Ok(()) => Ok(AuthType::ReCaptcha),
| Err(e) => {
error!("ReCaptcha verification failed: {e:?}");
Err(StandardErrorBody {
kind: ErrorKind::forbidden(),
message: "ReCaptcha verification failed".to_owned(),
})
Err(StandardErrorBody::new(ErrorKind::Forbidden, "ReCaptcha verification failed".to_owned()))
},
}
},
@@ -449,17 +428,11 @@ impl Service {
Ok(AuthType::RegistrationToken)
} else {
Err(StandardErrorBody {
kind: ErrorKind::forbidden(),
message: "Invalid registration token".to_owned(),
})
Err(StandardErrorBody::new(ErrorKind::Forbidden, "Invalid registration token".to_owned()))
}
},
| AuthData::Terms(_) => Ok(AuthType::Terms),
| _ => Err(StandardErrorBody {
kind: ErrorKind::Unrecognized,
message: "Unsupported stage type".into(),
}),
| _ => Err(StandardErrorBody::new(ErrorKind::Unrecognized, "Unsupported stage type".into())),
}
.map(|auth_type| (auth_type, identity))
}
+5 -3
View File
@@ -19,11 +19,13 @@ use ldap3::{LdapConnAsync, LdapConnSettings, Scope, SearchEntry};
use ruma::{
DeviceId, KeyId, MilliSecondsSinceUnixEpoch, OneTimeKeyAlgorithm, OneTimeKeyId,
OneTimeKeyName, OwnedDeviceId, OwnedKeyId, OwnedMxcUri, OwnedUserId, RoomId, UInt, UserId,
api::client::{device::Device, error::ErrorKind, filter::FilterDefinition},
api::{
client::{device::Device, filter::FilterDefinition},
error::ErrorKind,
},
encryption::{CrossSigningKey, DeviceKeys, OneTimeKey},
events::{
AnyToDeviceEvent, GlobalAccountDataEventType,
ignored_user_list::IgnoredUserListEvent,
AnyToDeviceEvent, GlobalAccountDataEventType, ignored_user_list::IgnoredUserListEvent,
},
serde::Raw,
uint,