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

Block attempts to annotate the same event twice #5212

Merged
merged 3 commits into from
May 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/5212.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add experimental support for relations (aka reactions and edits).
16 changes: 15 additions & 1 deletion synapse/handlers/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from twisted.internet import defer
from twisted.internet.defer import succeed

from synapse.api.constants import EventTypes, Membership
from synapse.api.constants import EventTypes, Membership, RelationTypes
from synapse.api.errors import (
AuthError,
Codes,
Expand Down Expand Up @@ -601,6 +601,20 @@ def create_new_client_event(self, builder, requester=None,

self.validator.validate_new(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).
relation = event.content.get("m.relates_to", {})
if relation.get("rel_type") == RelationTypes.ANNOTATION:
relates_to = relation["event_id"]
aggregation_key = relation["key"]

already_exists = yield self.store.has_user_annotated_event(
relates_to, event.type, aggregation_key, event.sender,
)
if already_exists:
raise SynapseError(400, "Can't send same reaction twice")

logger.debug(
"Created event %s",
event.event_id,
Expand Down
48 changes: 45 additions & 3 deletions synapse/storage/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,9 +350,7 @@ def get_applicable_edit(self, event_id):
"""

def _get_applicable_edit_txn(txn):
txn.execute(
sql, (event_id, RelationTypes.REPLACE,)
)
txn.execute(sql, (event_id, RelationTypes.REPLACE))
row = txn.fetchone()
if row:
return row[0]
Expand All @@ -367,6 +365,50 @@ def _get_applicable_edit_txn(txn):
edit_event = yield self.get_event(edit_id, allow_none=True)
defer.returnValue(edit_event)

def has_user_annotated_event(self, parent_id, event_type, aggregation_key, sender):
"""Check if a user has already annotated an event with the same key
(e.g. already liked an event).

Args:
parent_id (str): The event being annotated
event_type (str): The event type of the annotation
aggregation_key (str): The aggregation key of the annotation
sender (str): The sender of the annotation

Returns:
Deferred[bool]
"""

sql = """
SELECT 1 FROM event_relations
INNER JOIN events USING (event_id)
WHERE
relates_to_id = ?
AND relation_type = ?
AND type = ?
AND sender = ?
AND aggregation_key = ?
LIMIT 1;
"""

def _get_if_user_has_annotated_event(txn):
txn.execute(
sql,
(
parent_id,
RelationTypes.ANNOTATION,
event_type,
sender,
aggregation_key,
),
)

return bool(txn.fetchone())

return self.runInteraction(
"get_if_user_has_annotated_event", _get_if_user_has_annotated_event
)


class RelationsStore(RelationsWorkerStore):
def _handle_event_relations(self, txn, event):
Expand Down
27 changes: 26 additions & 1 deletion tests/rest/client/v2_alpha/test_relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ def test_deny_membership(self):
channel = self._send_relation(RelationTypes.ANNOTATION, EventTypes.Member)
self.assertEquals(400, channel.code, channel.json_body)

def test_deny_double_react(self):
"""Test that we deny relations on membership events
"""
channel = self._send_relation(RelationTypes.ANNOTATION, "m.reaction", "a")
self.assertEquals(200, channel.code, channel.json_body)

channel = self._send_relation(RelationTypes.ANNOTATION, "m.reaction", "a")
self.assertEquals(400, channel.code, channel.json_body)

def test_basic_paginate_relations(self):
"""Tests that calling pagination API corectly the latest relations.
"""
Expand Down Expand Up @@ -234,14 +243,30 @@ def test_aggregation_pagination_within_group(self):
"""Test that we can paginate within an annotation group.
"""

# We need to create ten separate users to send each reaction.
access_tokens = [self.user_token, self.user2_token]
idx = 0
while len(access_tokens) < 10:
user_id, token = self._create_user("test" + str(idx))
idx += 1

self.helper.join(self.room, user=user_id, tok=token)
access_tokens.append(token)

idx = 0
expected_event_ids = []
for _ in range(10):
channel = self._send_relation(
RelationTypes.ANNOTATION, "m.reaction", key=u"👍"
RelationTypes.ANNOTATION,
"m.reaction",
key=u"👍",
access_token=access_tokens[idx],
)
self.assertEquals(200, channel.code, channel.json_body)
expected_event_ids.append(channel.json_body["event_id"])

idx += 1

# Also send a different type of reaction so that we test we don't see it
channel = self._send_relation(RelationTypes.ANNOTATION, "m.reaction", key="a")
self.assertEquals(200, channel.code, channel.json_body)
Expand Down