Skip to content

Commit 32d605f

Browse files
committed
Allow admins to see policy server-flagged events
1 parent 481c4e2 commit 32d605f

File tree

9 files changed

+85
-4
lines changed

9 files changed

+85
-4
lines changed

changelog.d/18585.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
When admins enable themselves to see soft-failed events, they will also see if the cause is due to the policy server flagging them as spam via `unsigned`.

docs/admin_api/client_server_api_extensions.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,30 @@ To receive soft failed events in APIs like `/sync` and `/messages`, set `return_
2222
to `true` in the admin client config. When `false`, the normal behaviour of these endpoints is to
2323
exclude soft failed events.
2424

25+
**Note**: If the policy server flagged the event as spam and that caused soft failure, that will be indicated
26+
in the event's `unsigned` content like so:
27+
28+
```json
29+
{
30+
"type": "m.room.message",
31+
"other": "event_fields_go_here",
32+
"unsigned": {
33+
"io.element.synapse.soft_failed": true,
34+
"io.element.synapse.policy_server_spammy": true
35+
}
36+
}
37+
```
38+
39+
Default: `false`
40+
41+
## See events marked spammy by policy servers
42+
43+
Learn more about policy servers from [MSC4284](https://github.com/matrix-org/matrix-spec-proposals/pull/4284).
44+
45+
Similar to `return_soft_failed_events`, clients logged in with admin accounts can see events which were
46+
flagged by the policy server as spammy (and thus soft failed) by setting `return_policy_server_spammy_events`
47+
to `true`. If `return_soft_failed_events` is `true`, then `return_policy_server_spammy_events` is implied
48+
`true`. When `false`, the normal behaviour of Client-Server API endpoints is retained (unless `return_soft_failed_events`
49+
is `true`, per above).
50+
2551
Default: `false`

rust/src/events/internal_metadata.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ enum EventInternalMetadataData {
5454
RecheckRedaction(bool),
5555
SoftFailed(bool),
5656
ProactivelySend(bool),
57+
PolicyServerSpammy(bool),
5758
Redacted(bool),
5859
TxnId(Box<str>),
5960
TokenId(i64),
@@ -96,6 +97,13 @@ impl EventInternalMetadataData {
9697
.to_owned()
9798
.into_any(),
9899
),
100+
EventInternalMetadataData::PolicyServerSpammy(o) => (
101+
pyo3::intern!(py, "policy_server_spammy"),
102+
o.into_pyobject(py)
103+
.unwrap_infallible()
104+
.to_owned()
105+
.into_any(),
106+
),
99107
EventInternalMetadataData::Redacted(o) => (
100108
pyo3::intern!(py, "redacted"),
101109
o.into_pyobject(py)
@@ -155,6 +163,11 @@ impl EventInternalMetadataData {
155163
.extract()
156164
.with_context(|| format!("'{key_str}' has invalid type"))?,
157165
),
166+
"policy_server_spammy" => EventInternalMetadataData::PolicyServerSpammy(
167+
value
168+
.extract()
169+
.with_context(|| format!("'{key_str}' has invalid type"))?,
170+
),
158171
"redacted" => EventInternalMetadataData::Redacted(
159172
value
160173
.extract()
@@ -427,6 +440,17 @@ impl EventInternalMetadata {
427440
set_property!(self, ProactivelySend, obj);
428441
}
429442

443+
#[getter]
444+
fn get_policy_server_spammy(&self) -> PyResult<bool> {
445+
Ok(get_property_opt!(self, PolicyServerSpammy)
446+
.copied()
447+
.unwrap_or(false))
448+
}
449+
#[setter]
450+
fn set_policy_server_spammy(&mut self, obj: bool) {
451+
set_property!(self, PolicyServerSpammy, obj);
452+
}
453+
430454
#[getter]
431455
fn get_redacted(&self) -> PyResult<bool> {
432456
let bool = get_property!(self, Redacted)?;

synapse/events/utils.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -538,8 +538,11 @@ def serialize_event(
538538
d["content"] = dict(d["content"])
539539
d["content"]["redacts"] = e.redacts
540540

541-
if config.include_admin_metadata and e.internal_metadata.is_soft_failed():
542-
d["unsigned"]["io.element.synapse.soft_failed"] = True
541+
if config.include_admin_metadata:
542+
if e.internal_metadata.is_soft_failed():
543+
d["unsigned"]["io.element.synapse.soft_failed"] = True
544+
if e.internal_metadata.policy_server_spammy:
545+
d["unsigned"]["io.element.synapse.policy_server_spammy"] = True
543546

544547
only_event_fields = config.only_event_fields
545548
if only_event_fields:

synapse/federation/federation_base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ async def _check_sigs_and_hash(
174174
"Event not allowed by policy server, soft-failing %s", pdu.event_id
175175
)
176176
pdu.internal_metadata.soft_failed = True
177+
pdu.internal_metadata.policy_server_spammy = True
177178
# Note: we don't redact the event so admins can inspect the event after the
178179
# fact. Other processes may redact the event, but that won't be applied to
179180
# the database copy of the event until the server's config requires it.

synapse/handlers/message.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,9 @@ async def _create_and_send_nonmember_event_locked(
11111111

11121112
policy_allowed = await self._policy_handler.is_event_allowed(event)
11131113
if not policy_allowed:
1114+
# We shouldn't need to set the metadata because the raise should
1115+
# cause the request to be denied, but just in case:
1116+
event.internal_metadata.policy_server_spammy = True
11141117
logger.warning(
11151118
"Event not allowed by policy server, rejecting %s",
11161119
event.event_id,

synapse/storage/admin_client_config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@ def __init__(self, account_data: Optional[JsonMapping]):
1515
# `unsigned` portion of the event to inform clients that the event
1616
# is soft-failed.
1717
self.return_soft_failed_events: bool = False
18+
self.return_policy_server_spammy_events: bool = False
1819

1920
if account_data:
2021
self.return_soft_failed_events = account_data.get(
2122
"return_soft_failed_events", False
2223
)
24+
self.return_policy_server_spammy_events = account_data.get(
25+
"return_policy_server_spammy_events", False
26+
)

synapse/synapse_rust/events.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ class EventInternalMetadata:
3333
proactively_send: bool
3434
redacted: bool
3535

36+
policy_server_spammy: bool
37+
"""whether the policy server indicated that this event is spammy"""
38+
3639
txn_id: str
3740
"""The transaction ID, if it was set when the event was created."""
3841
token_id: int

synapse/visibility.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,26 @@ async def filter_events_for_client(
120120
client_config = await storage.main.get_admin_client_config_for_user(user_id)
121121
if not (
122122
filter_send_to_client
123-
and client_config.return_soft_failed_events
123+
and (
124+
client_config.return_soft_failed_events
125+
or client_config.return_policy_server_spammy_events
126+
)
124127
and await storage.main.is_server_admin(UserID.from_string(user_id))
125128
):
126-
events = [e for e in events if not e.internal_metadata.is_soft_failed()]
129+
# `return_soft_failed_events` implies `return_policy_server_spammy_events`, so
130+
# we want to check when they've asked for *just* `return_policy_server_spammy_events`
131+
if not client_config.return_soft_failed_events:
132+
events = [
133+
e
134+
for e in events
135+
# Return non-soft-failed events as well as those explicitly marked
136+
# as spam by a policy server. This excludes events that were soft
137+
# failed by other means.
138+
if not e.internal_metadata.is_soft_failed()
139+
or e.internal_metadata.policy_server_spammy
140+
]
141+
else:
142+
events = [e for e in events if not e.internal_metadata.is_soft_failed()]
127143
if len(events_before_filtering) != len(events):
128144
if filtered_event_logger.isEnabledFor(logging.DEBUG):
129145
filtered_event_logger.debug(

0 commit comments

Comments
 (0)