Skip to content
Snippets Groups Projects
Unverified Commit 9526aa96 authored by Erik Johnston's avatar Erik Johnston Committed by GitHub
Browse files

Merge pull request #5212 from matrix-org/erikj/deny_multiple_reactions

Block attempts to annotate the same event twice
parents 17f68048 44b8ba48
No related branches found
No related tags found
No related merge requests found
Add experimental support for relations (aka reactions and edits).
...@@ -22,7 +22,7 @@ from canonicaljson import encode_canonical_json, json ...@@ -22,7 +22,7 @@ from canonicaljson import encode_canonical_json, json
from twisted.internet import defer from twisted.internet import defer
from twisted.internet.defer import succeed 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 ( from synapse.api.errors import (
AuthError, AuthError,
Codes, Codes,
...@@ -601,6 +601,20 @@ class EventCreationHandler(object): ...@@ -601,6 +601,20 @@ class EventCreationHandler(object):
self.validator.validate_new(event) 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( logger.debug(
"Created event %s", "Created event %s",
event.event_id, event.event_id,
......
...@@ -350,9 +350,7 @@ class RelationsWorkerStore(SQLBaseStore): ...@@ -350,9 +350,7 @@ class RelationsWorkerStore(SQLBaseStore):
""" """
def _get_applicable_edit_txn(txn): def _get_applicable_edit_txn(txn):
txn.execute( txn.execute(sql, (event_id, RelationTypes.REPLACE))
sql, (event_id, RelationTypes.REPLACE,)
)
row = txn.fetchone() row = txn.fetchone()
if row: if row:
return row[0] return row[0]
...@@ -367,6 +365,50 @@ class RelationsWorkerStore(SQLBaseStore): ...@@ -367,6 +365,50 @@ class RelationsWorkerStore(SQLBaseStore):
edit_event = yield self.get_event(edit_id, allow_none=True) edit_event = yield self.get_event(edit_id, allow_none=True)
defer.returnValue(edit_event) 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): class RelationsStore(RelationsWorkerStore):
def _handle_event_relations(self, txn, event): def _handle_event_relations(self, txn, event):
......
...@@ -90,6 +90,15 @@ class RelationsTestCase(unittest.HomeserverTestCase): ...@@ -90,6 +90,15 @@ class RelationsTestCase(unittest.HomeserverTestCase):
channel = self._send_relation(RelationTypes.ANNOTATION, EventTypes.Member) channel = self._send_relation(RelationTypes.ANNOTATION, EventTypes.Member)
self.assertEquals(400, channel.code, channel.json_body) 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): def test_basic_paginate_relations(self):
"""Tests that calling pagination API corectly the latest relations. """Tests that calling pagination API corectly the latest relations.
""" """
...@@ -234,14 +243,30 @@ class RelationsTestCase(unittest.HomeserverTestCase): ...@@ -234,14 +243,30 @@ class RelationsTestCase(unittest.HomeserverTestCase):
"""Test that we can paginate within an annotation group. """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 = [] expected_event_ids = []
for _ in range(10): for _ in range(10):
channel = self._send_relation( 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) self.assertEquals(200, channel.code, channel.json_body)
expected_event_ids.append(channel.json_body["event_id"]) 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 # 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") channel = self._send_relation(RelationTypes.ANNOTATION, "m.reaction", key="a")
self.assertEquals(200, channel.code, channel.json_body) self.assertEquals(200, channel.code, channel.json_body)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment