Add query parameter `ts` to allow appservices set the `origin_server_ts` for state events. (#11866)

MSC3316 declares that both /rooms/{roomId}/send and /rooms/{roomId}/state
should accept a ts parameter for appservices. This change expands support
to /state and adds tests.
1.103.0-whithout-watcha
lukasdenk 2 years ago committed by GitHub
parent a423f45294
commit 719488dda8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      changelog.d/11866.feature
  2. 13
      synapse/handlers/room_member.py
  3. 34
      synapse/rest/client/room.py
  4. 119
      tests/rest/client/test_rooms.py

@ -0,0 +1 @@
Allow application services to set the `origin_server_ts` of a state event by providing the query parameter `ts` in `PUT /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}`, per [MSC3316](https://github.com/matrix-org/matrix-doc/pull/3316). Contributed by @lukasdenk.

@ -322,6 +322,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
require_consent: bool = True,
outlier: bool = False,
historical: bool = False,
origin_server_ts: Optional[int] = None,
) -> Tuple[str, int]:
"""
Internal membership update function to get an existing event or create
@ -361,6 +362,8 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
historical: Indicates whether the message is being inserted
back in time around some existing events. This is used to skip
a few checks and mark the event as backfilled.
origin_server_ts: The origin_server_ts to use if a new event is created. Uses
the current timestamp if set to None.
Returns:
Tuple of event ID and stream ordering position
@ -399,6 +402,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
"state_key": user_id,
# For backwards compatibility:
"membership": membership,
"origin_server_ts": origin_server_ts,
},
txn_id=txn_id,
allow_no_prev_events=allow_no_prev_events,
@ -504,6 +508,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
prev_event_ids: Optional[List[str]] = None,
state_event_ids: Optional[List[str]] = None,
depth: Optional[int] = None,
origin_server_ts: Optional[int] = None,
) -> Tuple[str, int]:
"""Update a user's membership in a room.
@ -542,6 +547,8 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
depth: Override the depth used to order the event in the DAG.
Should normally be set to None, which will cause the depth to be calculated
based on the prev_events.
origin_server_ts: The origin_server_ts to use if a new event is created. Uses
the current timestamp if set to None.
Returns:
A tuple of the new event ID and stream ID.
@ -583,6 +590,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
prev_event_ids=prev_event_ids,
state_event_ids=state_event_ids,
depth=depth,
origin_server_ts=origin_server_ts,
)
return result
@ -606,6 +614,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
prev_event_ids: Optional[List[str]] = None,
state_event_ids: Optional[List[str]] = None,
depth: Optional[int] = None,
origin_server_ts: Optional[int] = None,
) -> Tuple[str, int]:
"""Helper for update_membership.
@ -646,6 +655,8 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
depth: Override the depth used to order the event in the DAG.
Should normally be set to None, which will cause the depth to be calculated
based on the prev_events.
origin_server_ts: The origin_server_ts to use if a new event is created. Uses
the current timestamp if set to None.
Returns:
A tuple of the new event ID and stream ID.
@ -785,6 +796,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
require_consent=require_consent,
outlier=outlier,
historical=historical,
origin_server_ts=origin_server_ts,
)
latest_event_ids = await self.store.get_prev_events_for_room(room_id)
@ -1030,6 +1042,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
content=content,
require_consent=require_consent,
outlier=outlier,
origin_server_ts=origin_server_ts,
)
async def _should_perform_remote_join(

@ -268,15 +268,9 @@ class RoomStateEventRestServlet(TransactionRestServlet):
content = parse_json_object_from_request(request)
event_dict = {
"type": event_type,
"content": content,
"room_id": room_id,
"sender": requester.user.to_string(),
}
if state_key is not None:
event_dict["state_key"] = state_key
origin_server_ts = None
if requester.app_service:
origin_server_ts = parse_integer(request, "ts")
try:
if event_type == EventTypes.Member:
@ -287,8 +281,22 @@ class RoomStateEventRestServlet(TransactionRestServlet):
room_id=room_id,
action=membership,
content=content,
origin_server_ts=origin_server_ts,
)
else:
event_dict: JsonDict = {
"type": event_type,
"content": content,
"room_id": room_id,
"sender": requester.user.to_string(),
}
if state_key is not None:
event_dict["state_key"] = state_key
if origin_server_ts is not None:
event_dict["origin_server_ts"] = origin_server_ts
(
event,
_,
@ -333,10 +341,10 @@ class RoomSendEventRestServlet(TransactionRestServlet):
"sender": requester.user.to_string(),
}
# Twisted will have processed the args by now.
assert request.args is not None
if b"ts" in request.args and requester.app_service:
event_dict["origin_server_ts"] = parse_integer(request, "ts", 0)
if requester.app_service:
origin_server_ts = parse_integer(request, "ts")
if origin_server_ts is not None:
event_dict["origin_server_ts"] = origin_server_ts
try:
(

@ -20,7 +20,7 @@
import json
from http import HTTPStatus
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
from unittest.mock import Mock, call
from unittest.mock import Mock, call, patch
from urllib import parse as urlparse
from parameterized import param, parameterized
@ -39,9 +39,10 @@ from synapse.api.constants import (
RoomTypes,
)
from synapse.api.errors import Codes, HttpResponseException
from synapse.appservice import ApplicationService
from synapse.handlers.pagination import PurgeStatus
from synapse.rest import admin
from synapse.rest.client import account, directory, login, profile, room, sync
from synapse.rest.client import account, directory, login, profile, register, room, sync
from synapse.server import HomeServer
from synapse.types import JsonDict, RoomAlias, UserID, create_requester
from synapse.util import Clock
@ -1252,6 +1253,120 @@ class RoomJoinTestCase(RoomBase):
)
class RoomAppserviceTsParamTestCase(unittest.HomeserverTestCase):
servlets = [
room.register_servlets,
synapse.rest.admin.register_servlets,
register.register_servlets,
]
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
self.appservice_user, _ = self.register_appservice_user(
"as_user_potato", self.appservice.token
)
# Create a room as the appservice user.
args = {
"access_token": self.appservice.token,
"user_id": self.appservice_user,
}
channel = self.make_request(
"POST",
f"/_matrix/client/r0/createRoom?{urlparse.urlencode(args)}",
content={"visibility": "public"},
)
assert channel.code == 200
self.room = channel.json_body["room_id"]
self.main_store = self.hs.get_datastores().main
def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
config = self.default_config()
self.appservice = ApplicationService(
token="i_am_an_app_service",
id="1234",
namespaces={"users": [{"regex": r"@as_user.*", "exclusive": True}]},
# Note: this user does not have to match the regex above
sender="@as_main:test",
)
mock_load_appservices = Mock(return_value=[self.appservice])
with patch(
"synapse.storage.databases.main.appservice.load_appservices",
mock_load_appservices,
):
hs = self.setup_test_homeserver(config=config)
return hs
def test_send_event_ts(self) -> None:
"""Test sending a non-state event with a custom timestamp."""
ts = 1
url_params = {
"user_id": self.appservice_user,
"ts": ts,
}
channel = self.make_request(
"PUT",
path=f"/_matrix/client/r0/rooms/{self.room}/send/m.room.message/1234?"
+ urlparse.urlencode(url_params),
content={"body": "test", "msgtype": "m.text"},
access_token=self.appservice.token,
)
self.assertEqual(channel.code, 200, channel.json_body)
event_id = channel.json_body["event_id"]
# Ensure the event was persisted with the correct timestamp.
res = self.get_success(self.main_store.get_event(event_id))
self.assertEquals(ts, res.origin_server_ts)
def test_send_state_event_ts(self) -> None:
"""Test sending a state event with a custom timestamp."""
ts = 1
url_params = {
"user_id": self.appservice_user,
"ts": ts,
}
channel = self.make_request(
"PUT",
path=f"/_matrix/client/r0/rooms/{self.room}/state/m.room.name?"
+ urlparse.urlencode(url_params),
content={"name": "test"},
access_token=self.appservice.token,
)
self.assertEqual(channel.code, 200, channel.json_body)
event_id = channel.json_body["event_id"]
# Ensure the event was persisted with the correct timestamp.
res = self.get_success(self.main_store.get_event(event_id))
self.assertEquals(ts, res.origin_server_ts)
def test_send_membership_event_ts(self) -> None:
"""Test sending a membership event with a custom timestamp."""
ts = 1
url_params = {
"user_id": self.appservice_user,
"ts": ts,
}
channel = self.make_request(
"PUT",
path=f"/_matrix/client/r0/rooms/{self.room}/state/m.room.member/{self.appservice_user}?"
+ urlparse.urlencode(url_params),
content={"membership": "join", "display_name": "test"},
access_token=self.appservice.token,
)
self.assertEqual(channel.code, 200, channel.json_body)
event_id = channel.json_body["event_id"]
# Ensure the event was persisted with the correct timestamp.
res = self.get_success(self.main_store.get_event(event_id))
self.assertEquals(ts, res.origin_server_ts)
class RoomJoinRatelimitTestCase(RoomBase):
user_id = "@sid1:red"

Loading…
Cancel
Save