Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit e2a1adb

Browse files
author
David Robertson
authored
Allow selecting "prejoin" events by state keys (#14642)
* Declare new config * Parse new config * Read new config * Don't use trial/our TestCase where it's not needed Before: ``` $ time trial tests/events/test_utils.py > /dev/null real 0m2.277s user 0m2.186s sys 0m0.083s ``` After: ``` $ time trial tests/events/test_utils.py > /dev/null real 0m0.566s user 0m0.508s sys 0m0.056s ``` * Helper to upsert to event fields without exceeding size limits. * Use helper when adding invite/knock state Now that we allow admins to include events in prejoin room state with arbitrary state keys, be a good Matrix citizen and ensure they don't accidentally create an oversized event. * Changelog * Move StateFilter tests should have done this in #14668 * Add extra methods to StateFilter * Use StateFilter * Ensure test file enforces typed defs; alphabetise * Workaround surprising get_current_state_ids * Whoops, fix mypy
1 parent 3d87847 commit e2a1adb

File tree

14 files changed

+983
-695
lines changed

14 files changed

+983
-695
lines changed

changelog.d/14642.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow selecting "prejoin" events by state keys in addition to event types.

docs/usage/configuration/config_documentation.md

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2501,32 +2501,53 @@ Config settings related to the client/server API
25012501
---
25022502
### `room_prejoin_state`
25032503

2504-
Controls for the state that is shared with users who receive an invite
2505-
to a room. By default, the following state event types are shared with users who
2506-
receive invites to the room:
2507-
- m.room.join_rules
2508-
- m.room.canonical_alias
2509-
- m.room.avatar
2510-
- m.room.encryption
2511-
- m.room.name
2512-
- m.room.create
2513-
- m.room.topic
2504+
This setting controls the state that is shared with users upon receiving an
2505+
invite to a room, or in reply to a knock on a room. By default, the following
2506+
state events are shared with users:
2507+
2508+
- `m.room.join_rules`
2509+
- `m.room.canonical_alias`
2510+
- `m.room.avatar`
2511+
- `m.room.encryption`
2512+
- `m.room.name`
2513+
- `m.room.create`
2514+
- `m.room.topic`
25142515

25152516
To change the default behavior, use the following sub-options:
2516-
* `disable_default_event_types`: set to true to disable the above defaults. If this
2517-
is enabled, only the event types listed in `additional_event_types` are shared.
2518-
Defaults to false.
2519-
* `additional_event_types`: Additional state event types to share with users when they are invited
2520-
to a room. By default, this list is empty (so only the default event types are shared).
2517+
* `disable_default_event_types`: boolean. Set to `true` to disable the above
2518+
defaults. If this is enabled, only the event types listed in
2519+
`additional_event_types` are shared. Defaults to `false`.
2520+
* `additional_event_types`: A list of additional state events to include in the
2521+
events to be shared. By default, this list is empty (so only the default event
2522+
types are shared).
2523+
2524+
Each entry in this list should be either a single string or a list of two
2525+
strings.
2526+
* A standalone string `t` represents all events with type `t` (i.e.
2527+
with no restrictions on state keys).
2528+
* A pair of strings `[t, s]` represents a single event with type `t` and
2529+
state key `s`. The same type can appear in two entries with different state
2530+
keys: in this situation, both state keys are included in prejoin state.
25212531

25222532
Example configuration:
25232533
```yaml
25242534
room_prejoin_state:
2525-
disable_default_event_types: true
2535+
disable_default_event_types: false
25262536
additional_event_types:
2527-
- org.example.custom.event.type
2528-
- m.room.join_rules
2537+
# Share all events of type `org.example.custom.event.typeA`
2538+
- org.example.custom.event.typeA
2539+
# Share only events of type `org.example.custom.event.typeB` whose
2540+
# state_key is "foo"
2541+
- ["org.example.custom.event.typeB", "foo"]
2542+
# Share only events of type `org.example.custom.event.typeC` whose
2543+
# state_key is "bar" or "baz"
2544+
- ["org.example.custom.event.typeC", "bar"]
2545+
- ["org.example.custom.event.typeC", "baz"]
25292546
```
2547+
2548+
*Changed in Synapse 1.74:* admins can filter the events in prejoin state based
2549+
on their state key.
2550+
25302551
---
25312552
### `track_puppeted_user_ips`
25322553

mypy.ini

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ disallow_untyped_defs = False
8989
[mypy-tests.*]
9090
disallow_untyped_defs = False
9191

92+
[mypy-tests.config.test_api]
93+
disallow_untyped_defs = True
94+
95+
[mypy-tests.federation.transport.test_client]
96+
disallow_untyped_defs = True
97+
9298
[mypy-tests.handlers.test_sso]
9399
disallow_untyped_defs = True
94100

@@ -101,7 +107,7 @@ disallow_untyped_defs = True
101107
[mypy-tests.push.test_bulk_push_rule_evaluator]
102108
disallow_untyped_defs = True
103109

104-
[mypy-tests.test_server]
110+
[mypy-tests.rest.*]
105111
disallow_untyped_defs = True
106112

107113
[mypy-tests.state.test_profile]
@@ -110,10 +116,10 @@ disallow_untyped_defs = True
110116
[mypy-tests.storage.*]
111117
disallow_untyped_defs = True
112118

113-
[mypy-tests.rest.*]
119+
[mypy-tests.test_server]
114120
disallow_untyped_defs = True
115121

116-
[mypy-tests.federation.transport.test_client]
122+
[mypy-tests.types.*]
117123
disallow_untyped_defs = True
118124

119125
[mypy-tests.util.caches.*]

synapse/config/_util.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ def validate_config(
3333
config: the configuration value to be validated
3434
config_path: the path within the config file. This will be used as a basis
3535
for the error message.
36+
37+
Raises:
38+
ConfigError, if validation fails.
3639
"""
3740
try:
3841
jsonschema.validate(config, json_schema)

synapse/config/api.py

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,34 @@
1313
# limitations under the License.
1414

1515
import logging
16-
from typing import Any, Iterable
16+
from typing import Any, Iterable, Optional, Tuple
1717

1818
from synapse.api.constants import EventTypes
1919
from synapse.config._base import Config, ConfigError
2020
from synapse.config._util import validate_config
2121
from synapse.types import JsonDict
22+
from synapse.types.state import StateFilter
2223

2324
logger = logging.getLogger(__name__)
2425

2526

2627
class ApiConfig(Config):
2728
section = "api"
2829

30+
room_prejoin_state: StateFilter
31+
track_puppetted_users_ips: bool
32+
2933
def read_config(self, config: JsonDict, **kwargs: Any) -> None:
3034
validate_config(_MAIN_SCHEMA, config, ())
31-
self.room_prejoin_state = list(self._get_prejoin_state_types(config))
35+
self.room_prejoin_state = StateFilter.from_types(
36+
self._get_prejoin_state_entries(config)
37+
)
3238
self.track_puppeted_user_ips = config.get("track_puppeted_user_ips", False)
3339

34-
def _get_prejoin_state_types(self, config: JsonDict) -> Iterable[str]:
35-
"""Get the event types to include in the prejoin state
36-
37-
Parses the config and returns an iterable of the event types to be included.
38-
"""
40+
def _get_prejoin_state_entries(
41+
self, config: JsonDict
42+
) -> Iterable[Tuple[str, Optional[str]]]:
43+
"""Get the event types and state keys to include in the prejoin state."""
3944
room_prejoin_state_config = config.get("room_prejoin_state") or {}
4045

4146
# backwards-compatibility support for room_invite_state_types
@@ -50,33 +55,39 @@ def _get_prejoin_state_types(self, config: JsonDict) -> Iterable[str]:
5055

5156
logger.warning(_ROOM_INVITE_STATE_TYPES_WARNING)
5257

53-
yield from config["room_invite_state_types"]
58+
for event_type in config["room_invite_state_types"]:
59+
yield event_type, None
5460
return
5561

5662
if not room_prejoin_state_config.get("disable_default_event_types"):
57-
yield from _DEFAULT_PREJOIN_STATE_TYPES
63+
yield from _DEFAULT_PREJOIN_STATE_TYPES_AND_STATE_KEYS
5864

59-
yield from room_prejoin_state_config.get("additional_event_types", [])
65+
for entry in room_prejoin_state_config.get("additional_event_types", []):
66+
if isinstance(entry, str):
67+
yield entry, None
68+
else:
69+
yield entry
6070

6171

6272
_ROOM_INVITE_STATE_TYPES_WARNING = """\
6373
WARNING: The 'room_invite_state_types' configuration setting is now deprecated,
6474
and replaced with 'room_prejoin_state'. New features may not work correctly
65-
unless 'room_invite_state_types' is removed. See the sample configuration file for
66-
details of 'room_prejoin_state'.
75+
unless 'room_invite_state_types' is removed. See the config documentation at
76+
https://matrix-org.github.io/synapse/latest/usage/configuration/config_documentation.html#room_prejoin_state
77+
for details of 'room_prejoin_state'.
6778
--------------------------------------------------------------------------------
6879
"""
6980

70-
_DEFAULT_PREJOIN_STATE_TYPES = [
71-
EventTypes.JoinRules,
72-
EventTypes.CanonicalAlias,
73-
EventTypes.RoomAvatar,
74-
EventTypes.RoomEncryption,
75-
EventTypes.Name,
81+
_DEFAULT_PREJOIN_STATE_TYPES_AND_STATE_KEYS = [
82+
(EventTypes.JoinRules, ""),
83+
(EventTypes.CanonicalAlias, ""),
84+
(EventTypes.RoomAvatar, ""),
85+
(EventTypes.RoomEncryption, ""),
86+
(EventTypes.Name, ""),
7687
# Per MSC1772.
77-
EventTypes.Create,
88+
(EventTypes.Create, ""),
7889
# Per MSC3173.
79-
EventTypes.Topic,
90+
(EventTypes.Topic, ""),
8091
]
8192

8293

@@ -90,7 +101,17 @@ def _get_prejoin_state_types(self, config: JsonDict) -> Iterable[str]:
90101
"disable_default_event_types": {"type": "boolean"},
91102
"additional_event_types": {
92103
"type": "array",
93-
"items": {"type": "string"},
104+
"items": {
105+
"oneOf": [
106+
{"type": "string"},
107+
{
108+
"type": "array",
109+
"items": {"type": "string"},
110+
"minItems": 2,
111+
"maxItems": 2,
112+
},
113+
],
114+
},
94115
},
95116
},
96117
},

synapse/events/utils.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,14 @@
2828
)
2929

3030
import attr
31+
from canonicaljson import encode_canonical_json
3132

32-
from synapse.api.constants import EventContentFields, EventTypes, RelationTypes
33+
from synapse.api.constants import (
34+
MAX_PDU_SIZE,
35+
EventContentFields,
36+
EventTypes,
37+
RelationTypes,
38+
)
3339
from synapse.api.errors import Codes, SynapseError
3440
from synapse.api.room_versions import RoomVersion
3541
from synapse.types import JsonDict
@@ -674,3 +680,27 @@ def validate_canonicaljson(value: Any) -> None:
674680
elif not isinstance(value, (bool, str)) and value is not None:
675681
# Other potential JSON values (bool, None, str) are safe.
676682
raise SynapseError(400, "Unknown JSON value", Codes.BAD_JSON)
683+
684+
685+
def maybe_upsert_event_field(
686+
event: EventBase, container: JsonDict, key: str, value: object
687+
) -> bool:
688+
"""Upsert an event field, but only if this doesn't make the event too large.
689+
690+
Returns true iff the upsert took place.
691+
"""
692+
if key in container:
693+
old_value: object = container[key]
694+
container[key] = value
695+
# NB: here and below, we assume that passing a non-None `time_now` argument to
696+
# get_pdu_json doesn't increase the size of the encoded result.
697+
upsert_okay = len(encode_canonical_json(event.get_pdu_json())) <= MAX_PDU_SIZE
698+
if not upsert_okay:
699+
container[key] = old_value
700+
else:
701+
container[key] = value
702+
upsert_okay = len(encode_canonical_json(event.get_pdu_json())) <= MAX_PDU_SIZE
703+
if not upsert_okay:
704+
del container[key]
705+
706+
return upsert_okay

synapse/handlers/message.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
from synapse.events import EventBase, relation_from_event
5151
from synapse.events.builder import EventBuilder
5252
from synapse.events.snapshot import EventContext
53+
from synapse.events.utils import maybe_upsert_event_field
5354
from synapse.events.validator import EventValidator
5455
from synapse.handlers.directory import DirectoryHandler
5556
from synapse.logging import opentracing
@@ -1739,12 +1740,15 @@ async def persist_and_notify_client_events(
17391740

17401741
if event.type == EventTypes.Member:
17411742
if event.content["membership"] == Membership.INVITE:
1742-
event.unsigned[
1743-
"invite_room_state"
1744-
] = await self.store.get_stripped_room_state_from_event_context(
1745-
context,
1746-
self.room_prejoin_state_types,
1747-
membership_user_id=event.sender,
1743+
maybe_upsert_event_field(
1744+
event,
1745+
event.unsigned,
1746+
"invite_room_state",
1747+
await self.store.get_stripped_room_state_from_event_context(
1748+
context,
1749+
self.room_prejoin_state_types,
1750+
membership_user_id=event.sender,
1751+
),
17481752
)
17491753

17501754
invitee = UserID.from_string(event.state_key)
@@ -1762,11 +1766,14 @@ async def persist_and_notify_client_events(
17621766
event.signatures.update(returned_invite.signatures)
17631767

17641768
if event.content["membership"] == Membership.KNOCK:
1765-
event.unsigned[
1766-
"knock_room_state"
1767-
] = await self.store.get_stripped_room_state_from_event_context(
1768-
context,
1769-
self.room_prejoin_state_types,
1769+
maybe_upsert_event_field(
1770+
event,
1771+
event.unsigned,
1772+
"knock_room_state",
1773+
await self.store.get_stripped_room_state_from_event_context(
1774+
context,
1775+
self.room_prejoin_state_types,
1776+
),
17701777
)
17711778

17721779
if event.type == EventTypes.Redaction:

0 commit comments

Comments
 (0)