[WIP] Move redaction from ruma-signatures to ruma-common
This commit is contained in:
parent
402b2764fb
commit
940450b953
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user