-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Consolidate logic for parsing relations. #12693
Changes from all commits
e50674f
f9d63aa
1ff43dd
c5ea31a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Consolidate parsing of relation information from events. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,7 +44,7 @@ | |
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersions | ||
from synapse.api.urls import ConsentURIBuilder | ||
from synapse.event_auth import validate_event_for_room_version | ||
from synapse.events import EventBase | ||
from synapse.events import EventBase, relation_from_event | ||
from synapse.events.builder import EventBuilder | ||
from synapse.events.snapshot import EventContext | ||
from synapse.events.validator import EventValidator | ||
|
@@ -1060,20 +1060,11 @@ async def _validate_event_relation(self, event: EventBase) -> None: | |
SynapseError if the event is invalid. | ||
""" | ||
|
||
relation = event.content.get("m.relates_to") | ||
relation = relation_from_event(event) | ||
if not relation: | ||
return | ||
|
||
relation_type = relation.get("rel_type") | ||
if not relation_type: | ||
return | ||
|
||
# Ensure the parent is real. | ||
relates_to = relation.get("event_id") | ||
if not relates_to: | ||
return | ||
|
||
parent_event = await self.store.get_event(relates_to, allow_none=True) | ||
parent_event = await self.store.get_event(relation.parent_id, allow_none=True) | ||
if parent_event: | ||
# And in the same room. | ||
if parent_event.room_id != event.room_id: | ||
|
@@ -1082,28 +1073,31 @@ async def _validate_event_relation(self, event: EventBase) -> None: | |
else: | ||
# There must be some reason that the client knows the event exists, | ||
# see if there are existing relations. If so, assume everything is fine. | ||
if not await self.store.event_is_target_of_relation(relates_to): | ||
if not await self.store.event_is_target_of_relation(relation.parent_id): | ||
# Otherwise, the client can't know about the parent event! | ||
raise SynapseError(400, "Can't send relation to unknown event") | ||
|
||
# If this event is an annotation then we check that that the sender | ||
# can't annotate the same way twice (e.g. stops users from liking an | ||
# event multiple times). | ||
if relation_type == RelationTypes.ANNOTATION: | ||
aggregation_key = relation["key"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would raise a 500 on a missing key, now it will 400. |
||
if relation.rel_type == RelationTypes.ANNOTATION: | ||
aggregation_key = relation.aggregation_key | ||
|
||
if aggregation_key is None: | ||
raise SynapseError(400, "Missing aggregation key") | ||
|
||
if len(aggregation_key) > 500: | ||
raise SynapseError(400, "Aggregation key is too long") | ||
|
||
already_exists = await self.store.has_user_annotated_event( | ||
relates_to, event.type, aggregation_key, event.sender | ||
relation.parent_id, event.type, aggregation_key, event.sender | ||
) | ||
if already_exists: | ||
raise SynapseError(400, "Can't send same reaction twice") | ||
|
||
# Don't attempt to start a thread if the parent event is a relation. | ||
elif relation_type == RelationTypes.THREAD: | ||
if await self.store.event_includes_relation(relates_to): | ||
elif relation.rel_type == RelationTypes.THREAD: | ||
if await self.store.event_includes_relation(relation.parent_id): | ||
raise SynapseError( | ||
400, "Cannot start threads from an event with a relation" | ||
) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,7 +21,7 @@ | |
|
||
from synapse.api.constants import EventTypes, Membership, RelationTypes | ||
from synapse.event_auth import get_user_power_level | ||
from synapse.events import EventBase | ||
from synapse.events import EventBase, relation_from_event | ||
from synapse.events.snapshot import EventContext | ||
from synapse.state import POWER_KEY | ||
from synapse.storage.databases.main.roommember import EventIdMembership | ||
|
@@ -78,8 +78,8 @@ def _should_count_as_unread(event: EventBase, context: EventContext) -> bool: | |
return False | ||
|
||
# Exclude edits. | ||
relates_to = event.content.get("m.relates_to", {}) | ||
if relates_to.get("rel_type") == RelationTypes.REPLACE: | ||
Comment on lines
-81
to
-82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This didn't properly handle a non-dict |
||
relates_to = relation_from_event(event) | ||
if relates_to and relates_to.rel_type == RelationTypes.REPLACE: | ||
return False | ||
|
||
# Mark events that have a non-empty string body as unread. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,8 +36,8 @@ | |
import synapse.metrics | ||
from synapse.api.constants import EventContentFields, EventTypes, RelationTypes | ||
from synapse.api.room_versions import RoomVersions | ||
from synapse.events import EventBase # noqa: F401 | ||
from synapse.events.snapshot import EventContext # noqa: F401 | ||
from synapse.events import EventBase, relation_from_event | ||
from synapse.events.snapshot import EventContext | ||
from synapse.storage._base import db_to_json, make_in_list_sql_clause | ||
from synapse.storage.database import ( | ||
DatabasePool, | ||
|
@@ -1807,52 +1807,45 @@ def _handle_event_relations( | |
txn: The current database transaction. | ||
event: The event which might have relations. | ||
""" | ||
relation = event.content.get("m.relates_to") | ||
relation = relation_from_event(event) | ||
if not relation: | ||
# No relations | ||
# No relation, nothing to do. | ||
return | ||
|
||
# Relations must have a type and parent event ID. | ||
rel_type = relation.get("rel_type") | ||
if not isinstance(rel_type, str): | ||
return | ||
|
||
parent_id = relation.get("event_id") | ||
if not isinstance(parent_id, str): | ||
return | ||
|
||
# Annotations have a key field. | ||
aggregation_key = None | ||
if rel_type == RelationTypes.ANNOTATION: | ||
aggregation_key = relation.get("key") | ||
Comment on lines
-1824
to
-1827
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't properly handle non-string keys. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, even after this change it would allow for invalid keys (but just set them to null). I'm unsure if that's "OK" or not. (It seems better than trying to persist a dictionary or other odd data types though!) |
||
|
||
self.db_pool.simple_insert_txn( | ||
txn, | ||
table="event_relations", | ||
values={ | ||
"event_id": event.event_id, | ||
"relates_to_id": parent_id, | ||
"relation_type": rel_type, | ||
"aggregation_key": aggregation_key, | ||
"relates_to_id": relation.parent_id, | ||
"relation_type": relation.rel_type, | ||
"aggregation_key": relation.aggregation_key, | ||
}, | ||
) | ||
|
||
txn.call_after(self.store.get_relations_for_event.invalidate, (parent_id,)) | ||
txn.call_after( | ||
self.store.get_aggregation_groups_for_event.invalidate, (parent_id,) | ||
self.store.get_relations_for_event.invalidate, (relation.parent_id,) | ||
) | ||
txn.call_after( | ||
self.store.get_aggregation_groups_for_event.invalidate, | ||
(relation.parent_id,), | ||
) | ||
|
||
if rel_type == RelationTypes.REPLACE: | ||
txn.call_after(self.store.get_applicable_edit.invalidate, (parent_id,)) | ||
if relation.rel_type == RelationTypes.REPLACE: | ||
txn.call_after( | ||
self.store.get_applicable_edit.invalidate, (relation.parent_id,) | ||
) | ||
|
||
if rel_type == RelationTypes.THREAD: | ||
txn.call_after(self.store.get_thread_summary.invalidate, (parent_id,)) | ||
if relation.rel_type == RelationTypes.THREAD: | ||
txn.call_after( | ||
self.store.get_thread_summary.invalidate, (relation.parent_id,) | ||
) | ||
# It should be safe to only invalidate the cache if the user has not | ||
# previously participated in the thread, but that's difficult (and | ||
# potentially error-prone) so it is always invalidated. | ||
txn.call_after( | ||
self.store.get_thread_participated.invalidate, | ||
(parent_id, event.sender), | ||
(relation.parent_id, event.sender), | ||
) | ||
|
||
def _handle_insertion_event( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This didn't properly ignore non-string fields.