[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.
|
//! Canonical JSON types and related functions.
|
||||||
|
|
||||||
use std::fmt;
|
use std::{fmt, mem};
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::{Error as JsonError, Value as JsonValue};
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
|
use crate::RoomVersionId;
|
||||||
|
|
||||||
pub use self::value::{CanonicalJsonObject, CanonicalJsonValue};
|
pub use self::value::{CanonicalJsonObject, CanonicalJsonValue};
|
||||||
|
|
||||||
/// The set of possible errors when serializing to canonical JSON.
|
/// The set of possible errors when serializing to canonical JSON.
|
||||||
@ -18,7 +20,7 @@ pub enum CanonicalJsonError {
|
|||||||
IntConvert,
|
IntConvert,
|
||||||
|
|
||||||
/// An error occurred while serializing/deserializing.
|
/// An error occurred while serializing/deserializing.
|
||||||
SerDe(JsonError),
|
SerDe(serde_json::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for CanonicalJsonError {
|
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()
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
@ -23,67 +23,6 @@ use crate::{
|
|||||||
|
|
||||||
const MAX_PDU_BYTES: usize = 65_535;
|
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.
|
/// The fields to remove from a JSON object when converting JSON into the "canonical" form.
|
||||||
static CANONICAL_JSON_FIELDS_TO_REMOVE: &[&str] = &["signatures", "unsigned"];
|
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()))
|
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.
|
/// 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
|
/// 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