mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
refactor: Replace more uses of RoomVersionId with RoomVersionRules
This commit is contained in:
@@ -11,7 +11,7 @@ use std::fmt::Debug;
|
|||||||
|
|
||||||
use ruma::{
|
use ruma::{
|
||||||
CanonicalJsonObject, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, RoomId,
|
CanonicalJsonObject, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, RoomId,
|
||||||
RoomVersionId, UserId, events::TimelineEventType,
|
RoomVersionId, UserId, events::TimelineEventType, room_version_rules::RoomVersionRules,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::{Value as JsonValue, value::RawValue as RawJsonValue};
|
use serde_json::{Value as JsonValue, value::RawValue as RawJsonValue};
|
||||||
@@ -95,11 +95,11 @@ pub trait Event: Clone + Debug {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn redacts_id(&self, room_version: &RoomVersionId) -> Option<OwnedEventId>
|
fn redacts_id(&self, room_version_rules: &RoomVersionRules) -> Option<OwnedEventId>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
redact::redacts_id(self, room_version)
|
redact::redacts_id(self, room_version_rules)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use ruma::{CanonicalJsonObject, OwnedEventId, RoomVersionId};
|
use ruma::{CanonicalJsonObject, OwnedEventId, room_version_rules::RoomVersionRules};
|
||||||
use serde_json::value::RawValue as RawJsonValue;
|
use serde_json::value::RawValue as RawJsonValue;
|
||||||
|
|
||||||
use crate::{Err, Result, err};
|
use crate::{Err, Result, err};
|
||||||
@@ -9,12 +9,12 @@ use crate::{Err, Result, err};
|
|||||||
/// CanonicalJsonValue>`.
|
/// CanonicalJsonValue>`.
|
||||||
pub fn gen_event_id_canonical_json(
|
pub fn gen_event_id_canonical_json(
|
||||||
pdu: &RawJsonValue,
|
pdu: &RawJsonValue,
|
||||||
room_version_id: &RoomVersionId,
|
room_version_rules: &RoomVersionRules,
|
||||||
) -> Result<(OwnedEventId, CanonicalJsonObject)> {
|
) -> Result<(OwnedEventId, CanonicalJsonObject)> {
|
||||||
let value: CanonicalJsonObject = serde_json::from_str(pdu.get())
|
let value: CanonicalJsonObject = serde_json::from_str(pdu.get())
|
||||||
.map_err(|e| err!(BadServerResponse(warn!("Error parsing incoming event: {e:?}"))))?;
|
.map_err(|e| err!(BadServerResponse(warn!("Error parsing incoming event: {e:?}"))))?;
|
||||||
|
|
||||||
let event_id = gen_event_id(&value, room_version_id)?;
|
let event_id = gen_event_id(&value, room_version_rules)?;
|
||||||
|
|
||||||
Ok((event_id, value))
|
Ok((event_id, value))
|
||||||
}
|
}
|
||||||
@@ -22,12 +22,9 @@ pub fn gen_event_id_canonical_json(
|
|||||||
/// Generates a correct eventId for the incoming pdu.
|
/// Generates a correct eventId for the incoming pdu.
|
||||||
pub fn gen_event_id(
|
pub fn gen_event_id(
|
||||||
value: &CanonicalJsonObject,
|
value: &CanonicalJsonObject,
|
||||||
room_version_id: &RoomVersionId,
|
room_version_rules: &RoomVersionRules,
|
||||||
) -> Result<OwnedEventId> {
|
) -> Result<OwnedEventId> {
|
||||||
let Some(rules) = room_version_id.rules() else {
|
let reference_hash = ruma::signatures::reference_hash(value, room_version_rules)?;
|
||||||
return Err!("Cannot generate event ID for unknown room version {room_version_id}");
|
|
||||||
};
|
|
||||||
let reference_hash = ruma::signatures::reference_hash(value, &rules)?;
|
|
||||||
let event_id: OwnedEventId = format!("${reference_hash}").try_into()?;
|
let event_id: OwnedEventId = format!("${reference_hash}").try_into()?;
|
||||||
|
|
||||||
Ok(event_id)
|
Ok(event_id)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use ruma::{
|
use ruma::{
|
||||||
OwnedEventId, RoomVersionId,
|
OwnedEventId, RoomVersionId,
|
||||||
events::{TimelineEventType, room::redaction::RoomRedactionEventContent},
|
events::{TimelineEventType, room::redaction::RoomRedactionEventContent},
|
||||||
|
room_version_rules::RoomVersionRules,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::value::{RawValue as RawJsonValue, to_raw_value};
|
use serde_json::value::{RawValue as RawJsonValue, to_raw_value};
|
||||||
@@ -61,7 +62,7 @@ pub(super) fn is_redacted<E: Event>(event: &E) -> bool {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub(super) fn redacts_id<E: Event>(
|
pub(super) fn redacts_id<E: Event>(
|
||||||
event: &E,
|
event: &E,
|
||||||
room_version: &RoomVersionId,
|
room_version_rules: &RoomVersionRules,
|
||||||
) -> Option<OwnedEventId> {
|
) -> Option<OwnedEventId> {
|
||||||
use RoomVersionId::*;
|
use RoomVersionId::*;
|
||||||
|
|
||||||
@@ -69,14 +70,13 @@ pub(super) fn redacts_id<E: Event>(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
match *room_version {
|
if room_version_rules.redaction.content_field_redacts {
|
||||||
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
|
event.redacts().map(ToOwned::to_owned)
|
||||||
event.redacts().map(ToOwned::to_owned),
|
} else {
|
||||||
| _ =>
|
event
|
||||||
event
|
.get_content::<RoomRedactionEventContent>()
|
||||||
.get_content::<RoomRedactionEventContent>()
|
.ok()?
|
||||||
.ok()?
|
.redacts
|
||||||
.redacts,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use ruma::{
|
|||||||
api::federation::event::get_event,
|
api::federation::event::get_event,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::get_room_version;
|
use super::get_room_version_rules;
|
||||||
|
|
||||||
/// Find the event and auth it. Once the event is validated (steps 1 - 8)
|
/// Find the event and auth it. Once the event is validated (steps 1 - 8)
|
||||||
/// it is appended to the outliers Tree.
|
/// it is appended to the outliers Tree.
|
||||||
@@ -117,13 +117,13 @@ where
|
|||||||
{
|
{
|
||||||
| Ok(res) => {
|
| Ok(res) => {
|
||||||
debug!("Got {next_id} over federation from {origin}");
|
debug!("Got {next_id} over federation from {origin}");
|
||||||
let Ok(room_version_id) = get_room_version(create_event) else {
|
let Ok(room_version_rules) = get_room_version_rules(create_event) else {
|
||||||
back_off((*next_id).to_owned());
|
back_off((*next_id).to_owned());
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok((calculated_event_id, value)) =
|
let Ok((calculated_event_id, value)) =
|
||||||
gen_event_id_canonical_json(&res.pdu, &room_version_id)
|
gen_event_id_canonical_json(&res.pdu, &room_version_rules)
|
||||||
else {
|
else {
|
||||||
back_off((*next_id).to_owned());
|
back_off((*next_id).to_owned());
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use ruma::{
|
|||||||
events::StateEventType,
|
events::StateEventType,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{check_room_id, get_room_version};
|
use super::{check_room_id, get_room_version_rules};
|
||||||
use crate::rooms::timeline::pdu_fits;
|
use crate::rooms::timeline::pdu_fits;
|
||||||
|
|
||||||
#[implement(super::Service)]
|
#[implement(super::Service)]
|
||||||
@@ -41,21 +41,20 @@ where
|
|||||||
|
|
||||||
// 2. Check signatures, otherwise drop
|
// 2. Check signatures, otherwise drop
|
||||||
// 3. check content hash, redact if doesn't match
|
// 3. check content hash, redact if doesn't match
|
||||||
let room_version = get_room_version(create_event)?;
|
let room_version_rules = get_room_version_rules(create_event)?;
|
||||||
let room_rules = room_version
|
|
||||||
.rules()
|
|
||||||
.expect("room version should have defined rules");
|
|
||||||
let mut incoming_pdu = match self
|
let mut incoming_pdu = match self
|
||||||
.services
|
.services
|
||||||
.server_keys
|
.server_keys
|
||||||
.verify_event(&value, Some(&room_version))
|
.verify_event(&value, &room_version_rules)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
| Ok(ruma::signatures::Verified::All) => value,
|
| Ok(ruma::signatures::Verified::All) => value,
|
||||||
| Ok(ruma::signatures::Verified::Signatures) => {
|
| Ok(ruma::signatures::Verified::Signatures) => {
|
||||||
// Redact
|
// Redact
|
||||||
debug_info!("Calculated hash does not match (redaction): {event_id}");
|
debug_info!("Calculated hash does not match (redaction): {event_id}");
|
||||||
let Ok(obj) = ruma::canonical_json::redact(value, &room_rules.redaction, None) else {
|
let Ok(obj) =
|
||||||
|
ruma::canonical_json::redact(value, &room_version_rules.redaction, None)
|
||||||
|
else {
|
||||||
return Err!(Request(InvalidParam("Redaction failed")));
|
return Err!(Request(InvalidParam("Redaction failed")));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -187,7 +186,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
let auth_check = state_res::event_auth::auth_check(
|
let auth_check = state_res::event_auth::auth_check(
|
||||||
&room_rules,
|
&room_version_rules,
|
||||||
&pdu_event,
|
&pdu_event,
|
||||||
None, // TODO: third party invite
|
None, // TODO: third party invite
|
||||||
state_fetch,
|
state_fetch,
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ use std::{collections::HashMap, fmt::Write, sync::Arc, time::Instant};
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use conduwuit::{Err, Event, PduEvent, Result, Server, SyncRwLock, utils::MutexMap};
|
use conduwuit::{Err, Event, PduEvent, Result, Server, SyncRwLock, utils::MutexMap};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
OwnedEventId, OwnedRoomId, RoomId, RoomVersionId,
|
OwnedEventId, OwnedRoomId, RoomId, events::room::create::RoomCreateEventContent,
|
||||||
events::room::create::RoomCreateEventContent,
|
room_version_rules::RoomVersionRules,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Dep, globals, rooms, sending, server_keys};
|
use crate::{Dep, globals, rooms, sending, server_keys};
|
||||||
@@ -114,9 +114,11 @@ fn check_room_id<Pdu: Event>(room_id: &RoomId, pdu: &Pdu) -> Result {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_room_version<Pdu: Event>(create_event: &Pdu) -> Result<RoomVersionId> {
|
fn get_room_version_rules<Pdu: Event>(create_event: &Pdu) -> Result<RoomVersionRules> {
|
||||||
let content: RoomCreateEventContent = create_event.get_content()?;
|
let content: RoomCreateEventContent = create_event.get_content()?;
|
||||||
let room_version = content.room_version;
|
let Some(room_version_rules) = content.room_version.rules() else {
|
||||||
|
return Err!(Request(UnsupportedRoomVersion("Room version has no defined rules")));
|
||||||
|
};
|
||||||
|
|
||||||
Ok(room_version)
|
Ok(room_version_rules)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use conduwuit::{
|
|||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, RoomVersionId, };
|
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, RoomId, RoomVersionId };
|
||||||
use serde_json::value::RawValue as RawJsonValue;
|
use serde_json::value::RawValue as RawJsonValue;
|
||||||
|
|
||||||
type Parsed = (OwnedRoomId, OwnedEventId, CanonicalJsonObject);
|
type Parsed = (OwnedRoomId, OwnedEventId, CanonicalJsonObject);
|
||||||
@@ -16,9 +16,8 @@ type Parsed = (OwnedRoomId, OwnedEventId, CanonicalJsonObject);
|
|||||||
/// field over federation, it will be calculated if not provided, otherwise a
|
/// field over federation, it will be calculated if not provided, otherwise a
|
||||||
/// validation error will be returned.
|
/// validation error will be returned.
|
||||||
fn extract_room_id(event_type: &str, pdu: &CanonicalJsonObject) -> Result<OwnedRoomId> {
|
fn extract_room_id(event_type: &str, pdu: &CanonicalJsonObject) -> Result<OwnedRoomId> {
|
||||||
use RoomVersionId::*;
|
|
||||||
if let Some(room_id) = pdu.get("room_id").and_then(CanonicalJsonValue::as_str) {
|
if let Some(room_id) = pdu.get("room_id").and_then(CanonicalJsonValue::as_str) {
|
||||||
return OwnedRoomId::parse(room_id)
|
return RoomId::parse(room_id)
|
||||||
.map_err(|e| err!(Request(BadJson("Invalid room_id {room_id:?} in pdu: {e}"))));
|
.map_err(|e| err!(Request(BadJson("Invalid room_id {room_id:?} in pdu: {e}"))));
|
||||||
}
|
}
|
||||||
// If there's no room ID, and this is not a create event, it is illegal.
|
// If there's no room ID, and this is not a create event, it is illegal.
|
||||||
@@ -27,7 +26,7 @@ fn extract_room_id(event_type: &str, pdu: &CanonicalJsonObject) -> Result<OwnedR
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Room versions 11 and below require the room ID is present.
|
// Room versions 11 and below require the room ID is present.
|
||||||
let room_version_id = RoomVersionId::from_str(
|
let room_version = RoomVersionId::from_str(
|
||||||
pdu.get("content")
|
pdu.get("content")
|
||||||
.and_then(CanonicalJsonValue::as_object)
|
.and_then(CanonicalJsonValue::as_object)
|
||||||
.ok_or_else(|| err!(Request(InvalidParam("Missing or invalid content in pdu"))))?
|
.ok_or_else(|| err!(Request(InvalidParam("Missing or invalid content in pdu"))))?
|
||||||
@@ -37,11 +36,16 @@ fn extract_room_id(event_type: &str, pdu: &CanonicalJsonObject) -> Result<OwnedR
|
|||||||
)
|
)
|
||||||
.map_err(|e| err!(Request(BadJson("Invalid room_version in pdu: {e}"))))?;
|
.map_err(|e| err!(Request(BadJson("Invalid room_version in pdu: {e}"))))?;
|
||||||
|
|
||||||
if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 | V11) {
|
let Some(room_version_rules) = room_version.rules() else {
|
||||||
|
return Err!(Request(BadJson("Unknown room version in pdu")));
|
||||||
|
};
|
||||||
|
|
||||||
|
if !room_version_rules.authorization.room_create_event_id_as_room_id {
|
||||||
return Err!(Request(BadJson("Missing room_id in pdu")));
|
return Err!(Request(BadJson("Missing room_id in pdu")));
|
||||||
}
|
}
|
||||||
let event_id = gen_event_id(pdu, &room_version_id)?;
|
|
||||||
Ok(OwnedRoomId::parse(event_id.as_str().replace('$', "!"))
|
let event_id = gen_event_id(pdu, &room_version_rules)?;
|
||||||
|
Ok(RoomId::parse(event_id.as_str().replace('$', "!"))
|
||||||
.expect("constructed room ID has to be valid"))
|
.expect("constructed room ID has to be valid"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +64,7 @@ fn expect_event_id_array(value: &CanonicalJsonObject, field: &str) -> Result<Vec
|
|||||||
err!(Request(BadJson("expected an array of event IDs for `{field}`")))
|
err!(Request(BadJson("expected an array of event IDs for `{field}`")))
|
||||||
})
|
})
|
||||||
.and_then(|s| {
|
.and_then(|s| {
|
||||||
OwnedEventId::parse(s)
|
EventId::parse(s)
|
||||||
.map_err(|e| err!(Request(BadJson("invalid event ID in `{field}`: {e}"))))
|
.map_err(|e| err!(Request(BadJson("invalid event ID in `{field}`: {e}"))))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -103,13 +107,16 @@ pub async fn parse_incoming_pdu(&self, pdu: &RawJsonValue) -> Result<Parsed> {
|
|||||||
|
|
||||||
let room_id = extract_room_id(event_type, &value)?;
|
let room_id = extract_room_id(event_type, &value)?;
|
||||||
|
|
||||||
let room_version_id = self
|
let room_version_rules = self
|
||||||
.services
|
.services
|
||||||
.state
|
.state
|
||||||
.get_room_version(&room_id)
|
.get_room_version(&room_id)
|
||||||
.await
|
.await
|
||||||
.unwrap_or(RoomVersionId::V1);
|
.unwrap_or(RoomVersionId::V1)
|
||||||
let (event_id, value) = gen_event_id_canonical_json(pdu, &room_version_id).map_err(|e| {
|
.rules()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (event_id, value) = gen_event_id_canonical_json(pdu, &room_version_rules).map_err(|e| {
|
||||||
err!(Request(InvalidParam("Could not convert event to canonical json: {e}")))
|
err!(Request(InvalidParam("Could not convert event to canonical json: {e}")))
|
||||||
})?;
|
})?;
|
||||||
self.validate_pdu(&value)?;
|
self.validate_pdu(&value)?;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use conduwuit::{
|
|||||||
use futures::{FutureExt, StreamExt, future::ready};
|
use futures::{FutureExt, StreamExt, future::ready};
|
||||||
use ruma::{CanonicalJsonValue, RoomId, ServerName, events::StateEventType};
|
use ruma::{CanonicalJsonValue, RoomId, ServerName, events::StateEventType};
|
||||||
|
|
||||||
use super::get_room_version;
|
use super::get_room_version_rules;
|
||||||
use crate::rooms::{
|
use crate::rooms::{
|
||||||
state_compressor::{CompressedState, HashSetCompressStateEvent},
|
state_compressor::{CompressedState, HashSetCompressStateEvent},
|
||||||
timeline::RawPduId,
|
timeline::RawPduId,
|
||||||
@@ -52,10 +52,7 @@ where
|
|||||||
"Upgrading PDU from outlier to timeline"
|
"Upgrading PDU from outlier to timeline"
|
||||||
);
|
);
|
||||||
let timer = Instant::now();
|
let timer = Instant::now();
|
||||||
let room_version_id = get_room_version(create_event)?;
|
let room_version_rules = get_room_version_rules(create_event)?;
|
||||||
let room_version_rules = room_version_id
|
|
||||||
.rules()
|
|
||||||
.expect("room version should have defined rules");
|
|
||||||
|
|
||||||
// 10. Fetch missing state and auth chain events by calling /state_ids at
|
// 10. Fetch missing state and auth chain events by calling /state_ids at
|
||||||
// backwards extremities doing all the checks in this list starting at 1.
|
// backwards extremities doing all the checks in this list starting at 1.
|
||||||
@@ -153,7 +150,7 @@ where
|
|||||||
event_id = %incoming_pdu.event_id,
|
event_id = %incoming_pdu.event_id,
|
||||||
"Performing soft-fail check"
|
"Performing soft-fail check"
|
||||||
);
|
);
|
||||||
let mut soft_fail = match (auth_check, incoming_pdu.redacts_id(&room_version_id)) {
|
let mut soft_fail = match (auth_check, incoming_pdu.redacts_id(&room_version_rules)) {
|
||||||
| (false, _) => true,
|
| (false, _) => true,
|
||||||
| (true, None) => false,
|
| (true, None) => false,
|
||||||
| (true, Some(redact_id)) =>
|
| (true, Some(redact_id)) =>
|
||||||
@@ -286,7 +283,7 @@ where
|
|||||||
// TODO: this is supposed to hide redactions from policy servers, however, for
|
// TODO: this is supposed to hide redactions from policy servers, however, for
|
||||||
// full efficacy it also needs to hide redactions for unknown events. This
|
// full efficacy it also needs to hide redactions for unknown events. This
|
||||||
// needs to be investigated at a later time.
|
// needs to be investigated at a later time.
|
||||||
if let Some(redact_id) = incoming_pdu.redacts_id(&room_version_id) {
|
if let Some(redact_id) = incoming_pdu.redacts_id(&room_version_rules) {
|
||||||
debug!(
|
debug!(
|
||||||
redact_id = %redact_id,
|
redact_id = %redact_id,
|
||||||
"Checking if redaction is for a soft-failed event"
|
"Checking if redaction is for a soft-failed event"
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ pub async fn create_hash_and_sign_event(
|
|||||||
if let Err(e) = self
|
if let Err(e) = self
|
||||||
.services
|
.services
|
||||||
.server_keys
|
.server_keys
|
||||||
.hash_and_sign_event(&mut pdu_json, &room_version)
|
.hash_and_sign_event(&mut pdu_json, &room_version_rules)
|
||||||
{
|
{
|
||||||
return match e {
|
return match e {
|
||||||
| Error::SignatureJson(ruma::signatures::JsonError::PduTooLarge) => {
|
| Error::SignatureJson(ruma::signatures::JsonError::PduTooLarge) => {
|
||||||
@@ -299,7 +299,7 @@ pub async fn create_hash_and_sign_event(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
// Generate event id
|
// Generate event id
|
||||||
pdu.event_id = gen_event_id(&pdu_json, &room_version)?;
|
pdu.event_id = gen_event_id(&pdu_json, &room_version_rules)?;
|
||||||
pdu_json.insert("event_id".into(), CanonicalJsonValue::String(pdu.event_id.clone().into()));
|
pdu_json.insert("event_id".into(), CanonicalJsonValue::String(pdu.event_id.clone().into()));
|
||||||
// Verify that the *full* PDU isn't over 64KiB.
|
// Verify that the *full* PDU isn't over 64KiB.
|
||||||
// Ruma only validates that it's under 64KiB before signing and hashing.
|
// Ruma only validates that it's under 64KiB before signing and hashing.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::borrow::Borrow;
|
|||||||
use conduwuit::{Err, Result, debug_error, implement, trace};
|
use conduwuit::{Err, Result, debug_error, implement, trace};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
CanonicalJsonObject, RoomVersionId, ServerName, ServerSigningKeyId,
|
CanonicalJsonObject, RoomVersionId, ServerName, ServerSigningKeyId,
|
||||||
api::federation::discovery::VerifyKey,
|
api::federation::discovery::VerifyKey, room_version_rules::RoomVersionRules,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{PubKeyMap, PubKeys, extract_key};
|
use super::{PubKeyMap, PubKeys, extract_key};
|
||||||
@@ -13,9 +13,9 @@ use crate::server_keys::util::required_keys;
|
|||||||
pub async fn get_event_keys(
|
pub async fn get_event_keys(
|
||||||
&self,
|
&self,
|
||||||
object: &CanonicalJsonObject,
|
object: &CanonicalJsonObject,
|
||||||
version: &RoomVersionId,
|
room_version_rules: &RoomVersionRules,
|
||||||
) -> Result<PubKeyMap> {
|
) -> Result<PubKeyMap> {
|
||||||
let required = match required_keys(object, version) {
|
let required = match required_keys(object, &room_version_rules.signatures) {
|
||||||
| Ok(required) => required,
|
| Ok(required) => required,
|
||||||
| Err(e) => {
|
| Err(e) => {
|
||||||
debug_error!("Failed to determine keys required to verify: {e}");
|
debug_error!("Failed to determine keys required to verify: {e}");
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use ruma::{
|
|||||||
CanonicalJsonObject, MilliSecondsSinceUnixEpoch, OwnedServerSigningKeyId, RoomVersionId,
|
CanonicalJsonObject, MilliSecondsSinceUnixEpoch, OwnedServerSigningKeyId, RoomVersionId,
|
||||||
ServerName, ServerSigningKeyId,
|
ServerName, ServerSigningKeyId,
|
||||||
api::federation::discovery::{ServerSigningKeys, VerifyKey},
|
api::federation::discovery::{ServerSigningKeys, VerifyKey},
|
||||||
|
room_version_rules::RoomVersionRules,
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
signatures::{Ed25519KeyPair, PublicKeyMap, PublicKeySet},
|
signatures::{Ed25519KeyPair, PublicKeyMap, PublicKeySet},
|
||||||
};
|
};
|
||||||
@@ -117,10 +118,10 @@ async fn add_signing_keys(&self, new_keys: ServerSigningKeys) {
|
|||||||
pub async fn required_keys_exist(
|
pub async fn required_keys_exist(
|
||||||
&self,
|
&self,
|
||||||
object: &CanonicalJsonObject,
|
object: &CanonicalJsonObject,
|
||||||
version: &RoomVersionId,
|
room_version_rules: &RoomVersionRules,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
trace!(?object, "Checking required keys exist");
|
trace!(?object, "Checking required keys exist");
|
||||||
let Ok(required_keys) = required_keys(object, version) else {
|
let Ok(required_keys) = required_keys(object, &room_version_rules.signatures) else {
|
||||||
debug_error!("Failed to determine required keys");
|
debug_error!("Failed to determine required keys");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use conduwuit::{Result, implement};
|
use conduwuit::{Result, implement};
|
||||||
use ruma::{CanonicalJsonObject, RoomVersionId};
|
use ruma::{CanonicalJsonObject, RoomVersionId, room_version_rules::RoomVersionRules};
|
||||||
|
|
||||||
#[implement(super::Service)]
|
#[implement(super::Service)]
|
||||||
pub fn sign_json(&self, object: &mut CanonicalJsonObject) -> Result {
|
pub fn sign_json(&self, object: &mut CanonicalJsonObject) -> Result {
|
||||||
@@ -13,16 +13,11 @@ pub fn sign_json(&self, object: &mut CanonicalJsonObject) -> Result {
|
|||||||
pub fn hash_and_sign_event(
|
pub fn hash_and_sign_event(
|
||||||
&self,
|
&self,
|
||||||
object: &mut CanonicalJsonObject,
|
object: &mut CanonicalJsonObject,
|
||||||
room_version: &RoomVersionId,
|
room_version_rules: &RoomVersionRules,
|
||||||
) -> Result {
|
) -> Result {
|
||||||
use ruma::signatures::hash_and_sign_event;
|
use ruma::signatures::hash_and_sign_event;
|
||||||
|
|
||||||
let server_name = self.services.globals.server_name().as_str();
|
let server_name = self.services.globals.server_name().as_str();
|
||||||
hash_and_sign_event(
|
hash_and_sign_event(server_name, self.keypair(), object, &room_version_rules.redaction)
|
||||||
server_name,
|
.map_err(Into::into)
|
||||||
self.keypair(),
|
|
||||||
object,
|
|
||||||
&room_version.rules().unwrap().redaction,
|
|
||||||
)
|
|
||||||
.map_err(Into::into)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,157 +4,14 @@ use ruma::{
|
|||||||
CanonicalJsonObject, CanonicalJsonValue, IdParseError, OwnedEventId, OwnedServerName,
|
CanonicalJsonObject, CanonicalJsonValue, IdParseError, OwnedEventId, OwnedServerName,
|
||||||
OwnedServerSigningKeyId, RoomVersionId, UserId,
|
OwnedServerSigningKeyId, RoomVersionId, UserId,
|
||||||
canonical_json::JsonType,
|
canonical_json::JsonType,
|
||||||
signatures::{JsonError, VerificationError},
|
room_version_rules::SignaturesRules,
|
||||||
|
signatures::{JsonError, VerificationError, required_server_signatures_to_verify_event},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// 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, 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
};
|
|
||||||
|
|
||||||
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()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extracts the server names to check signatures for given event.
|
|
||||||
///
|
|
||||||
/// 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.
|
|
||||||
/// - 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.
|
|
||||||
///
|
|
||||||
/// [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>, 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(|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()),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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(|| VerificationError::ParseIdentifier {
|
|
||||||
identifier_type: "event ID",
|
|
||||||
source: IdParseError::InvalidServerName,
|
|
||||||
})?
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
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!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(servers_to_check)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extracts the server names and key ids to check signatures for given event.
|
/// Extracts the server names and key ids to check signatures for given event.
|
||||||
pub fn required_keys(
|
pub fn required_keys(
|
||||||
object: &CanonicalJsonObject,
|
object: &CanonicalJsonObject,
|
||||||
version: &RoomVersionId,
|
rules: &SignaturesRules,
|
||||||
) -> Result<BTreeMap<OwnedServerName, Vec<OwnedServerSigningKeyId>>, VerificationError> {
|
) -> Result<BTreeMap<OwnedServerName, Vec<OwnedServerSigningKeyId>>, VerificationError> {
|
||||||
use CanonicalJsonValue::Object;
|
use CanonicalJsonValue::Object;
|
||||||
let mut map = BTreeMap::<OwnedServerName, Vec<OwnedServerSigningKeyId>>::new();
|
let mut map = BTreeMap::<OwnedServerName, Vec<OwnedServerSigningKeyId>>::new();
|
||||||
@@ -162,7 +19,7 @@ pub fn required_keys(
|
|||||||
return Ok(map);
|
return Ok(map);
|
||||||
};
|
};
|
||||||
|
|
||||||
for server in servers_to_check_signatures(object, version)? {
|
for server in required_server_signatures_to_verify_event(object, rules)? {
|
||||||
let Some(Object(set)) = signatures.get(server.as_str()) else {
|
let Some(Object(set)) = signatures.get(server.as_str()) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ use conduwuit::{
|
|||||||
Err, Result, debug_warn, implement, matrix::event::gen_event_id_canonical_json, trace,
|
Err, Result, debug_warn, implement, matrix::event::gen_event_id_canonical_json, trace,
|
||||||
};
|
};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, RoomVersionId, signatures::Verified,
|
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, RoomVersionId,
|
||||||
|
room_version_rules::RoomVersionRules, signatures::Verified,
|
||||||
};
|
};
|
||||||
use serde_json::value::RawValue as RawJsonValue;
|
use serde_json::value::RawValue as RawJsonValue;
|
||||||
|
|
||||||
@@ -10,10 +11,10 @@ use serde_json::value::RawValue as RawJsonValue;
|
|||||||
pub async fn validate_and_add_event_id(
|
pub async fn validate_and_add_event_id(
|
||||||
&self,
|
&self,
|
||||||
pdu: &RawJsonValue,
|
pdu: &RawJsonValue,
|
||||||
room_version: &RoomVersionId,
|
room_version_rules: &RoomVersionRules,
|
||||||
) -> Result<(OwnedEventId, CanonicalJsonObject)> {
|
) -> Result<(OwnedEventId, CanonicalJsonObject)> {
|
||||||
let (event_id, mut value) = gen_event_id_canonical_json(pdu, room_version)?;
|
let (event_id, mut value) = gen_event_id_canonical_json(pdu, room_version_rules)?;
|
||||||
if let Err(e) = self.verify_event(&value, Some(room_version)).await {
|
if let Err(e) = self.verify_event(&value, room_version_rules).await {
|
||||||
return Err!(BadServerResponse(debug_error!(
|
return Err!(BadServerResponse(debug_error!(
|
||||||
"Event {event_id} failed verification: {e:?}"
|
"Event {event_id} failed verification: {e:?}"
|
||||||
)));
|
)));
|
||||||
@@ -28,12 +29,12 @@ pub async fn validate_and_add_event_id(
|
|||||||
pub async fn validate_and_add_event_id_no_fetch(
|
pub async fn validate_and_add_event_id_no_fetch(
|
||||||
&self,
|
&self,
|
||||||
pdu: &RawJsonValue,
|
pdu: &RawJsonValue,
|
||||||
room_version: &RoomVersionId,
|
room_version_rules: &RoomVersionRules,
|
||||||
) -> Result<(OwnedEventId, CanonicalJsonObject)> {
|
) -> Result<(OwnedEventId, CanonicalJsonObject)> {
|
||||||
trace!(?pdu, "Validating PDU without fetching keys");
|
trace!(?pdu, "Validating PDU without fetching keys");
|
||||||
let (event_id, mut value) = gen_event_id_canonical_json(pdu, room_version)?;
|
let (event_id, mut value) = gen_event_id_canonical_json(pdu, room_version_rules)?;
|
||||||
trace!(event_id = event_id.as_str(), "Generated event ID, checking required keys");
|
trace!(event_id = event_id.as_str(), "Generated event ID, checking required keys");
|
||||||
if !self.required_keys_exist(&value, room_version).await {
|
if !self.required_keys_exist(&value, room_version_rules).await {
|
||||||
debug_warn!(
|
debug_warn!(
|
||||||
"Event {event_id} is missing required keys, cannot verify without fetching keys"
|
"Event {event_id} is missing required keys, cannot verify without fetching keys"
|
||||||
);
|
);
|
||||||
@@ -42,7 +43,7 @@ pub async fn validate_and_add_event_id_no_fetch(
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
trace!("All required keys exist, verifying event");
|
trace!("All required keys exist, verifying event");
|
||||||
if let Err(e) = self.verify_event(&value, Some(room_version)).await {
|
if let Err(e) = self.verify_event(&value, room_version_rules).await {
|
||||||
debug_warn!("Event verification failed");
|
debug_warn!("Event verification failed");
|
||||||
return Err!(BadServerResponse(debug_error!(
|
return Err!(BadServerResponse(debug_error!(
|
||||||
"Event {event_id} failed verification: {e:?}"
|
"Event {event_id} failed verification: {e:?}"
|
||||||
@@ -59,21 +60,18 @@ pub async fn validate_and_add_event_id_no_fetch(
|
|||||||
pub async fn verify_event(
|
pub async fn verify_event(
|
||||||
&self,
|
&self,
|
||||||
event: &CanonicalJsonObject,
|
event: &CanonicalJsonObject,
|
||||||
room_version: Option<&RoomVersionId>,
|
room_version_rules: &RoomVersionRules,
|
||||||
) -> Result<Verified> {
|
) -> Result<Verified> {
|
||||||
let room_version = room_version.unwrap_or(&RoomVersionId::V12);
|
let keys = self.get_event_keys(event, room_version_rules).await?;
|
||||||
let keys = self.get_event_keys(event, room_version).await?;
|
ruma::signatures::verify_event(&keys, event, room_version_rules).map_err(Into::into)
|
||||||
ruma::signatures::verify_event(&keys, event, &room_version.rules().unwrap())
|
|
||||||
.map_err(Into::into)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[implement(super::Service)]
|
#[implement(super::Service)]
|
||||||
pub async fn verify_json(
|
pub async fn verify_json(
|
||||||
&self,
|
&self,
|
||||||
event: &CanonicalJsonObject,
|
event: &CanonicalJsonObject,
|
||||||
room_version: Option<&RoomVersionId>,
|
room_version_rules: &RoomVersionRules,
|
||||||
) -> Result {
|
) -> Result {
|
||||||
let room_version = room_version.unwrap_or(&RoomVersionId::V12);
|
let keys = self.get_event_keys(event, room_version_rules).await?;
|
||||||
let keys = self.get_event_keys(event, room_version).await?;
|
|
||||||
ruma::signatures::verify_json(&keys, event).map_err(Into::into)
|
ruma::signatures::verify_json(&keys, event).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user