ruma_common/
canonical_json.rs

1//! Canonical JSON types and related functions.
2
3use std::{fmt, mem};
4
5use serde::Serialize;
6use serde_json::Value as JsonValue;
7
8mod macros;
9mod serializer;
10mod value;
11
12pub use self::{
13    serializer::Serializer,
14    value::{CanonicalJsonObject, CanonicalJsonValue},
15};
16#[doc(inline)]
17pub use crate::assert_to_canonical_json_eq;
18use crate::{room_version_rules::RedactionRules, serde::Raw};
19
20/// The set of possible errors when serializing to canonical JSON.
21#[derive(Debug)]
22#[allow(clippy::exhaustive_enums)]
23pub enum CanonicalJsonError {
24    /// The integer value is out of the range of [`js_int::Int`].
25    IntegerOutOfRange,
26
27    /// The given type cannot be serialized to canonical JSON.
28    InvalidType(String),
29
30    /// The given type cannot be serialized to an object key.
31    InvalidObjectKeyType(String),
32
33    /// The same object key was serialized twice.
34    DuplicateObjectKey(String),
35
36    /// An error occurred while re-serializing a [`serde_json::value::RawValue`].
37    InvalidRawValue(serde_json::Error),
38
39    /// An other error happened.
40    Other(String),
41}
42
43impl fmt::Display for CanonicalJsonError {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        match self {
46            Self::IntegerOutOfRange => f.write_str("integer is out of the range of `js_int::Int`"),
47            Self::InvalidType(ty) => write!(f, "{ty} cannot be serialized as canonical JSON"),
48            Self::InvalidObjectKeyType(ty) => {
49                write!(f, "{ty} cannot be used as an object key, expected a string type")
50            }
51            Self::InvalidRawValue(error) => {
52                write!(f, "invalid raw value: {error}")
53            }
54            Self::DuplicateObjectKey(key) => write!(f, "duplicate object key `{key}`"),
55            Self::Other(msg) => f.write_str(msg),
56        }
57    }
58}
59
60impl std::error::Error for CanonicalJsonError {}
61
62impl serde::ser::Error for CanonicalJsonError {
63    fn custom<T>(msg: T) -> Self
64    where
65        T: fmt::Display,
66    {
67        Self::Other(msg.to_string())
68    }
69}
70
71/// Errors that can happen in redaction.
72#[derive(Debug)]
73#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
74pub enum RedactionError {
75    /// The field `field` is not of the correct type `of_type` ([`JsonType`]).
76    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
77    NotOfType {
78        /// The field name.
79        field: String,
80        /// The expected JSON type.
81        of_type: JsonType,
82    },
83
84    /// The given required field is missing from a JSON object.
85    JsonFieldMissingFromObject(String),
86}
87
88impl fmt::Display for RedactionError {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        match self {
91            RedactionError::NotOfType { field, of_type } => {
92                write!(f, "Value in {field:?} must be a JSON {of_type:?}")
93            }
94            RedactionError::JsonFieldMissingFromObject(field) => {
95                write!(f, "JSON object must contain the field {field:?}")
96            }
97        }
98    }
99}
100
101impl std::error::Error for RedactionError {}
102
103impl RedactionError {
104    fn not_of_type(target: &str, of_type: JsonType) -> Self {
105        Self::NotOfType { field: target.to_owned(), of_type }
106    }
107
108    fn field_missing_from_object(target: &str) -> Self {
109        Self::JsonFieldMissingFromObject(target.to_owned())
110    }
111}
112
113/// A JSON type enum for [`RedactionError`] variants.
114#[derive(Debug)]
115#[allow(clippy::exhaustive_enums)]
116pub enum JsonType {
117    /// A JSON Object.
118    Object,
119
120    /// A JSON String.
121    String,
122
123    /// A JSON Integer.
124    Integer,
125
126    /// A JSON Array.
127    Array,
128
129    /// A JSON Boolean.
130    Boolean,
131
132    /// JSON Null.
133    Null,
134}
135
136/// Fallible conversion from a `serde_json::Map` to a `CanonicalJsonObject`.
137pub fn try_from_json_map(
138    json: serde_json::Map<String, JsonValue>,
139) -> Result<CanonicalJsonObject, CanonicalJsonError> {
140    json.into_iter().map(|(k, v)| Ok((k, v.try_into()?))).collect()
141}
142
143/// Fallible conversion from any value that implements [`Serialize`] to a [`CanonicalJsonValue`].
144///
145/// This behaves similarly to [`serde_json::to_value()`], except for the following restrictions
146/// which return errors:
147///
148/// - Integers must be in the range accepted by [`js_int::Int`].
149/// - Floats and bytes are not serializable.
150/// - Booleans and integers cannot be used as keys for an object. `serde_json` accepts those types
151///   as keys by serializing them as strings.
152/// - The same key cannot be serialized twice in an object. `serde_json` uses the last value that is
153///   serialized for the same key.
154pub fn to_canonical_value<T: Serialize>(
155    value: T,
156) -> Result<CanonicalJsonValue, CanonicalJsonError> {
157    value.serialize(Serializer)
158}
159
160/// The value to put in `unsigned.redacted_because`.
161#[derive(Clone, Debug)]
162pub struct RedactedBecause(CanonicalJsonObject);
163
164impl RedactedBecause {
165    /// Create a `RedactedBecause` from an arbitrary JSON object.
166    pub fn from_json(obj: CanonicalJsonObject) -> Self {
167        Self(obj)
168    }
169
170    /// Create a `RedactedBecause` from a redaction event.
171    ///
172    /// Fails if the raw event is not valid canonical JSON.
173    pub fn from_raw_event(ev: &Raw<impl RedactionEvent>) -> serde_json::Result<Self> {
174        ev.deserialize_as_unchecked().map(Self)
175    }
176}
177
178/// Marker trait for redaction events.
179pub trait RedactionEvent {}
180
181/// Redacts an event using the rules specified in the Matrix client-server specification.
182///
183/// This is part of the process of signing an event.
184///
185/// Redaction is also suggested when verifying an event with `verify_event` returns
186/// `Verified::Signatures`. See the documentation for `Verified` for details.
187///
188/// Returns a new JSON object with all applicable fields redacted.
189///
190/// # Parameters
191///
192/// * `object`: A JSON object to redact.
193/// * `version`: The room version, determines which keys to keep for a few event types.
194/// * `redacted_because`: If this is set, an `unsigned` object with a `redacted_because` field set
195///   to the given value is added to the event after redaction.
196///
197/// # Errors
198///
199/// Returns an error if:
200///
201/// * `object` contains a field called `content` that is not a JSON object.
202/// * `object` contains a field called `hashes` that is not a JSON object.
203/// * `object` contains a field called `signatures` that is not a JSON object.
204/// * `object` is missing the `type` field or the field is not a JSON string.
205pub fn redact(
206    mut object: CanonicalJsonObject,
207    rules: &RedactionRules,
208    redacted_because: Option<RedactedBecause>,
209) -> Result<CanonicalJsonObject, RedactionError> {
210    redact_in_place(&mut object, rules, redacted_because)?;
211    Ok(object)
212}
213
214/// Redacts an event using the rules specified in the Matrix client-server specification.
215///
216/// Functionally equivalent to `redact`, only this'll redact the event in-place.
217pub fn redact_in_place(
218    event: &mut CanonicalJsonObject,
219    rules: &RedactionRules,
220    redacted_because: Option<RedactedBecause>,
221) -> Result<(), RedactionError> {
222    // Get the content keys here even if they're only needed inside the branch below, because we
223    // can't teach rust that this is a disjoint borrow with `get_mut("content")`.
224    let retained_event_content_keys = match event.get("type") {
225        Some(CanonicalJsonValue::String(event_type)) => {
226            retained_event_content_keys(event_type.as_ref(), rules)
227        }
228        Some(_) => return Err(RedactionError::not_of_type("type", JsonType::String)),
229        None => return Err(RedactionError::field_missing_from_object("type")),
230    };
231
232    if let Some(content_value) = event.get_mut("content") {
233        let CanonicalJsonValue::Object(content) = content_value else {
234            return Err(RedactionError::not_of_type("content", JsonType::Object));
235        };
236
237        retained_event_content_keys.apply(rules, content)?;
238    }
239
240    let retained_event_keys =
241        RetainedKeys::some(|rules, key, _value| Ok(is_event_key_retained(rules, key)));
242    retained_event_keys.apply(rules, event)?;
243
244    if let Some(redacted_because) = redacted_because {
245        let unsigned = CanonicalJsonObject::from_iter([(
246            "redacted_because".to_owned(),
247            redacted_because.0.into(),
248        )]);
249        event.insert("unsigned".to_owned(), unsigned.into());
250    }
251
252    Ok(())
253}
254
255/// Redacts the given event content using the given redaction rules for the version of the current
256/// room.
257///
258/// Edits the `content` in-place.
259pub fn redact_content_in_place(
260    content: &mut CanonicalJsonObject,
261    rules: &RedactionRules,
262    event_type: impl AsRef<str>,
263) -> Result<(), RedactionError> {
264    retained_event_content_keys(event_type.as_ref(), rules).apply(rules, content)
265}
266
267/// A function that takes redaction rules, a key and its value, and returns whether the field
268/// should be retained.
269type RetainKeyFn =
270    dyn Fn(&RedactionRules, &str, &mut CanonicalJsonValue) -> Result<bool, RedactionError>;
271
272/// Keys to retain on an object.
273enum RetainedKeys {
274    /// All keys are retained.
275    All,
276
277    /// Some keys are retained, they are determined by the inner function.
278    Some(Box<RetainKeyFn>),
279
280    /// No keys are retained.
281    None,
282}
283
284impl RetainedKeys {
285    /// Construct a `RetainedKeys::Some(_)` with the given function.
286    fn some<F>(retain_key_fn: F) -> Self
287    where
288        F: Fn(&RedactionRules, &str, &mut CanonicalJsonValue) -> Result<bool, RedactionError>
289            + 'static,
290    {
291        Self::Some(Box::new(retain_key_fn))
292    }
293
294    /// Apply this `RetainedKeys` on the given object.
295    fn apply(
296        &self,
297        rules: &RedactionRules,
298        object: &mut CanonicalJsonObject,
299    ) -> Result<(), RedactionError> {
300        match self {
301            Self::All => {}
302            Self::Some(allow_field_fn) => {
303                let old_object = mem::take(object);
304
305                for (key, mut value) in old_object {
306                    if allow_field_fn(rules, &key, &mut value)? {
307                        object.insert(key, value);
308                    }
309                }
310            }
311            Self::None => object.clear(),
312        }
313
314        Ok(())
315    }
316}
317
318/// Get the given keys should be retained at the top level of an event.
319fn is_event_key_retained(rules: &RedactionRules, key: &str) -> bool {
320    match key {
321        "event_id" | "type" | "room_id" | "sender" | "state_key" | "content" | "hashes"
322        | "signatures" | "depth" | "prev_events" | "auth_events" | "origin_server_ts" => true,
323        "origin" | "membership" | "prev_state" => rules.keep_origin_membership_prev_state,
324        _ => false,
325    }
326}
327
328/// Get the keys that should be retained in the `content` of an event with the given type.
329fn retained_event_content_keys(event_type: &str, rules: &RedactionRules) -> RetainedKeys {
330    match event_type {
331        "m.room.member" => RetainedKeys::some(is_room_member_content_key_retained),
332        "m.room.create" => room_create_content_retained_keys(rules),
333        "m.room.join_rules" => RetainedKeys::some(|rules, key, _value| {
334            is_room_join_rules_content_key_retained(rules, key)
335        }),
336        "m.room.power_levels" => RetainedKeys::some(|rules, key, _value| {
337            is_room_power_levels_content_key_retained(rules, key)
338        }),
339        "m.room.history_visibility" => RetainedKeys::some(|_rules, key, _value| {
340            is_room_history_visibility_content_key_retained(key)
341        }),
342        "m.room.redaction" => room_redaction_content_retained_keys(rules),
343        "m.room.aliases" => room_aliases_content_retained_keys(rules),
344        #[cfg(feature = "unstable-msc2870")]
345        "m.room.server_acl" => RetainedKeys::some(|rules, key, _value| {
346            is_room_server_acl_content_key_retained(rules, key)
347        }),
348        _ => RetainedKeys::None,
349    }
350}
351
352/// Whether the given key in the `content` of an `m.room.member` event is retained after redaction.
353fn is_room_member_content_key_retained(
354    rules: &RedactionRules,
355    key: &str,
356    value: &mut CanonicalJsonValue,
357) -> Result<bool, RedactionError> {
358    Ok(match key {
359        "membership" => true,
360        "join_authorised_via_users_server" => {
361            rules.keep_room_member_join_authorised_via_users_server
362        }
363        "third_party_invite" if rules.keep_room_member_third_party_invite_signed => {
364            let Some(third_party_invite) = value.as_object_mut() else {
365                return Err(RedactionError::not_of_type("third_party_invite", JsonType::Object));
366            };
367
368            third_party_invite.retain(|key, _| key == "signed");
369
370            // Keep the field only if it's not empty.
371            !third_party_invite.is_empty()
372        }
373        _ => false,
374    })
375}
376
377/// Get the retained keys in the `content` of an `m.room.create` event.
378fn room_create_content_retained_keys(rules: &RedactionRules) -> RetainedKeys {
379    if rules.keep_room_create_content {
380        RetainedKeys::All
381    } else {
382        RetainedKeys::some(|_rules, field, _value| Ok(field == "creator"))
383    }
384}
385
386/// Whether the given key in the `content` of an `m.room.join_rules` event is retained after
387/// redaction.
388fn is_room_join_rules_content_key_retained(
389    rules: &RedactionRules,
390    key: &str,
391) -> Result<bool, RedactionError> {
392    Ok(match key {
393        "join_rule" => true,
394        "allow" => rules.keep_room_join_rules_allow,
395        _ => false,
396    })
397}
398
399/// Whether the given key in the `content` of an `m.room.power_levels` event is retained after
400/// redaction.
401fn is_room_power_levels_content_key_retained(
402    rules: &RedactionRules,
403    key: &str,
404) -> Result<bool, RedactionError> {
405    Ok(match key {
406        "ban" | "events" | "events_default" | "kick" | "redact" | "state_default" | "users"
407        | "users_default" => true,
408        "invite" => rules.keep_room_power_levels_invite,
409        _ => false,
410    })
411}
412
413/// Whether the given key in the `content` of an `m.room.history_visibility` event is retained after
414/// redaction.
415fn is_room_history_visibility_content_key_retained(key: &str) -> Result<bool, RedactionError> {
416    Ok(key == "history_visibility")
417}
418
419/// Get the retained keys in the `content` of an `m.room.redaction` event.
420fn room_redaction_content_retained_keys(rules: &RedactionRules) -> RetainedKeys {
421    if rules.keep_room_redaction_redacts {
422        RetainedKeys::some(|_rules, field, _value| Ok(field == "redacts"))
423    } else {
424        RetainedKeys::None
425    }
426}
427
428/// Get the retained keys in the `content` of an `m.room.aliases` event.
429fn room_aliases_content_retained_keys(rules: &RedactionRules) -> RetainedKeys {
430    if rules.keep_room_aliases_aliases {
431        RetainedKeys::some(|_rules, field, _value| Ok(field == "aliases"))
432    } else {
433        RetainedKeys::None
434    }
435}
436
437/// Whether the given key in the `content` of an `m.room.server_acl` event is retained after
438/// redaction.
439#[cfg(feature = "unstable-msc2870")]
440fn is_room_server_acl_content_key_retained(
441    rules: &RedactionRules,
442    key: &str,
443) -> Result<bool, RedactionError> {
444    Ok(match key {
445        "allow" | "deny" | "allow_ip_literals" => {
446            rules.keep_room_server_acl_allow_deny_allow_ip_literals
447        }
448        _ => false,
449    })
450}
451
452#[cfg(test)]
453mod tests {
454    use std::collections::BTreeMap;
455
456    use assert_matches2::assert_matches;
457    use js_int::int;
458    use serde_json::{
459        from_str as from_json_str, json, to_string as to_json_string, to_value as to_json_value,
460        value::RawValue as RawJsonValue,
461    };
462
463    use super::{
464        CanonicalJsonError, assert_to_canonical_json_eq, redact_in_place, to_canonical_value,
465        try_from_json_map, value::CanonicalJsonValue,
466    };
467    use crate::room_version_rules::RedactionRules;
468
469    #[test]
470    fn serialize_canon() {
471        let json: CanonicalJsonValue = json!({
472            "a": [1, 2, 3],
473            "other": { "stuff": "hello" },
474            "string": "Thing"
475        })
476        .try_into()
477        .unwrap();
478
479        let ser = to_json_string(&json).unwrap();
480        let back = from_json_str::<CanonicalJsonValue>(&ser).unwrap();
481
482        assert_eq!(json, back);
483    }
484
485    #[test]
486    fn check_canonical_sorts_keys() {
487        let json: CanonicalJsonValue = json!({
488            "auth": {
489                "success": true,
490                "mxid": "@john.doe:example.com",
491                "profile": {
492                    "display_name": "John Doe",
493                    "three_pids": [
494                        {
495                            "medium": "email",
496                            "address": "john.doe@example.org"
497                        },
498                        {
499                            "medium": "msisdn",
500                            "address": "123456789"
501                        }
502                    ]
503                }
504            }
505        })
506        .try_into()
507        .unwrap();
508
509        assert_eq!(
510            to_json_string(&json).unwrap(),
511            r#"{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}}"#
512        );
513    }
514
515    #[test]
516    fn serialize_map_to_canonical() {
517        let mut expected = BTreeMap::new();
518        expected.insert("foo".into(), CanonicalJsonValue::String("string".into()));
519        expected.insert(
520            "bar".into(),
521            CanonicalJsonValue::Array(vec![
522                CanonicalJsonValue::Integer(int!(0)),
523                CanonicalJsonValue::Integer(int!(1)),
524                CanonicalJsonValue::Integer(int!(2)),
525            ]),
526        );
527
528        let mut map = serde_json::Map::new();
529        map.insert("foo".into(), json!("string"));
530        map.insert("bar".into(), json!(vec![0, 1, 2,]));
531
532        assert_eq!(try_from_json_map(map).unwrap(), expected);
533    }
534
535    #[test]
536    fn to_canonical_value_success() {
537        #[derive(Debug, serde::Serialize)]
538        struct MyStruct {
539            string: String,
540            array: Vec<u8>,
541            boolean: Option<bool>,
542            object: BTreeMap<String, MyEnum>,
543            null: (),
544            raw: Box<RawJsonValue>,
545        }
546
547        #[derive(Debug, serde::Serialize)]
548        enum MyEnum {
549            Foo,
550            #[serde(rename = "bar")]
551            Bar,
552        }
553
554        let t = MyStruct {
555            string: "string".into(),
556            array: vec![0, 1, 2],
557            boolean: Some(true),
558            object: [("foo".to_owned(), MyEnum::Foo), ("bar".to_owned(), MyEnum::Bar)].into(),
559            null: (),
560            raw: RawJsonValue::from_string(r#"{"baz":false}"#.to_owned()).unwrap(),
561        };
562
563        let mut expected = BTreeMap::new();
564        expected.insert("string".to_owned(), CanonicalJsonValue::String("string".to_owned()));
565        expected.insert(
566            "array".to_owned(),
567            CanonicalJsonValue::Array(vec![
568                CanonicalJsonValue::Integer(int!(0)),
569                CanonicalJsonValue::Integer(int!(1)),
570                CanonicalJsonValue::Integer(int!(2)),
571            ]),
572        );
573        expected.insert("boolean".to_owned(), CanonicalJsonValue::Bool(true));
574        let mut child_object = BTreeMap::new();
575        child_object.insert("foo".to_owned(), CanonicalJsonValue::String("Foo".to_owned()));
576        child_object.insert("bar".to_owned(), CanonicalJsonValue::String("bar".to_owned()));
577        expected.insert("object".to_owned(), CanonicalJsonValue::Object(child_object));
578        expected.insert("null".to_owned(), CanonicalJsonValue::Null);
579        let mut raw_object = BTreeMap::new();
580        raw_object.insert("baz".to_owned(), CanonicalJsonValue::Bool(false));
581        expected.insert("raw".to_owned(), CanonicalJsonValue::Object(raw_object));
582
583        let expected = CanonicalJsonValue::Object(expected);
584        assert_eq!(to_canonical_value(&t).unwrap(), expected);
585        assert_to_canonical_json_eq!(t, expected.into());
586    }
587
588    #[test]
589    fn to_canonical_value_out_of_range_int() {
590        #[derive(Debug, serde::Serialize)]
591        struct StructWithInt {
592            foo: i64,
593        }
594
595        let t = StructWithInt { foo: i64::MAX };
596        assert_matches!(to_canonical_value(t), Err(CanonicalJsonError::IntegerOutOfRange));
597    }
598
599    #[test]
600    fn to_canonical_value_invalid_type() {
601        #[derive(Debug, serde::Serialize)]
602        struct StructWithFloat {
603            foo: f32,
604        }
605
606        let t = StructWithFloat { foo: 10.0 };
607        assert_matches!(to_canonical_value(t), Err(CanonicalJsonError::InvalidType(_)));
608    }
609
610    #[test]
611    fn to_canonical_value_invalid_object_key_type() {
612        {
613            #[derive(Debug, serde::Serialize)]
614            struct StructWithBoolKey {
615                foo: BTreeMap<bool, String>,
616            }
617
618            let t = StructWithBoolKey { foo: [(true, "bar".to_owned())].into() };
619            assert_matches!(
620                to_canonical_value(t),
621                Err(CanonicalJsonError::InvalidObjectKeyType(_))
622            );
623        }
624
625        {
626            #[derive(Debug, serde::Serialize)]
627            struct StructWithIntKey {
628                foo: BTreeMap<i8, String>,
629            }
630
631            let t = StructWithIntKey { foo: [(4, "bar".to_owned())].into() };
632            assert_matches!(
633                to_canonical_value(t),
634                Err(CanonicalJsonError::InvalidObjectKeyType(_))
635            );
636        }
637
638        {
639            #[derive(Debug, serde::Serialize)]
640            struct StructWithUnitKey {
641                foo: BTreeMap<(), String>,
642            }
643
644            let t = StructWithUnitKey { foo: [((), "bar".to_owned())].into() };
645            assert_matches!(
646                to_canonical_value(t),
647                Err(CanonicalJsonError::InvalidObjectKeyType(_))
648            );
649        }
650
651        {
652            #[derive(Debug, serde::Serialize)]
653            struct StructWithTupleKey {
654                foo: BTreeMap<(String, String), bool>,
655            }
656
657            let t =
658                StructWithTupleKey { foo: [(("bar".to_owned(), "baz".to_owned()), false)].into() };
659            assert_matches!(
660                to_canonical_value(t),
661                Err(CanonicalJsonError::InvalidObjectKeyType(_))
662            );
663        }
664    }
665
666    #[test]
667    fn to_canonical_value_duplicate_object_key() {
668        #[derive(Debug, serde::Serialize)]
669        struct StructWithDuplicateKey {
670            foo: String,
671            #[serde(rename = "foo")]
672            bar: Vec<u8>,
673        }
674
675        let t = StructWithDuplicateKey { foo: "string".into(), bar: vec![0, 1, 2] };
676        assert_matches!(to_canonical_value(t), Err(CanonicalJsonError::DuplicateObjectKey(_)));
677    }
678
679    #[test]
680    fn redact_allowed_keys_some() {
681        let original_event = json!({
682            "content": {
683                "ban": 50,
684                "events": {
685                    "m.room.avatar": 50,
686                    "m.room.canonical_alias": 50,
687                    "m.room.history_visibility": 100,
688                    "m.room.name": 50,
689                    "m.room.power_levels": 100
690                },
691                "events_default": 0,
692                "invite": 0,
693                "kick": 50,
694                "redact": 50,
695                "state_default": 50,
696                "users": {
697                    "@example:localhost": 100
698                },
699                "users_default": 0
700            },
701            "event_id": "$15139375512JaHAW:localhost",
702            "origin_server_ts": 45,
703            "sender": "@example:localhost",
704            "room_id": "!room:localhost",
705            "state_key": "",
706            "type": "m.room.power_levels",
707            "unsigned": {
708                "age": 45
709            }
710        });
711
712        assert_matches!(
713            CanonicalJsonValue::try_from(original_event),
714            Ok(CanonicalJsonValue::Object(mut object))
715        );
716
717        redact_in_place(&mut object, &RedactionRules::V1, None).unwrap();
718
719        let expected = json!({
720            "content": {
721                "ban": 50,
722                "events": {
723                    "m.room.avatar": 50,
724                    "m.room.canonical_alias": 50,
725                    "m.room.history_visibility": 100,
726                    "m.room.name": 50,
727                    "m.room.power_levels": 100
728                },
729                "events_default": 0,
730                "kick": 50,
731                "redact": 50,
732                "state_default": 50,
733                "users": {
734                    "@example:localhost": 100
735                },
736                "users_default": 0
737            },
738            "event_id": "$15139375512JaHAW:localhost",
739            "origin_server_ts": 45,
740            "sender": "@example:localhost",
741            "room_id": "!room:localhost",
742            "state_key": "",
743            "type": "m.room.power_levels",
744        });
745
746        assert_eq!(to_json_value(&object).unwrap(), expected);
747        assert_to_canonical_json_eq!(object, expected);
748    }
749
750    #[test]
751    fn redact_allowed_keys_none() {
752        let original_event = json!({
753            "content": {
754                "aliases": ["#somewhere:localhost"]
755            },
756            "event_id": "$152037280074GZeOm:localhost",
757            "origin_server_ts": 1,
758            "sender": "@example:localhost",
759            "state_key": "room.com",
760            "room_id": "!room:room.com",
761            "type": "m.room.aliases",
762            "unsigned": {
763                "age": 1
764            }
765        });
766
767        assert_matches!(
768            CanonicalJsonValue::try_from(original_event),
769            Ok(CanonicalJsonValue::Object(mut object))
770        );
771
772        redact_in_place(&mut object, &RedactionRules::V9, None).unwrap();
773
774        let expected = json!({
775            "content": {},
776            "event_id": "$152037280074GZeOm:localhost",
777            "origin_server_ts": 1,
778            "sender": "@example:localhost",
779            "state_key": "room.com",
780            "room_id": "!room:room.com",
781            "type": "m.room.aliases",
782        });
783
784        assert_eq!(to_json_value(&object).unwrap(), expected);
785        assert_to_canonical_json_eq!(object, expected);
786    }
787
788    #[test]
789    fn redact_allowed_keys_all() {
790        let original_event = json!({
791            "content": {
792              "m.federate": true,
793              "predecessor": {
794                "event_id": "$something",
795                "room_id": "!oldroom:example.org"
796              },
797              "room_version": "11",
798            },
799            "event_id": "$143273582443PhrSn",
800            "origin_server_ts": 1_432_735,
801            "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
802            "sender": "@example:example.org",
803            "state_key": "",
804            "type": "m.room.create",
805            "unsigned": {
806              "age": 1234,
807            },
808        });
809
810        assert_matches!(
811            CanonicalJsonValue::try_from(original_event),
812            Ok(CanonicalJsonValue::Object(mut object))
813        );
814
815        redact_in_place(&mut object, &RedactionRules::V11, None).unwrap();
816
817        let expected = json!({
818            "content": {
819              "m.federate": true,
820              "predecessor": {
821                "event_id": "$something",
822                "room_id": "!oldroom:example.org"
823              },
824              "room_version": "11",
825            },
826            "event_id": "$143273582443PhrSn",
827            "origin_server_ts": 1_432_735,
828            "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
829            "sender": "@example:example.org",
830            "state_key": "",
831            "type": "m.room.create",
832        });
833
834        assert_eq!(to_json_value(&object).unwrap(), expected);
835        assert_to_canonical_json_eq!(object, expected);
836    }
837}