1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
|
package pushrules
import (
"encoding/json"
"fmt"
"strings"
"github.com/matrix-org/gomatrixserverlib"
)
// A RuleSetEvaluator encapsulates context to evaluate an event
// against a rule set.
type RuleSetEvaluator struct {
ec EvaluationContext
ruleSet []kindAndRules
}
// An EvaluationContext gives a RuleSetEvaluator access to the
// environment, for rules that require that.
type EvaluationContext interface {
// UserDisplayName returns the current user's display name.
UserDisplayName() string
// RoomMemberCount returns the number of members in the room of
// the current event.
RoomMemberCount() (int, error)
// HasPowerLevel returns whether the user has at least the given
// power in the room of the current event.
HasPowerLevel(userID, levelKey string) (bool, error)
}
// A kindAndRules is just here to simplify iteration of the (ordered)
// kinds of rules.
type kindAndRules struct {
Kind Kind
Rules []*Rule
}
// NewRuleSetEvaluator creates a new evaluator for the given rule set.
func NewRuleSetEvaluator(ec EvaluationContext, ruleSet *RuleSet) *RuleSetEvaluator {
return &RuleSetEvaluator{
ec: ec,
ruleSet: []kindAndRules{
{OverrideKind, ruleSet.Override},
{ContentKind, ruleSet.Content},
{RoomKind, ruleSet.Room},
{SenderKind, ruleSet.Sender},
{UnderrideKind, ruleSet.Underride},
},
}
}
// MatchEvent returns the first matching rule. Returns nil if there
// was no match rule.
func (rse *RuleSetEvaluator) MatchEvent(event gomatrixserverlib.PDU) (*Rule, error) {
// TODO: server-default rules have lower priority than user rules,
// but they are stored together with the user rules. It's a bit
// unclear what the specification (11.14.1.4 Predefined rules)
// means the ordering should be.
//
// The most reasonable interpretation is that default overrides
// still have lower priority than user content rules, so we
// iterate twice.
for _, rsat := range rse.ruleSet {
for _, defRules := range []bool{false, true} {
for _, rule := range rsat.Rules {
if rule.Default != defRules {
continue
}
ok, err := ruleMatches(rule, rsat.Kind, event, rse.ec)
if err != nil {
return nil, err
}
if ok {
return rule, nil
}
}
}
}
// No matching rule.
return nil, nil
}
func ruleMatches(rule *Rule, kind Kind, event gomatrixserverlib.PDU, ec EvaluationContext) (bool, error) {
if !rule.Enabled {
return false, nil
}
switch kind {
case OverrideKind, UnderrideKind:
for _, cond := range rule.Conditions {
ok, err := conditionMatches(cond, event, ec)
if err != nil {
return false, err
}
if !ok {
return false, nil
}
}
return true, nil
case ContentKind:
// TODO: "These configure behaviour for (unencrypted) messages
// that match certain patterns." - Does that mean "content.body"?
if rule.Pattern == nil {
return false, nil
}
return patternMatches("content.body", *rule.Pattern, event)
case RoomKind:
return rule.RuleID == event.RoomID(), nil
case SenderKind:
return rule.RuleID == event.Sender(), nil
default:
return false, nil
}
}
func conditionMatches(cond *Condition, event gomatrixserverlib.PDU, ec EvaluationContext) (bool, error) {
switch cond.Kind {
case EventMatchCondition:
if cond.Pattern == nil {
return false, fmt.Errorf("missing condition pattern")
}
return patternMatches(cond.Key, *cond.Pattern, event)
case ContainsDisplayNameCondition:
return patternMatches("content.body", ec.UserDisplayName(), event)
case RoomMemberCountCondition:
cmp, err := parseRoomMemberCountCondition(cond.Is)
if err != nil {
return false, fmt.Errorf("parsing room_member_count condition: %w", err)
}
n, err := ec.RoomMemberCount()
if err != nil {
return false, fmt.Errorf("RoomMemberCount failed: %w", err)
}
return cmp(n), nil
case SenderNotificationPermissionCondition:
return ec.HasPowerLevel(event.Sender(), cond.Key)
default:
return false, nil
}
}
func patternMatches(key, pattern string, event gomatrixserverlib.PDU) (bool, error) {
// It doesn't make sense for an empty pattern to match anything.
if pattern == "" {
return false, nil
}
re, err := globToRegexp(pattern)
if err != nil {
return false, err
}
var eventMap map[string]interface{}
if err = json.Unmarshal(event.JSON(), &eventMap); err != nil {
return false, fmt.Errorf("parsing event: %w", err)
}
// From the spec:
// "If the property specified by key is completely absent from
// the event, or does not have a string value, then the condition
// will not match, even if pattern is *."
v, err := lookupMapPath(strings.Split(key, "."), eventMap)
if err != nil {
// An unknown path is a benign error that shouldn't stop rule
// processing. It's just a non-match.
return false, nil
}
if _, ok := v.(string); !ok {
// A non-string never matches.
return false, nil
}
return re.MatchString(fmt.Sprint(v)), nil
}
|