[WIP] Move redaction from ruma-signatures to ruma-common

This commit is contained in:
Jonas Platte 2022-06-17 19:27:46 +02:00 committed by Jonas Platte
parent 402b2764fb
commit 940450b953
2 changed files with 153 additions and 154 deletions

View File

@ -1,12 +1,14 @@
//! Canonical JSON types and related functions.
use std::fmt;
use std::{fmt, mem};
use serde::Serialize;
use serde_json::{Error as JsonError, Value as JsonValue};
use serde_json::Value as JsonValue;
mod value;
use crate::RoomVersionId;
pub use self::value::{CanonicalJsonObject, CanonicalJsonValue};
/// The set of possible errors when serializing to canonical JSON.
@ -18,7 +20,7 @@ pub enum CanonicalJsonError {
IntConvert,
/// An error occurred while serializing/deserializing.
SerDe(JsonError),
SerDe(serde_json::Error),
}
impl fmt::Display for CanonicalJsonError {
@ -48,6 +50,154 @@ pub fn to_canonical_value<T: Serialize>(
serde_json::to_value(value).map_err(CanonicalJsonError::SerDe)?.try_into()
}
/// Redacts an event using the rules specified in the Matrix client-server specification.
///
/// This is part of the process of signing an event.
///
/// Redaction is also suggested when a verifying an event with `verify_event` returns
/// `Verified::Signatures`. See the documentation for `Verified` for details.
///
/// Returns a new JSON object with all applicable fields redacted.
///
/// # Parameters
///
/// * object: A JSON object to redact.
///
/// # Errors
///
/// Returns an error if:
///
/// * `object` contains a field called `content` that is not a JSON object.
/// * `object` contains a field called `hashes` that is not a JSON object.
/// * `object` contains a field called `signatures` that is not a JSON object.
/// * `object` is missing the `type` field or the field is not a JSON string.
pub fn redact(
object: &CanonicalJsonObject,
version: &RoomVersionId,
) -> Result<CanonicalJsonObject, ()> {
let mut val = object.clone();
redact_in_place(&mut val, version)?;
Ok(val)
}
/// Redacts an event using the rules specified in the Matrix client-server specification.
///
/// Functionally equivalent to `redact`, only;
/// * upon error, the event is not touched.
/// * this'll redact the event in-place.
pub fn redact_in_place(event: &mut CanonicalJsonObject, version: &RoomVersionId) -> Result<(), ()> {
// Get the content keys here instead of the event type, because we cant teach rust that this is
// a disjoint borrow.
let allowed_content_keys: &[&str] = match event.get("type") {
Some(CanonicalJsonValue::String(event_type)) => {
allowed_content_keys_for(event_type, version)
}
Some(_) => return Err(()),
None => return Err(()),
};
if let Some(content_value) = event.get_mut("content") {
let content = match content_value {
CanonicalJsonValue::Object(map) => map,
_ => return Err(()),
};
object_retain_keys(content, allowed_content_keys);
}
let mut old_event = mem::take(event);
for &key in ALLOWED_KEYS {
if let Some(value) = old_event.remove(key) {
event.insert(key.to_owned(), value);
}
}
Ok(())
}
/// Redacts event content using the rules specified in the Matrix client-server specification.
///
/// Edits the `object` in-place.
pub fn redact_content_in_place(
object: &mut CanonicalJsonObject,
version: &RoomVersionId,
event_type: impl AsRef<str>,
) {
object_retain_keys(object, allowed_content_keys_for(event_type.as_ref(), version));
}
fn object_retain_keys(object: &mut CanonicalJsonObject, keys: &[&str]) {
let mut old_content = mem::take(object);
for &key in keys {
if let Some(value) = old_content.remove(key) {
object.insert(key.to_owned(), value);
}
}
}
/// The fields that are allowed to remain in an event during redaction.
static ALLOWED_KEYS: &[&str] = &[
"event_id",
"type",
"room_id",
"sender",
"state_key",
"content",
"hashes",
"signatures",
"depth",
"prev_events",
"prev_state",
"auth_events",
"origin",
"origin_server_ts",
"membership",
];
fn allowed_content_keys_for(event_type: &str, version: &RoomVersionId) -> &'static [&'static str] {
match event_type {
"m.room.member" => match version {
RoomVersionId::V9 | RoomVersionId::V10 => {
&["membership", "join_authorised_via_users_server"]
}
_ => &["membership"],
},
"m.room.create" => &["creator"],
"m.room.join_rules" => match version {
RoomVersionId::V8 | RoomVersionId::V9 | RoomVersionId::V10 => &["join_rule", "allow"],
_ => &["join_rule"],
},
"m.room.power_levels" => &[
"ban",
"events",
"events_default",
"kick",
"redact",
"state_default",
"users",
"users_default",
],
"m.room.aliases" => match version {
RoomVersionId::V1
| RoomVersionId::V2
| RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5 => &["aliases"],
// All other room versions, including custom ones, are treated by version 6 rules.
// TODO: Should we return an error for unknown versions instead?
_ => &[],
},
#[cfg(feature = "unstable-msc2870")]
"m.room.server_acl" if version.as_str() == "org.matrix.msc2870" => {
&["allow", "deny", "allow_ip_literals"]
}
"m.room.history_visibility" => &["history_visibility"],
_ => &[],
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;

View File

@ -23,67 +23,6 @@ use crate::{
const MAX_PDU_BYTES: usize = 65_535;
/// The fields that are allowed to remain in an event during redaction.
static ALLOWED_KEYS: &[&str] = &[
"event_id",
"type",
"room_id",
"sender",
"state_key",
"content",
"hashes",
"signatures",
"depth",
"prev_events",
"prev_state",
"auth_events",
"origin",
"origin_server_ts",
"membership",
];
fn allowed_content_keys_for(event_type: &str, version: &RoomVersionId) -> &'static [&'static str] {
match event_type {
"m.room.member" => match version {
RoomVersionId::V9 | RoomVersionId::V10 => {
&["membership", "join_authorised_via_users_server"]
}
_ => &["membership"],
},
"m.room.create" => &["creator"],
"m.room.join_rules" => match version {
RoomVersionId::V8 | RoomVersionId::V9 | RoomVersionId::V10 => &["join_rule", "allow"],
_ => &["join_rule"],
},
"m.room.power_levels" => &[
"ban",
"events",
"events_default",
"kick",
"redact",
"state_default",
"users",
"users_default",
],
"m.room.aliases" => match version {
RoomVersionId::V1
| RoomVersionId::V2
| RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5 => &["aliases"],
// All other room versions, including custom ones, are treated by version 6 rules.
// TODO: Should we return an error for unknown versions instead?
_ => &[],
},
#[cfg(feature = "unstable-msc2870")]
"m.room.server_acl" if version.as_str() == "org.matrix.msc2870" => {
&["allow", "deny", "allow_ip_literals"]
}
"m.room.history_visibility" => &["history_visibility"],
_ => &[],
}
}
/// The fields to remove from a JSON object when converting JSON into the "canonical" form.
static CANONICAL_JSON_FIELDS_TO_REMOVE: &[&str] = &["signatures", "unsigned"];
@ -708,96 +647,6 @@ fn canonical_json_with_fields_to_remove(
to_json_string(&owned_object).map_err(|e| Error::Json(e.into()))
}
/// Redacts an event using the rules specified in the Matrix client-server specification.
///
/// This is part of the process of signing an event.
///
/// Redaction is also suggested when a verifying an event with `verify_event` returns
/// `Verified::Signatures`. See the documentation for `Verified` for details.
///
/// Returns a new JSON object with all applicable fields redacted.
///
/// # Parameters
///
/// * object: A JSON object to redact.
///
/// # Errors
///
/// Returns an error if:
///
/// * `object` contains a field called `content` that is not a JSON object.
/// * `object` contains a field called `hashes` that is not a JSON object.
/// * `object` contains a field called `signatures` that is not a JSON object.
/// * `object` is missing the `type` field or the field is not a JSON string.
pub fn redact(
object: &CanonicalJsonObject,
version: &RoomVersionId,
) -> Result<CanonicalJsonObject, Error> {
let mut val = object.clone();
redact_in_place(&mut val, version)?;
Ok(val)
}
/// Redacts an event using the rules specified in the Matrix client-server specification.
///
/// Functionally equivalent to `redact`, only;
/// * upon error, the event is not touched.
/// * this'll redact the event in-place.
pub fn redact_in_place(
event: &mut CanonicalJsonObject,
version: &RoomVersionId,
) -> Result<(), Error> {
// Get the content keys here instead of the event type, because we cant teach rust that this is
// a disjoint borrow.
let allowed_content_keys: &[&str] = match event.get("type") {
Some(CanonicalJsonValue::String(event_type)) => {
allowed_content_keys_for(event_type, version)
}
Some(_) => return Err(JsonError::not_of_type("type", JsonType::String)),
None => return Err(JsonError::field_missing_from_object("type")),
};
if let Some(content_value) = event.get_mut("content") {
let content = match content_value {
CanonicalJsonValue::Object(map) => map,
_ => return Err(JsonError::not_of_type("content", JsonType::Object)),
};
object_retain_keys(content, allowed_content_keys);
}
let mut old_event = mem::take(event);
for &key in ALLOWED_KEYS {
if let Some(value) = old_event.remove(key) {
event.insert(key.to_owned(), value);
}
}
Ok(())
}
/// Redacts event content using the rules specified in the Matrix client-server specification.
///
/// Edits the `object` in-place.
pub fn redact_content_in_place(
object: &mut CanonicalJsonObject,
version: &RoomVersionId,
event_type: impl AsRef<str>,
) {
object_retain_keys(object, allowed_content_keys_for(event_type.as_ref(), version));
}
fn object_retain_keys(object: &mut CanonicalJsonObject, keys: &[&str]) {
let mut old_content = mem::take(object);
for &key in keys {
if let Some(value) = old_content.remove(key) {
object.insert(key.to_owned(), value);
}
}
}
/// Extracts the server names to check signatures for given event.
///
/// It will return the sender's server (unless it's a third party invite) and the event id server