mirror of
https://github.com/element-hq/synapse.git
synced 2025-07-04 00:00:27 -04:00
Compare commits
42 Commits
7e09a4d233
...
1a44634122
Author | SHA1 | Date | |
---|---|---|---|
|
1a44634122 | ||
|
e3430769b2 | ||
|
1bee9501d8 | ||
|
91595cf780 | ||
|
a1a05b3c3a | ||
|
dac68a3bdd | ||
|
bb9922f50f | ||
|
0408d5ec36 | ||
|
2fb8ce71a6 | ||
|
6e79df0b9e | ||
|
f7c089c93f | ||
|
35993c6110 | ||
|
c3f2b94c42 | ||
|
881d521532 | ||
|
61ba9018b1 | ||
|
5c4ba56e2b | ||
|
bcf3a8925d | ||
|
7e8e66e6d6 | ||
|
37a38f8a53 | ||
|
25ef8dd1e6 | ||
|
b38c208d5d | ||
|
280094382e | ||
|
1c7e7f108a | ||
|
88b4723ca7 | ||
|
b2e4a63439 | ||
|
f967bbb521 | ||
|
7344990e22 | ||
|
1262b10b4c | ||
|
8908312875 | ||
|
99b9ee27e9 | ||
|
043bd86d7d | ||
|
38a8937b27 | ||
|
f24386ad2c | ||
|
24c809f27b | ||
|
efa5ad9fa3 | ||
|
a12af4500f | ||
|
8e823be4cb | ||
|
b453b1a7dd | ||
|
331bc7c0fd | ||
|
8f2fa30fcb | ||
|
a855b55c6b | ||
|
d1c73e71c7 |
1
changelog.d/18238.feature
Normal file
1
changelog.d/18238.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
If enabled by the user, server admins will see [soft failed](https://spec.matrix.org/v1.13/server-server-api/#soft-failure) events over the Client-Server API.
|
1
changelog.d/18585.feature
Normal file
1
changelog.d/18585.feature
Normal file
@ -0,0 +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`.
|
@ -74,6 +74,7 @@
|
|||||||
- [Users](admin_api/user_admin_api.md)
|
- [Users](admin_api/user_admin_api.md)
|
||||||
- [Server Version](admin_api/version_api.md)
|
- [Server Version](admin_api/version_api.md)
|
||||||
- [Federation](usage/administration/admin_api/federation.md)
|
- [Federation](usage/administration/admin_api/federation.md)
|
||||||
|
- [Client-Server API Extensions](admin_api/client_server_api_extensions.md)
|
||||||
- [Manhole](manhole.md)
|
- [Manhole](manhole.md)
|
||||||
- [Monitoring](metrics-howto.md)
|
- [Monitoring](metrics-howto.md)
|
||||||
- [Reporting Homeserver Usage Statistics](usage/administration/monitoring/reporting_homeserver_usage_statistics.md)
|
- [Reporting Homeserver Usage Statistics](usage/administration/monitoring/reporting_homeserver_usage_statistics.md)
|
||||||
|
51
docs/admin_api/client_server_api_extensions.md
Normal file
51
docs/admin_api/client_server_api_extensions.md
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# Client-Server API Extensions
|
||||||
|
|
||||||
|
Server administrators can set special account data to change how the Client-Server API behaves for
|
||||||
|
their clients. Setting the account data, or having it already set, as a non-admin has no effect.
|
||||||
|
|
||||||
|
All configuration options can be set through the `io.element.synapse.admin_client_config` global
|
||||||
|
account data on the admin's user account.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
PUT /_matrix/client/v3/user/{adminUserId}/account_data/io.element.synapse.admin_client_config
|
||||||
|
{
|
||||||
|
"return_soft_failed_events": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## See soft failed events
|
||||||
|
|
||||||
|
Learn more about soft failure from [the spec](https://spec.matrix.org/v1.14/server-server-api/#soft-failure).
|
||||||
|
|
||||||
|
To receive soft failed events in APIs like `/sync` and `/messages`, set `return_soft_failed_events`
|
||||||
|
to `true` in the admin client config. When `false`, the normal behaviour of these endpoints is to
|
||||||
|
exclude soft failed events.
|
||||||
|
|
||||||
|
**Note**: If the policy server flagged the event as spam and that caused soft failure, that will be indicated
|
||||||
|
in the event's `unsigned` content like so:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "m.room.message",
|
||||||
|
"other": "event_fields_go_here",
|
||||||
|
"unsigned": {
|
||||||
|
"io.element.synapse.soft_failed": true,
|
||||||
|
"io.element.synapse.policy_server_spammy": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Default: `false`
|
||||||
|
|
||||||
|
## See events marked spammy by policy servers
|
||||||
|
|
||||||
|
Learn more about policy servers from [MSC4284](https://github.com/matrix-org/matrix-spec-proposals/pull/4284).
|
||||||
|
|
||||||
|
Similar to `return_soft_failed_events`, clients logged in with admin accounts can see events which were
|
||||||
|
flagged by the policy server as spammy (and thus soft failed) by setting `return_policy_server_spammy_events`
|
||||||
|
to `true`. If `return_soft_failed_events` is `true`, then `return_policy_server_spammy_events` is implied
|
||||||
|
`true`. When `false`, the normal behaviour of Client-Server API endpoints is retained (unless `return_soft_failed_events`
|
||||||
|
is `true`, per above).
|
||||||
|
|
||||||
|
Default: `false`
|
@ -54,6 +54,7 @@ enum EventInternalMetadataData {
|
|||||||
RecheckRedaction(bool),
|
RecheckRedaction(bool),
|
||||||
SoftFailed(bool),
|
SoftFailed(bool),
|
||||||
ProactivelySend(bool),
|
ProactivelySend(bool),
|
||||||
|
PolicyServerSpammy(bool),
|
||||||
Redacted(bool),
|
Redacted(bool),
|
||||||
TxnId(Box<str>),
|
TxnId(Box<str>),
|
||||||
TokenId(i64),
|
TokenId(i64),
|
||||||
@ -96,6 +97,13 @@ impl EventInternalMetadataData {
|
|||||||
.to_owned()
|
.to_owned()
|
||||||
.into_any(),
|
.into_any(),
|
||||||
),
|
),
|
||||||
|
EventInternalMetadataData::PolicyServerSpammy(o) => (
|
||||||
|
pyo3::intern!(py, "policy_server_spammy"),
|
||||||
|
o.into_pyobject(py)
|
||||||
|
.unwrap_infallible()
|
||||||
|
.to_owned()
|
||||||
|
.into_any(),
|
||||||
|
),
|
||||||
EventInternalMetadataData::Redacted(o) => (
|
EventInternalMetadataData::Redacted(o) => (
|
||||||
pyo3::intern!(py, "redacted"),
|
pyo3::intern!(py, "redacted"),
|
||||||
o.into_pyobject(py)
|
o.into_pyobject(py)
|
||||||
@ -155,6 +163,11 @@ impl EventInternalMetadataData {
|
|||||||
.extract()
|
.extract()
|
||||||
.with_context(|| format!("'{key_str}' has invalid type"))?,
|
.with_context(|| format!("'{key_str}' has invalid type"))?,
|
||||||
),
|
),
|
||||||
|
"policy_server_spammy" => EventInternalMetadataData::PolicyServerSpammy(
|
||||||
|
value
|
||||||
|
.extract()
|
||||||
|
.with_context(|| format!("'{key_str}' has invalid type"))?,
|
||||||
|
),
|
||||||
"redacted" => EventInternalMetadataData::Redacted(
|
"redacted" => EventInternalMetadataData::Redacted(
|
||||||
value
|
value
|
||||||
.extract()
|
.extract()
|
||||||
@ -427,6 +440,17 @@ impl EventInternalMetadata {
|
|||||||
set_property!(self, ProactivelySend, obj);
|
set_property!(self, ProactivelySend, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn get_policy_server_spammy(&self) -> PyResult<bool> {
|
||||||
|
Ok(get_property_opt!(self, PolicyServerSpammy)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(false))
|
||||||
|
}
|
||||||
|
#[setter]
|
||||||
|
fn set_policy_server_spammy(&mut self, obj: bool) {
|
||||||
|
set_property!(self, PolicyServerSpammy, obj);
|
||||||
|
}
|
||||||
|
|
||||||
#[getter]
|
#[getter]
|
||||||
fn get_redacted(&self) -> PyResult<bool> {
|
fn get_redacted(&self) -> PyResult<bool> {
|
||||||
let bool = get_property!(self, Redacted)?;
|
let bool = get_property!(self, Redacted)?;
|
||||||
|
@ -290,6 +290,9 @@ class AccountDataTypes:
|
|||||||
MSC4155_INVITE_PERMISSION_CONFIG: Final = (
|
MSC4155_INVITE_PERMISSION_CONFIG: Final = (
|
||||||
"org.matrix.msc4155.invite_permission_config"
|
"org.matrix.msc4155.invite_permission_config"
|
||||||
)
|
)
|
||||||
|
# Synapse-specific behaviour. See "Client-Server API Extensions" documentation
|
||||||
|
# in Admin API for more information.
|
||||||
|
SYNAPSE_ADMIN_CLIENT_CONFIG: Final = "io.element.synapse.admin_client_config"
|
||||||
|
|
||||||
|
|
||||||
class HistoryVisibility:
|
class HistoryVisibility:
|
||||||
|
@ -555,6 +555,9 @@ class ApplicationServiceApi(SimpleHttpClient):
|
|||||||
)
|
)
|
||||||
and service.is_interested_in_user(e.state_key)
|
and service.is_interested_in_user(e.state_key)
|
||||||
),
|
),
|
||||||
|
# Appservices are considered 'trusted' by the admin and should have
|
||||||
|
# applicable metadata on their events.
|
||||||
|
include_admin_metadata=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
for e in events
|
for e in events
|
||||||
|
@ -421,11 +421,31 @@ class SerializeEventConfig:
|
|||||||
# False, that state will be removed from the event before it is returned.
|
# False, that state will be removed from the event before it is returned.
|
||||||
# Otherwise, it will be kept.
|
# Otherwise, it will be kept.
|
||||||
include_stripped_room_state: bool = False
|
include_stripped_room_state: bool = False
|
||||||
|
# When True, sets unsigned flags to help clients identify events which
|
||||||
|
# only server admins can see through other configuration. For example,
|
||||||
|
# whether an event was soft failed by the server.
|
||||||
|
include_admin_metadata: bool = False
|
||||||
|
|
||||||
|
# Developer note: when adding properties, update make_config_for_admin() below.
|
||||||
|
|
||||||
|
|
||||||
_DEFAULT_SERIALIZE_EVENT_CONFIG = SerializeEventConfig()
|
_DEFAULT_SERIALIZE_EVENT_CONFIG = SerializeEventConfig()
|
||||||
|
|
||||||
|
|
||||||
|
def make_config_for_admin(existing: SerializeEventConfig) -> SerializeEventConfig:
|
||||||
|
# Developer note: when adding properties, update test_make_serialize_config_for_admin_retains_other_fields
|
||||||
|
return SerializeEventConfig(
|
||||||
|
# Set the options which are only available to server admins
|
||||||
|
include_admin_metadata=True,
|
||||||
|
# And copy the rest
|
||||||
|
as_client_event=existing.as_client_event,
|
||||||
|
event_format=existing.event_format,
|
||||||
|
requester=existing.requester,
|
||||||
|
only_event_fields=existing.only_event_fields,
|
||||||
|
include_stripped_room_state=existing.include_stripped_room_state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def serialize_event(
|
def serialize_event(
|
||||||
e: Union[JsonDict, EventBase],
|
e: Union[JsonDict, EventBase],
|
||||||
time_now_ms: int,
|
time_now_ms: int,
|
||||||
@ -528,6 +548,12 @@ def serialize_event(
|
|||||||
d["content"] = dict(d["content"])
|
d["content"] = dict(d["content"])
|
||||||
d["content"]["redacts"] = e.redacts
|
d["content"]["redacts"] = e.redacts
|
||||||
|
|
||||||
|
if config.include_admin_metadata:
|
||||||
|
if e.internal_metadata.is_soft_failed():
|
||||||
|
d["unsigned"]["io.element.synapse.soft_failed"] = True
|
||||||
|
if e.internal_metadata.policy_server_spammy:
|
||||||
|
d["unsigned"]["io.element.synapse.policy_server_spammy"] = True
|
||||||
|
|
||||||
only_event_fields = config.only_event_fields
|
only_event_fields = config.only_event_fields
|
||||||
if only_event_fields:
|
if only_event_fields:
|
||||||
if not isinstance(only_event_fields, list) or not all(
|
if not isinstance(only_event_fields, list) or not all(
|
||||||
@ -576,6 +602,15 @@ class EventClientSerializer:
|
|||||||
if not isinstance(event, EventBase):
|
if not isinstance(event, EventBase):
|
||||||
return event
|
return event
|
||||||
|
|
||||||
|
# Force-enable server admin metadata because the only time an event with
|
||||||
|
# relevant metadata will be when the admin requested it via their admin
|
||||||
|
# client config account data. Also, it's "just" some `unsigned` flags, so
|
||||||
|
# shouldn't cause much in terms of problems to downstream consumers.
|
||||||
|
if config.requester is not None and await self._store.is_server_admin(
|
||||||
|
config.requester.user
|
||||||
|
):
|
||||||
|
config = make_config_for_admin(config)
|
||||||
|
|
||||||
serialized_event = serialize_event(event, time_now, config=config)
|
serialized_event = serialize_event(event, time_now, config=config)
|
||||||
|
|
||||||
new_unsigned = {}
|
new_unsigned = {}
|
||||||
|
@ -174,6 +174,7 @@ class FederationBase:
|
|||||||
"Event not allowed by policy server, soft-failing %s", pdu.event_id
|
"Event not allowed by policy server, soft-failing %s", pdu.event_id
|
||||||
)
|
)
|
||||||
pdu.internal_metadata.soft_failed = True
|
pdu.internal_metadata.soft_failed = True
|
||||||
|
pdu.internal_metadata.policy_server_spammy = True
|
||||||
# Note: we don't redact the event so admins can inspect the event after the
|
# Note: we don't redact the event so admins can inspect the event after the
|
||||||
# fact. Other processes may redact the event, but that won't be applied to
|
# fact. Other processes may redact the event, but that won't be applied to
|
||||||
# the database copy of the event until the server's config requires it.
|
# the database copy of the event until the server's config requires it.
|
||||||
|
@ -1111,6 +1111,9 @@ class EventCreationHandler:
|
|||||||
|
|
||||||
policy_allowed = await self._policy_handler.is_event_allowed(event)
|
policy_allowed = await self._policy_handler.is_event_allowed(event)
|
||||||
if not policy_allowed:
|
if not policy_allowed:
|
||||||
|
# We shouldn't need to set the metadata because the raise should
|
||||||
|
# cause the request to be denied, but just in case:
|
||||||
|
event.internal_metadata.policy_server_spammy = True
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Event not allowed by policy server, rejecting %s",
|
"Event not allowed by policy server, rejecting %s",
|
||||||
event.event_id,
|
event.event_id,
|
||||||
|
22
synapse/storage/admin_client_config.py
Normal file
22
synapse/storage/admin_client_config.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from synapse.types import JsonMapping
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AdminClientConfig:
|
||||||
|
"""Class to track various Synapse-specific admin-only client-impacting config options."""
|
||||||
|
|
||||||
|
def __init__(self, account_data: Optional[JsonMapping]):
|
||||||
|
self.return_soft_failed_events: bool = False
|
||||||
|
self.return_policy_server_spammy_events: bool = False
|
||||||
|
|
||||||
|
if account_data:
|
||||||
|
self.return_soft_failed_events = account_data.get(
|
||||||
|
"return_soft_failed_events", False
|
||||||
|
)
|
||||||
|
self.return_policy_server_spammy_events = account_data.get(
|
||||||
|
"return_policy_server_spammy_events", False
|
||||||
|
)
|
@ -37,6 +37,7 @@ from synapse.api.constants import AccountDataTypes
|
|||||||
from synapse.api.errors import Codes, SynapseError
|
from synapse.api.errors import Codes, SynapseError
|
||||||
from synapse.replication.tcp.streams import AccountDataStream
|
from synapse.replication.tcp.streams import AccountDataStream
|
||||||
from synapse.storage._base import db_to_json
|
from synapse.storage._base import db_to_json
|
||||||
|
from synapse.storage.admin_client_config import AdminClientConfig
|
||||||
from synapse.storage.database import (
|
from synapse.storage.database import (
|
||||||
DatabasePool,
|
DatabasePool,
|
||||||
LoggingDatabaseConnection,
|
LoggingDatabaseConnection,
|
||||||
@ -578,6 +579,21 @@ class AccountDataWorkerStore(PushRulesWorkerStore, CacheInvalidationWorkerStore)
|
|||||||
)
|
)
|
||||||
return InviteRulesConfig(data)
|
return InviteRulesConfig(data)
|
||||||
|
|
||||||
|
async def get_admin_client_config_for_user(self, user_id: str) -> AdminClientConfig:
|
||||||
|
"""
|
||||||
|
Get the admin client configuration for the specified user.
|
||||||
|
|
||||||
|
The admin client config contains Synapse-specific settings that clients running
|
||||||
|
server admin accounts can use. They have no effect on non-admin users.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id: The user ID to get config for.
|
||||||
|
"""
|
||||||
|
data = await self.get_global_account_data_by_type_for_user(
|
||||||
|
user_id, AccountDataTypes.SYNAPSE_ADMIN_CLIENT_CONFIG
|
||||||
|
)
|
||||||
|
return AdminClientConfig(data)
|
||||||
|
|
||||||
def process_replication_rows(
|
def process_replication_rows(
|
||||||
self,
|
self,
|
||||||
stream_name: str,
|
stream_name: str,
|
||||||
|
@ -33,6 +33,9 @@ class EventInternalMetadata:
|
|||||||
proactively_send: bool
|
proactively_send: bool
|
||||||
redacted: bool
|
redacted: bool
|
||||||
|
|
||||||
|
policy_server_spammy: bool
|
||||||
|
"""whether the policy server indicated that this event is spammy"""
|
||||||
|
|
||||||
txn_id: str
|
txn_id: str
|
||||||
"""The transaction ID, if it was set when the event was created."""
|
"""The transaction ID, if it was set when the event was created."""
|
||||||
token_id: int
|
token_id: int
|
||||||
|
@ -48,7 +48,13 @@ from synapse.logging.opentracing import trace
|
|||||||
from synapse.storage.controllers import StorageControllers
|
from synapse.storage.controllers import StorageControllers
|
||||||
from synapse.storage.databases.main import DataStore
|
from synapse.storage.databases.main import DataStore
|
||||||
from synapse.synapse_rust.events import event_visible_to_server
|
from synapse.synapse_rust.events import event_visible_to_server
|
||||||
from synapse.types import RetentionPolicy, StateMap, StrCollection, get_domain_from_id
|
from synapse.types import (
|
||||||
|
RetentionPolicy,
|
||||||
|
StateMap,
|
||||||
|
StrCollection,
|
||||||
|
UserID,
|
||||||
|
get_domain_from_id,
|
||||||
|
)
|
||||||
from synapse.types.state import StateFilter
|
from synapse.types.state import StateFilter
|
||||||
from synapse.util import Clock
|
from synapse.util import Clock
|
||||||
|
|
||||||
@ -106,9 +112,30 @@ async def filter_events_for_client(
|
|||||||
of `user_id` at each event.
|
of `user_id` at each event.
|
||||||
"""
|
"""
|
||||||
# Filter out events that have been soft failed so that we don't relay them
|
# Filter out events that have been soft failed so that we don't relay them
|
||||||
# to clients.
|
# to clients, unless they're a server admin and want that to happen.
|
||||||
events_before_filtering = events
|
events_before_filtering = events
|
||||||
events = [e for e in events if not e.internal_metadata.is_soft_failed()]
|
client_config = await storage.main.get_admin_client_config_for_user(user_id)
|
||||||
|
if (
|
||||||
|
filter_send_to_client
|
||||||
|
and (
|
||||||
|
client_config.return_soft_failed_events
|
||||||
|
or client_config.return_policy_server_spammy_events
|
||||||
|
)
|
||||||
|
and await storage.main.is_server_admin(UserID.from_string(user_id))
|
||||||
|
):
|
||||||
|
# `return_soft_failed_events` implies `return_policy_server_spammy_events`, so
|
||||||
|
# we want to check when they've asked for *just* `return_policy_server_spammy_events`
|
||||||
|
if not client_config.return_soft_failed_events:
|
||||||
|
events = [
|
||||||
|
e
|
||||||
|
for e in events
|
||||||
|
if not e.internal_metadata.is_soft_failed()
|
||||||
|
or e.internal_metadata.policy_server_spammy
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
events = events_before_filtering
|
||||||
|
else:
|
||||||
|
events = [e for e in events if not e.internal_metadata.is_soft_failed()]
|
||||||
if len(events_before_filtering) != len(events):
|
if len(events_before_filtering) != len(events):
|
||||||
if filtered_event_logger.isEnabledFor(logging.DEBUG):
|
if filtered_event_logger.isEnabledFor(logging.DEBUG):
|
||||||
filtered_event_logger.debug(
|
filtered_event_logger.debug(
|
||||||
|
@ -34,11 +34,13 @@ from synapse.events.utils import (
|
|||||||
_split_field,
|
_split_field,
|
||||||
clone_event,
|
clone_event,
|
||||||
copy_and_fixup_power_levels_contents,
|
copy_and_fixup_power_levels_contents,
|
||||||
|
format_event_raw,
|
||||||
|
make_config_for_admin,
|
||||||
maybe_upsert_event_field,
|
maybe_upsert_event_field,
|
||||||
prune_event,
|
prune_event,
|
||||||
serialize_event,
|
serialize_event,
|
||||||
)
|
)
|
||||||
from synapse.types import JsonDict
|
from synapse.types import JsonDict, create_requester
|
||||||
from synapse.util.frozenutils import freeze
|
from synapse.util.frozenutils import freeze
|
||||||
|
|
||||||
|
|
||||||
@ -49,7 +51,13 @@ def MockEvent(**kwargs: Any) -> EventBase:
|
|||||||
kwargs["type"] = "fake_type"
|
kwargs["type"] = "fake_type"
|
||||||
if "content" not in kwargs:
|
if "content" not in kwargs:
|
||||||
kwargs["content"] = {}
|
kwargs["content"] = {}
|
||||||
return make_event_from_dict(kwargs)
|
|
||||||
|
# Move internal metadata out so we can call make_event properly
|
||||||
|
internal_metadata = kwargs.get("internal_metadata")
|
||||||
|
if internal_metadata is not None:
|
||||||
|
kwargs.pop("internal_metadata")
|
||||||
|
|
||||||
|
return make_event_from_dict(kwargs, internal_metadata_dict=internal_metadata)
|
||||||
|
|
||||||
|
|
||||||
class TestMaybeUpsertEventField(stdlib_unittest.TestCase):
|
class TestMaybeUpsertEventField(stdlib_unittest.TestCase):
|
||||||
@ -639,9 +647,18 @@ class CloneEventTestCase(stdlib_unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class SerializeEventTestCase(stdlib_unittest.TestCase):
|
class SerializeEventTestCase(stdlib_unittest.TestCase):
|
||||||
def serialize(self, ev: EventBase, fields: Optional[List[str]]) -> JsonDict:
|
def serialize(
|
||||||
|
self,
|
||||||
|
ev: EventBase,
|
||||||
|
fields: Optional[List[str]],
|
||||||
|
include_admin_metadata: bool = False,
|
||||||
|
) -> JsonDict:
|
||||||
return serialize_event(
|
return serialize_event(
|
||||||
ev, 1479807801915, config=SerializeEventConfig(only_event_fields=fields)
|
ev,
|
||||||
|
1479807801915,
|
||||||
|
config=SerializeEventConfig(
|
||||||
|
only_event_fields=fields, include_admin_metadata=include_admin_metadata
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_event_fields_works_with_keys(self) -> None:
|
def test_event_fields_works_with_keys(self) -> None:
|
||||||
@ -760,6 +777,78 @@ class SerializeEventTestCase(stdlib_unittest.TestCase):
|
|||||||
["room_id", 4], # type: ignore[list-item]
|
["room_id", 4], # type: ignore[list-item]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_default_serialize_config_excludes_admin_metadata(self) -> None:
|
||||||
|
# We just really don't want this to be set to True accidentally
|
||||||
|
self.assertFalse(SerializeEventConfig().include_admin_metadata)
|
||||||
|
|
||||||
|
def test_event_flagged_for_admins(self) -> None:
|
||||||
|
# Default behaviour should be *not* to include it
|
||||||
|
self.assertEqual(
|
||||||
|
self.serialize(
|
||||||
|
MockEvent(
|
||||||
|
type="foo",
|
||||||
|
event_id="test",
|
||||||
|
room_id="!foo:bar",
|
||||||
|
content={"foo": "bar"},
|
||||||
|
internal_metadata={"soft_failed": True},
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"type": "foo",
|
||||||
|
"event_id": "test",
|
||||||
|
"room_id": "!foo:bar",
|
||||||
|
"content": {"foo": "bar"},
|
||||||
|
"unsigned": {},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# When asked though, we should set it
|
||||||
|
self.assertEqual(
|
||||||
|
self.serialize(
|
||||||
|
MockEvent(
|
||||||
|
type="foo",
|
||||||
|
event_id="test",
|
||||||
|
room_id="!foo:bar",
|
||||||
|
content={"foo": "bar"},
|
||||||
|
internal_metadata={"soft_failed": True},
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"type": "foo",
|
||||||
|
"event_id": "test",
|
||||||
|
"room_id": "!foo:bar",
|
||||||
|
"content": {"foo": "bar"},
|
||||||
|
"unsigned": {"io.element.synapse.soft_failed": True},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_make_serialize_config_for_admin_retains_other_fields(self) -> None:
|
||||||
|
non_default_config = SerializeEventConfig(
|
||||||
|
include_admin_metadata=False, # should be True in a moment
|
||||||
|
as_client_event=False, # default True
|
||||||
|
event_format=format_event_raw, # default format_event_for_client_v1
|
||||||
|
requester=create_requester("@example:example.org"), # default None
|
||||||
|
only_event_fields=["foo"], # default None
|
||||||
|
include_stripped_room_state=True, # default False
|
||||||
|
)
|
||||||
|
admin_config = make_config_for_admin(non_default_config)
|
||||||
|
self.assertEqual(
|
||||||
|
admin_config.as_client_event, non_default_config.as_client_event
|
||||||
|
)
|
||||||
|
self.assertEqual(admin_config.event_format, non_default_config.event_format)
|
||||||
|
self.assertEqual(admin_config.requester, non_default_config.requester)
|
||||||
|
self.assertEqual(
|
||||||
|
admin_config.only_event_fields, non_default_config.only_event_fields
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
admin_config.include_stripped_room_state,
|
||||||
|
admin_config.include_stripped_room_state,
|
||||||
|
)
|
||||||
|
self.assertTrue(admin_config.include_admin_metadata)
|
||||||
|
|
||||||
|
|
||||||
class CopyPowerLevelsContentTestCase(stdlib_unittest.TestCase):
|
class CopyPowerLevelsContentTestCase(stdlib_unittest.TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
|
@ -1181,7 +1181,7 @@ class BundledAggregationsTestCase(BaseRelationsTestCase):
|
|||||||
bundled_aggregations,
|
bundled_aggregations,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._test_bundled_aggregations(RelationTypes.REFERENCE, assert_annotations, 6)
|
self._test_bundled_aggregations(RelationTypes.REFERENCE, assert_annotations, 8)
|
||||||
|
|
||||||
def test_thread(self) -> None:
|
def test_thread(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -1226,21 +1226,21 @@ class BundledAggregationsTestCase(BaseRelationsTestCase):
|
|||||||
|
|
||||||
# The "user" sent the root event and is making queries for the bundled
|
# The "user" sent the root event and is making queries for the bundled
|
||||||
# aggregations: they have participated.
|
# aggregations: they have participated.
|
||||||
self._test_bundled_aggregations(RelationTypes.THREAD, _gen_assert(True), 6)
|
self._test_bundled_aggregations(RelationTypes.THREAD, _gen_assert(True), 9)
|
||||||
# The "user2" sent replies in the thread and is making queries for the
|
# The "user2" sent replies in the thread and is making queries for the
|
||||||
# bundled aggregations: they have participated.
|
# bundled aggregations: they have participated.
|
||||||
#
|
#
|
||||||
# Note that this re-uses some cached values, so the total number of
|
# Note that this re-uses some cached values, so the total number of
|
||||||
# queries is much smaller.
|
# queries is much smaller.
|
||||||
self._test_bundled_aggregations(
|
self._test_bundled_aggregations(
|
||||||
RelationTypes.THREAD, _gen_assert(True), 3, access_token=self.user2_token
|
RelationTypes.THREAD, _gen_assert(True), 6, access_token=self.user2_token
|
||||||
)
|
)
|
||||||
|
|
||||||
# A user with no interactions with the thread: they have not participated.
|
# A user with no interactions with the thread: they have not participated.
|
||||||
user3_id, user3_token = self._create_user("charlie")
|
user3_id, user3_token = self._create_user("charlie")
|
||||||
self.helper.join(self.room, user=user3_id, tok=user3_token)
|
self.helper.join(self.room, user=user3_id, tok=user3_token)
|
||||||
self._test_bundled_aggregations(
|
self._test_bundled_aggregations(
|
||||||
RelationTypes.THREAD, _gen_assert(False), 3, access_token=user3_token
|
RelationTypes.THREAD, _gen_assert(False), 6, access_token=user3_token
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_thread_with_bundled_aggregations_for_latest(self) -> None:
|
def test_thread_with_bundled_aggregations_for_latest(self) -> None:
|
||||||
@ -1287,7 +1287,7 @@ class BundledAggregationsTestCase(BaseRelationsTestCase):
|
|||||||
bundled_aggregations["latest_event"].get("unsigned"),
|
bundled_aggregations["latest_event"].get("unsigned"),
|
||||||
)
|
)
|
||||||
|
|
||||||
self._test_bundled_aggregations(RelationTypes.THREAD, assert_thread, 6)
|
self._test_bundled_aggregations(RelationTypes.THREAD, assert_thread, 9)
|
||||||
|
|
||||||
def test_nested_thread(self) -> None:
|
def test_nested_thread(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user