Internally the push rules module uses a `pattern_type` property for `event_match` conditions (and `related_event_match`) to mark the condition as matching the current user's Matrix ID or localpart. This is leaky to the Client-Server API where a user can successfully set a condition which provides `pattern_type` instead of `pattern` (note that there's no benefit to doing this -- the user can just use their own Matrix ID or localpart instead). When serializing back to the client the `pattern_type` property is converted into a proper `pattern`. The following changes are made to avoid this: * Separate the `KnownCondition::EventMatch` enum value into `EventMatch` and `EventMatchType`, each with their own expected properties. (Note that a similar change is made for `RelatedEventMatch`.) * Make it such that the `pattern_type` variants serialize to the same condition kind, but cannot be deserialized (since they're only provided by base rules). * As a final tweak, convert `user_id` vs. `user_localpart` values into an enum.tags/v1.79.0rc1
@@ -0,0 +1 @@ | |||
Fix a long-standing bug where Synapse handled an unspecced field on push rules. |
@@ -60,8 +60,7 @@ fn bench_match_exact(b: &mut Bencher) { | |||
let condition = Condition::Known(synapse::push::KnownCondition::EventMatch( | |||
EventMatchCondition { | |||
key: "room_id".into(), | |||
pattern: Some("!room:server".into()), | |||
pattern_type: None, | |||
pattern: "!room:server".into(), | |||
}, | |||
)); | |||
@@ -109,8 +108,7 @@ fn bench_match_word(b: &mut Bencher) { | |||
let condition = Condition::Known(synapse::push::KnownCondition::EventMatch( | |||
EventMatchCondition { | |||
key: "content.body".into(), | |||
pattern: Some("test".into()), | |||
pattern_type: None, | |||
pattern: "test".into(), | |||
}, | |||
)); | |||
@@ -158,8 +156,7 @@ fn bench_match_word_miss(b: &mut Bencher) { | |||
let condition = Condition::Known(synapse::push::KnownCondition::EventMatch( | |||
EventMatchCondition { | |||
key: "content.body".into(), | |||
pattern: Some("foobar".into()), | |||
pattern_type: None, | |||
pattern: "foobar".into(), | |||
}, | |||
)); | |||
@@ -21,13 +21,13 @@ use lazy_static::lazy_static; | |||
use serde_json::Value; | |||
use super::KnownCondition; | |||
use crate::push::Condition; | |||
use crate::push::EventMatchCondition; | |||
use crate::push::PushRule; | |||
use crate::push::RelatedEventMatchCondition; | |||
use crate::push::RelatedEventMatchTypeCondition; | |||
use crate::push::SetTweak; | |||
use crate::push::TweakValue; | |||
use crate::push::{Action, ExactEventMatchCondition, SimpleJsonValue}; | |||
use crate::push::{Condition, EventMatchTypeCondition}; | |||
use crate::push::{EventMatchCondition, EventMatchPatternType}; | |||
const HIGHLIGHT_ACTION: Action = Action::SetTweak(SetTweak { | |||
set_tweak: Cow::Borrowed("highlight"), | |||
@@ -72,8 +72,7 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[ | |||
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch( | |||
EventMatchCondition { | |||
key: Cow::Borrowed("content.m.relates_to.rel_type"), | |||
pattern: Some(Cow::Borrowed("m.replace")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.replace"), | |||
}, | |||
))]), | |||
actions: Cow::Borrowed(&[]), | |||
@@ -86,8 +85,7 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[ | |||
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch( | |||
EventMatchCondition { | |||
key: Cow::Borrowed("content.msgtype"), | |||
pattern: Some(Cow::Borrowed("m.notice")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.notice"), | |||
}, | |||
))]), | |||
actions: Cow::Borrowed(&[Action::DontNotify]), | |||
@@ -100,18 +98,15 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[ | |||
conditions: Cow::Borrowed(&[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
pattern: Some(Cow::Borrowed("m.room.member")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.room.member"), | |||
})), | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("content.membership"), | |||
pattern: Some(Cow::Borrowed("invite")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("invite"), | |||
})), | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
Condition::Known(KnownCondition::EventMatchType(EventMatchTypeCondition { | |||
key: Cow::Borrowed("state_key"), | |||
pattern: None, | |||
pattern_type: Some(Cow::Borrowed("user_id")), | |||
pattern_type: Cow::Borrowed(&EventMatchPatternType::UserId), | |||
})), | |||
]), | |||
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_FALSE_ACTION, SOUND_ACTION]), | |||
@@ -124,8 +119,7 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[ | |||
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch( | |||
EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
pattern: Some(Cow::Borrowed("m.room.member")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.room.member"), | |||
}, | |||
))]), | |||
actions: Cow::Borrowed(&[Action::DontNotify]), | |||
@@ -135,11 +129,10 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[ | |||
PushRule { | |||
rule_id: Cow::Borrowed("global/override/.im.nheko.msc3664.reply"), | |||
priority_class: 5, | |||
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::RelatedEventMatch( | |||
RelatedEventMatchCondition { | |||
key: Some(Cow::Borrowed("sender")), | |||
pattern: None, | |||
pattern_type: Some(Cow::Borrowed("user_id")), | |||
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::RelatedEventMatchType( | |||
RelatedEventMatchTypeCondition { | |||
key: Cow::Borrowed("sender"), | |||
pattern_type: Cow::Borrowed(&EventMatchPatternType::UserId), | |||
rel_type: Cow::Borrowed("m.in_reply_to"), | |||
include_fallbacks: None, | |||
}, | |||
@@ -189,8 +182,7 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[ | |||
}), | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("content.body"), | |||
pattern: Some(Cow::Borrowed("@room")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("@room"), | |||
})), | |||
]), | |||
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_ACTION]), | |||
@@ -203,13 +195,11 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[ | |||
conditions: Cow::Borrowed(&[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
pattern: Some(Cow::Borrowed("m.room.tombstone")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.room.tombstone"), | |||
})), | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("state_key"), | |||
pattern: Some(Cow::Borrowed("")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed(""), | |||
})), | |||
]), | |||
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_ACTION]), | |||
@@ -222,8 +212,7 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[ | |||
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch( | |||
EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
pattern: Some(Cow::Borrowed("m.reaction")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.reaction"), | |||
}, | |||
))]), | |||
actions: Cow::Borrowed(&[]), | |||
@@ -236,13 +225,11 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[ | |||
conditions: Cow::Borrowed(&[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
pattern: Some(Cow::Borrowed("m.room.server_acl")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.room.server_acl"), | |||
})), | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("state_key"), | |||
pattern: Some(Cow::Borrowed("")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed(""), | |||
})), | |||
]), | |||
actions: Cow::Borrowed(&[]), | |||
@@ -255,8 +242,7 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[ | |||
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch( | |||
EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
pattern: Some(Cow::Borrowed("org.matrix.msc3381.poll.response")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("org.matrix.msc3381.poll.response"), | |||
}, | |||
))]), | |||
actions: Cow::Borrowed(&[]), | |||
@@ -268,11 +254,10 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[ | |||
pub const BASE_APPEND_CONTENT_RULES: &[PushRule] = &[PushRule { | |||
rule_id: Cow::Borrowed("global/content/.m.rule.contains_user_name"), | |||
priority_class: 4, | |||
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch( | |||
EventMatchCondition { | |||
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatchType( | |||
EventMatchTypeCondition { | |||
key: Cow::Borrowed("content.body"), | |||
pattern: None, | |||
pattern_type: Some(Cow::Borrowed("user_localpart")), | |||
pattern_type: Cow::Borrowed(&EventMatchPatternType::UserLocalpart), | |||
}, | |||
))]), | |||
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_ACTION, SOUND_ACTION]), | |||
@@ -287,8 +272,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch( | |||
EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
pattern: Some(Cow::Borrowed("m.call.invite")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.call.invite"), | |||
}, | |||
))]), | |||
actions: Cow::Borrowed(&[Action::Notify, RING_ACTION, HIGHLIGHT_FALSE_ACTION]), | |||
@@ -301,8 +285,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
conditions: Cow::Borrowed(&[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
pattern: Some(Cow::Borrowed("m.room.message")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.room.message"), | |||
})), | |||
Condition::Known(KnownCondition::RoomMemberCount { | |||
is: Some(Cow::Borrowed("2")), | |||
@@ -318,8 +301,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
conditions: Cow::Borrowed(&[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
pattern: Some(Cow::Borrowed("m.room.encrypted")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.room.encrypted"), | |||
})), | |||
Condition::Known(KnownCondition::RoomMemberCount { | |||
is: Some(Cow::Borrowed("2")), | |||
@@ -338,8 +320,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
// MSC3933: Type changed from template rule - see MSC. | |||
pattern: Some(Cow::Borrowed("org.matrix.msc1767.encrypted")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("org.matrix.msc1767.encrypted"), | |||
})), | |||
Condition::Known(KnownCondition::RoomMemberCount { | |||
is: Some(Cow::Borrowed("2")), | |||
@@ -363,8 +344,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
// MSC3933: Type changed from template rule - see MSC. | |||
pattern: Some(Cow::Borrowed("org.matrix.msc1767.message")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("org.matrix.msc1767.message"), | |||
})), | |||
Condition::Known(KnownCondition::RoomMemberCount { | |||
is: Some(Cow::Borrowed("2")), | |||
@@ -388,8 +368,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
// MSC3933: Type changed from template rule - see MSC. | |||
pattern: Some(Cow::Borrowed("org.matrix.msc1767.file")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("org.matrix.msc1767.file"), | |||
})), | |||
Condition::Known(KnownCondition::RoomMemberCount { | |||
is: Some(Cow::Borrowed("2")), | |||
@@ -413,8 +392,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
// MSC3933: Type changed from template rule - see MSC. | |||
pattern: Some(Cow::Borrowed("org.matrix.msc1767.image")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("org.matrix.msc1767.image"), | |||
})), | |||
Condition::Known(KnownCondition::RoomMemberCount { | |||
is: Some(Cow::Borrowed("2")), | |||
@@ -438,8 +416,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
// MSC3933: Type changed from template rule - see MSC. | |||
pattern: Some(Cow::Borrowed("org.matrix.msc1767.video")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("org.matrix.msc1767.video"), | |||
})), | |||
Condition::Known(KnownCondition::RoomMemberCount { | |||
is: Some(Cow::Borrowed("2")), | |||
@@ -463,8 +440,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
// MSC3933: Type changed from template rule - see MSC. | |||
pattern: Some(Cow::Borrowed("org.matrix.msc1767.audio")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("org.matrix.msc1767.audio"), | |||
})), | |||
Condition::Known(KnownCondition::RoomMemberCount { | |||
is: Some(Cow::Borrowed("2")), | |||
@@ -485,8 +461,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch( | |||
EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
pattern: Some(Cow::Borrowed("m.room.message")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.room.message"), | |||
}, | |||
))]), | |||
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_FALSE_ACTION]), | |||
@@ -499,8 +474,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch( | |||
EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
pattern: Some(Cow::Borrowed("m.room.encrypted")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.room.encrypted"), | |||
}, | |||
))]), | |||
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_FALSE_ACTION]), | |||
@@ -514,8 +488,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
// MSC3933: Type changed from template rule - see MSC. | |||
pattern: Some(Cow::Borrowed("m.encrypted")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.encrypted"), | |||
})), | |||
// MSC3933: Add condition on top of template rule - see MSC. | |||
Condition::Known(KnownCondition::RoomVersionSupports { | |||
@@ -534,8 +507,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
// MSC3933: Type changed from template rule - see MSC. | |||
pattern: Some(Cow::Borrowed("m.message")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.message"), | |||
})), | |||
// MSC3933: Add condition on top of template rule - see MSC. | |||
Condition::Known(KnownCondition::RoomVersionSupports { | |||
@@ -554,8 +526,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
// MSC3933: Type changed from template rule - see MSC. | |||
pattern: Some(Cow::Borrowed("m.file")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.file"), | |||
})), | |||
// MSC3933: Add condition on top of template rule - see MSC. | |||
Condition::Known(KnownCondition::RoomVersionSupports { | |||
@@ -574,8 +545,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
// MSC3933: Type changed from template rule - see MSC. | |||
pattern: Some(Cow::Borrowed("m.image")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.image"), | |||
})), | |||
// MSC3933: Add condition on top of template rule - see MSC. | |||
Condition::Known(KnownCondition::RoomVersionSupports { | |||
@@ -594,8 +564,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
// MSC3933: Type changed from template rule - see MSC. | |||
pattern: Some(Cow::Borrowed("m.video")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.video"), | |||
})), | |||
// MSC3933: Add condition on top of template rule - see MSC. | |||
Condition::Known(KnownCondition::RoomVersionSupports { | |||
@@ -614,8 +583,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
// MSC3933: Type changed from template rule - see MSC. | |||
pattern: Some(Cow::Borrowed("m.audio")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("m.audio"), | |||
})), | |||
// MSC3933: Add condition on top of template rule - see MSC. | |||
Condition::Known(KnownCondition::RoomVersionSupports { | |||
@@ -633,18 +601,15 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
conditions: Cow::Borrowed(&[ | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
pattern: Some(Cow::Borrowed("im.vector.modular.widgets")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("im.vector.modular.widgets"), | |||
})), | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("content.type"), | |||
pattern: Some(Cow::Borrowed("jitsi")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("jitsi"), | |||
})), | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("state_key"), | |||
pattern: Some(Cow::Borrowed("*")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("*"), | |||
})), | |||
]), | |||
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_FALSE_ACTION]), | |||
@@ -660,8 +625,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
}), | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
pattern: Some(Cow::Borrowed("org.matrix.msc3381.poll.start")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("org.matrix.msc3381.poll.start"), | |||
})), | |||
]), | |||
actions: Cow::Borrowed(&[Action::Notify, SOUND_ACTION]), | |||
@@ -674,8 +638,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch( | |||
EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
pattern: Some(Cow::Borrowed("org.matrix.msc3381.poll.start")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("org.matrix.msc3381.poll.start"), | |||
}, | |||
))]), | |||
actions: Cow::Borrowed(&[Action::Notify]), | |||
@@ -691,8 +654,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
}), | |||
Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
pattern: Some(Cow::Borrowed("org.matrix.msc3381.poll.end")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("org.matrix.msc3381.poll.end"), | |||
})), | |||
]), | |||
actions: Cow::Borrowed(&[Action::Notify, SOUND_ACTION]), | |||
@@ -705,8 +667,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[ | |||
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch( | |||
EventMatchCondition { | |||
key: Cow::Borrowed("type"), | |||
pattern: Some(Cow::Borrowed("org.matrix.msc3381.poll.end")), | |||
pattern_type: None, | |||
pattern: Cow::Borrowed("org.matrix.msc3381.poll.end"), | |||
}, | |||
))]), | |||
actions: Cow::Borrowed(&[Action::Notify]), | |||
@@ -12,9 +12,10 @@ | |||
// See the License for the specific language governing permissions and | |||
// limitations under the License. | |||
use std::borrow::Cow; | |||
use std::collections::{BTreeMap, BTreeSet}; | |||
use crate::push::JsonValue; | |||
use crate::push::{EventMatchPatternType, JsonValue}; | |||
use anyhow::{Context, Error}; | |||
use lazy_static::lazy_static; | |||
use log::warn; | |||
@@ -23,8 +24,8 @@ use regex::Regex; | |||
use super::{ | |||
utils::{get_glob_matcher, get_localpart_from_id, GlobMatchType}, | |||
Action, Condition, EventMatchCondition, ExactEventMatchCondition, FilteredPushRules, | |||
KnownCondition, RelatedEventMatchCondition, SimpleJsonValue, | |||
Action, Condition, ExactEventMatchCondition, FilteredPushRules, KnownCondition, | |||
SimpleJsonValue, | |||
}; | |||
lazy_static! { | |||
@@ -256,14 +257,58 @@ impl PushRuleEvaluator { | |||
}; | |||
let result = match known_condition { | |||
KnownCondition::EventMatch(event_match) => { | |||
self.match_event_match(event_match, user_id)? | |||
KnownCondition::EventMatch(event_match) => self.match_event_match( | |||
&self.flattened_keys, | |||
&event_match.key, | |||
&event_match.pattern, | |||
)?, | |||
KnownCondition::EventMatchType(event_match) => { | |||
// The `pattern_type` can either be "user_id" or "user_localpart", | |||
// either way if we don't have a `user_id` then the condition can't | |||
// match. | |||
let user_id = if let Some(user_id) = user_id { | |||
user_id | |||
} else { | |||
return Ok(false); | |||
}; | |||
let pattern = match &*event_match.pattern_type { | |||
EventMatchPatternType::UserId => user_id, | |||
EventMatchPatternType::UserLocalpart => get_localpart_from_id(user_id)?, | |||
}; | |||
self.match_event_match(&self.flattened_keys, &event_match.key, pattern)? | |||
} | |||
KnownCondition::ExactEventMatch(exact_event_match) => { | |||
self.match_exact_event_match(exact_event_match)? | |||
} | |||
KnownCondition::RelatedEventMatch(event_match) => { | |||
self.match_related_event_match(event_match, user_id)? | |||
KnownCondition::RelatedEventMatch(event_match) => self.match_related_event_match( | |||
&event_match.rel_type.clone(), | |||
event_match.include_fallbacks, | |||
event_match.key.clone(), | |||
event_match.pattern.clone(), | |||
)?, | |||
KnownCondition::RelatedEventMatchType(event_match) => { | |||
// The `pattern_type` can either be "user_id" or "user_localpart", | |||
// either way if we don't have a `user_id` then the condition can't | |||
// match. | |||
let user_id = if let Some(user_id) = user_id { | |||
user_id | |||
} else { | |||
return Ok(false); | |||
}; | |||
let pattern = match &*event_match.pattern_type { | |||
EventMatchPatternType::UserId => user_id, | |||
EventMatchPatternType::UserLocalpart => get_localpart_from_id(user_id)?, | |||
}; | |||
self.match_related_event_match( | |||
&event_match.rel_type.clone(), | |||
event_match.include_fallbacks, | |||
Some(event_match.key.clone()), | |||
Some(Cow::Borrowed(pattern)), | |||
)? | |||
} | |||
KnownCondition::ExactEventPropertyContains(exact_event_match) => { | |||
self.match_exact_event_property_contains(exact_event_match)? | |||
@@ -325,32 +370,12 @@ impl PushRuleEvaluator { | |||
/// Evaluates a `event_match` condition. | |||
fn match_event_match( | |||
&self, | |||
event_match: &EventMatchCondition, | |||
user_id: Option<&str>, | |||
flattened_event: &BTreeMap<String, JsonValue>, | |||
key: &str, | |||
pattern: &str, | |||
) -> Result<bool, Error> { | |||
let pattern = if let Some(pattern) = &event_match.pattern { | |||
pattern | |||
} else if let Some(pattern_type) = &event_match.pattern_type { | |||
// The `pattern_type` can either be "user_id" or "user_localpart", | |||
// either way if we don't have a `user_id` then the condition can't | |||
// match. | |||
let user_id = if let Some(user_id) = user_id { | |||
user_id | |||
} else { | |||
return Ok(false); | |||
}; | |||
match &**pattern_type { | |||
"user_id" => user_id, | |||
"user_localpart" => get_localpart_from_id(user_id)?, | |||
_ => return Ok(false), | |||
} | |||
} else { | |||
return Ok(false); | |||
}; | |||
let haystack = if let Some(JsonValue::Value(SimpleJsonValue::Str(haystack))) = | |||
self.flattened_keys.get(&*event_match.key) | |||
flattened_event.get(key) | |||
{ | |||
haystack | |||
} else { | |||
@@ -359,7 +384,7 @@ impl PushRuleEvaluator { | |||
// For the content.body we match against "words", but for everything | |||
// else we match against the entire value. | |||
let match_type = if event_match.key == "content.body" { | |||
let match_type = if key == "content.body" { | |||
GlobMatchType::Word | |||
} else { | |||
GlobMatchType::Whole | |||
@@ -395,8 +420,10 @@ impl PushRuleEvaluator { | |||
/// Evaluates a `related_event_match` condition. (MSC3664) | |||
fn match_related_event_match( | |||
&self, | |||
event_match: &RelatedEventMatchCondition, | |||
user_id: Option<&str>, | |||
rel_type: &str, | |||
include_fallbacks: Option<bool>, | |||
key: Option<Cow<str>>, | |||
pattern: Option<Cow<str>>, | |||
) -> Result<bool, Error> { | |||
// First check if related event matching is enabled... | |||
if !self.related_event_match_enabled { | |||
@@ -404,7 +431,7 @@ impl PushRuleEvaluator { | |||
} | |||
// get the related event, fail if there is none. | |||
let event = if let Some(event) = self.related_events_flattened.get(&*event_match.rel_type) { | |||
let event = if let Some(event) = self.related_events_flattened.get(rel_type) { | |||
event | |||
} else { | |||
return Ok(false); | |||
@@ -412,58 +439,18 @@ impl PushRuleEvaluator { | |||
// If we are not matching fallbacks, don't match if our special key indicating this is a | |||
// fallback relation is not present. | |||
if !event_match.include_fallbacks.unwrap_or(false) | |||
&& event.contains_key("im.vector.is_falling_back") | |||
{ | |||
if !include_fallbacks.unwrap_or(false) && event.contains_key("im.vector.is_falling_back") { | |||
return Ok(false); | |||
} | |||
// if we have no key, accept the event as matching, if it existed without matching any | |||
// fields. | |||
let key = if let Some(key) = &event_match.key { | |||
key | |||
} else { | |||
return Ok(true); | |||
}; | |||
let pattern = if let Some(pattern) = &event_match.pattern { | |||
pattern | |||
} else if let Some(pattern_type) = &event_match.pattern_type { | |||
// The `pattern_type` can either be "user_id" or "user_localpart", | |||
// either way if we don't have a `user_id` then the condition can't | |||
// match. | |||
let user_id = if let Some(user_id) = user_id { | |||
user_id | |||
} else { | |||
return Ok(false); | |||
}; | |||
match &**pattern_type { | |||
"user_id" => user_id, | |||
"user_localpart" => get_localpart_from_id(user_id)?, | |||
_ => return Ok(false), | |||
} | |||
} else { | |||
return Ok(false); | |||
}; | |||
let haystack = | |||
if let Some(JsonValue::Value(SimpleJsonValue::Str(haystack))) = event.get(&**key) { | |||
haystack | |||
} else { | |||
return Ok(false); | |||
}; | |||
// For the content.body we match against "words", but for everything | |||
// else we match against the entire value. | |||
let match_type = if key == "content.body" { | |||
GlobMatchType::Word | |||
} else { | |||
GlobMatchType::Whole | |||
}; | |||
let mut compiled_pattern = get_glob_matcher(pattern, match_type)?; | |||
compiled_pattern.is_match(haystack) | |||
match (key, pattern) { | |||
// if we have no key, accept the event as matching. | |||
(None, _) => Ok(true), | |||
// There was a key, so we *must* have a pattern to go with it. | |||
(Some(_), None) => Ok(false), | |||
// If there is a key & pattern, check if they're in the flattened event (given by rel_type). | |||
(Some(key), Some(pattern)) => self.match_event_match(event, &key, &pattern), | |||
} | |||
} | |||
/// Evaluates a `exact_event_property_contains` condition. (MSC3758) | |||
@@ -328,10 +328,16 @@ pub enum Condition { | |||
#[serde(tag = "kind")] | |||
pub enum KnownCondition { | |||
EventMatch(EventMatchCondition), | |||
// Identical to event_match but gives predefined patterns. Cannot be added by users. | |||
#[serde(skip_deserializing, rename = "event_match")] | |||
EventMatchType(EventMatchTypeCondition), | |||
#[serde(rename = "com.beeper.msc3758.exact_event_match")] | |||
ExactEventMatch(ExactEventMatchCondition), | |||
#[serde(rename = "im.nheko.msc3664.related_event_match")] | |||
RelatedEventMatch(RelatedEventMatchCondition), | |||
// Identical to related_event_match but gives predefined patterns. Cannot be added by users. | |||
#[serde(skip_deserializing, rename = "im.nheko.msc3664.related_event_match")] | |||
RelatedEventMatchType(RelatedEventMatchTypeCondition), | |||
#[serde(rename = "org.matrix.msc3966.exact_event_property_contains")] | |||
ExactEventPropertyContains(ExactEventMatchCondition), | |||
#[serde(rename = "org.matrix.msc3952.is_user_mention")] | |||
@@ -362,14 +368,27 @@ impl<'source> FromPyObject<'source> for Condition { | |||
} | |||
} | |||
/// The body of a [`Condition::EventMatch`] | |||
/// The body of a [`Condition::EventMatch`] with a pattern. | |||
#[derive(Serialize, Deserialize, Debug, Clone)] | |||
pub struct EventMatchCondition { | |||
pub key: Cow<'static, str>, | |||
#[serde(skip_serializing_if = "Option::is_none")] | |||
pub pattern: Option<Cow<'static, str>>, | |||
#[serde(skip_serializing_if = "Option::is_none")] | |||
pub pattern_type: Option<Cow<'static, str>>, | |||
pub pattern: Cow<'static, str>, | |||
} | |||
#[derive(Serialize, Debug, Clone)] | |||
#[serde(rename_all = "snake_case")] | |||
pub enum EventMatchPatternType { | |||
UserId, | |||
UserLocalpart, | |||
} | |||
/// The body of a [`Condition::EventMatch`] that uses user_id or user_localpart as a pattern. | |||
#[derive(Serialize, Debug, Clone)] | |||
pub struct EventMatchTypeCondition { | |||
pub key: Cow<'static, str>, | |||
// During serialization, the pattern_type property gets replaced with a | |||
// pattern property of the correct value in synapse.push.clientformat.format_push_rules_for_user. | |||
pub pattern_type: Cow<'static, EventMatchPatternType>, | |||
} | |||
/// The body of a [`Condition::ExactEventMatch`] | |||
@@ -386,8 +405,18 @@ pub struct RelatedEventMatchCondition { | |||
pub key: Option<Cow<'static, str>>, | |||
#[serde(skip_serializing_if = "Option::is_none")] | |||
pub pattern: Option<Cow<'static, str>>, | |||
pub rel_type: Cow<'static, str>, | |||
#[serde(skip_serializing_if = "Option::is_none")] | |||
pub pattern_type: Option<Cow<'static, str>>, | |||
pub include_fallbacks: Option<bool>, | |||
} | |||
/// The body of a [`Condition::RelatedEventMatch`] that uses user_id or user_localpart as a pattern. | |||
#[derive(Serialize, Debug, Clone)] | |||
pub struct RelatedEventMatchTypeCondition { | |||
// This is only used if pattern_type exists (and thus key must exist), so is | |||
// a bit simpler than RelatedEventMatchCondition. | |||
pub key: Cow<'static, str>, | |||
pub pattern_type: Cow<'static, EventMatchPatternType>, | |||
pub rel_type: Cow<'static, str>, | |||
#[serde(skip_serializing_if = "Option::is_none")] | |||
pub include_fallbacks: Option<bool>, | |||
@@ -571,8 +600,7 @@ impl FilteredPushRules { | |||
fn test_serialize_condition() { | |||
let condition = Condition::Known(KnownCondition::EventMatch(EventMatchCondition { | |||
key: "content.body".into(), | |||
pattern: Some("coffee".into()), | |||
pattern_type: None, | |||
pattern: "coffee".into(), | |||
})); | |||
let json = serde_json::to_string(&condition).unwrap(); | |||
@@ -586,7 +614,33 @@ fn test_serialize_condition() { | |||
fn test_deserialize_condition() { | |||
let json = r#"{"kind":"event_match","key":"content.body","pattern":"coffee"}"#; | |||
let _: Condition = serde_json::from_str(json).unwrap(); | |||
let condition: Condition = serde_json::from_str(json).unwrap(); | |||
assert!(matches!( | |||
condition, | |||
Condition::Known(KnownCondition::EventMatch(_)) | |||
)); | |||
} | |||
#[test] | |||
fn test_serialize_event_match_condition_with_pattern_type() { | |||
let condition = Condition::Known(KnownCondition::EventMatchType(EventMatchTypeCondition { | |||
key: "content.body".into(), | |||
pattern_type: Cow::Owned(EventMatchPatternType::UserId), | |||
})); | |||
let json = serde_json::to_string(&condition).unwrap(); | |||
assert_eq!( | |||
json, | |||
r#"{"kind":"event_match","key":"content.body","pattern_type":"user_id"}"# | |||
) | |||
} | |||
#[test] | |||
fn test_cannot_deserialize_event_match_condition_with_pattern_type() { | |||
let json = r#"{"kind":"event_match","key":"content.body","pattern_type":"user_id"}"#; | |||
let condition: Condition = serde_json::from_str(json).unwrap(); | |||
assert!(matches!(condition, Condition::Unknown(_))); | |||
} | |||
#[test] | |||
@@ -600,6 +654,37 @@ fn test_deserialize_unstable_msc3664_condition() { | |||
)); | |||
} | |||
#[test] | |||
fn test_serialize_unstable_msc3664_condition_with_pattern_type() { | |||
let condition = Condition::Known(KnownCondition::RelatedEventMatchType( | |||
RelatedEventMatchTypeCondition { | |||
key: "content.body".into(), | |||
pattern_type: Cow::Owned(EventMatchPatternType::UserId), | |||
rel_type: "m.in_reply_to".into(), | |||
include_fallbacks: Some(true), | |||
}, | |||
)); | |||
let json = serde_json::to_string(&condition).unwrap(); | |||
assert_eq!( | |||
json, | |||
r#"{"kind":"im.nheko.msc3664.related_event_match","key":"content.body","pattern_type":"user_id","rel_type":"m.in_reply_to","include_fallbacks":true}"# | |||
) | |||
} | |||
#[test] | |||
fn test_cannot_deserialize_unstable_msc3664_condition_with_pattern_type() { | |||
let json = r#"{"kind":"im.nheko.msc3664.related_event_match","key":"content.body","pattern_type":"user_id","rel_type":"m.in_reply_to"}"#; | |||
let condition: Condition = serde_json::from_str(json).unwrap(); | |||
// Since pattern is optional on RelatedEventMatch it deserializes it to that | |||
// instead of RelatedEventMatchType. | |||
assert!(matches!( | |||
condition, | |||
Condition::Known(KnownCondition::RelatedEventMatch(_)) | |||
)); | |||
} | |||
#[test] | |||
fn test_deserialize_unstable_msc3931_condition() { | |||
let json = | |||
@@ -401,6 +401,33 @@ class PushRuleEvaluatorTestCase(unittest.TestCase): | |||
"pattern should not match before a newline", | |||
) | |||
def test_event_match_pattern(self) -> None: | |||
"""Check that event_match conditions do not use a "pattern_type" from user data.""" | |||
# The pattern_type should not be deserialized into anything valid. | |||
condition = { | |||
"kind": "event_match", | |||
"key": "content.value", | |||
"pattern_type": "user_id", | |||
} | |||
self._assert_not_matches( | |||
condition, | |||
{"value": "@user:test"}, | |||
"should not be possible to pass a pattern_type in", | |||
) | |||
# This is an internal-only condition which shouldn't get deserialized. | |||
condition = { | |||
"kind": "event_match_type", | |||
"key": "content.value", | |||
"pattern_type": "user_id", | |||
} | |||
self._assert_not_matches( | |||
condition, | |||
{"value": "@user:test"}, | |||
"should not be possible to pass a pattern_type in", | |||
) | |||
def test_exact_event_match_string(self) -> None: | |||
"""Check that exact_event_match conditions work as expected for strings.""" | |||