mirror of
https://github.com/element-hq/synapse.git
synced 2024-12-14 11:57:44 +00:00
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.
This commit is contained in:
parent
a423f45294
commit
719488dda8
4 changed files with 152 additions and 15 deletions
1
changelog.d/11866.feature
Normal file
1
changelog.d/11866.feature
Normal file
|
@ -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,
|
require_consent: bool = True,
|
||||||
outlier: bool = False,
|
outlier: bool = False,
|
||||||
historical: bool = False,
|
historical: bool = False,
|
||||||
|
origin_server_ts: Optional[int] = None,
|
||||||
) -> Tuple[str, int]:
|
) -> Tuple[str, int]:
|
||||||
"""
|
"""
|
||||||
Internal membership update function to get an existing event or create
|
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
|
historical: Indicates whether the message is being inserted
|
||||||
back in time around some existing events. This is used to skip
|
back in time around some existing events. This is used to skip
|
||||||
a few checks and mark the event as backfilled.
|
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:
|
Returns:
|
||||||
Tuple of event ID and stream ordering position
|
Tuple of event ID and stream ordering position
|
||||||
|
@ -399,6 +402,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
|
||||||
"state_key": user_id,
|
"state_key": user_id,
|
||||||
# For backwards compatibility:
|
# For backwards compatibility:
|
||||||
"membership": membership,
|
"membership": membership,
|
||||||
|
"origin_server_ts": origin_server_ts,
|
||||||
},
|
},
|
||||||
txn_id=txn_id,
|
txn_id=txn_id,
|
||||||
allow_no_prev_events=allow_no_prev_events,
|
allow_no_prev_events=allow_no_prev_events,
|
||||||
|
@ -504,6 +508,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
|
||||||
prev_event_ids: Optional[List[str]] = None,
|
prev_event_ids: Optional[List[str]] = None,
|
||||||
state_event_ids: Optional[List[str]] = None,
|
state_event_ids: Optional[List[str]] = None,
|
||||||
depth: Optional[int] = None,
|
depth: Optional[int] = None,
|
||||||
|
origin_server_ts: Optional[int] = None,
|
||||||
) -> Tuple[str, int]:
|
) -> Tuple[str, int]:
|
||||||
"""Update a user's membership in a room.
|
"""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.
|
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
|
Should normally be set to None, which will cause the depth to be calculated
|
||||||
based on the prev_events.
|
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:
|
Returns:
|
||||||
A tuple of the new event ID and stream ID.
|
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,
|
prev_event_ids=prev_event_ids,
|
||||||
state_event_ids=state_event_ids,
|
state_event_ids=state_event_ids,
|
||||||
depth=depth,
|
depth=depth,
|
||||||
|
origin_server_ts=origin_server_ts,
|
||||||
)
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
@ -606,6 +614,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
|
||||||
prev_event_ids: Optional[List[str]] = None,
|
prev_event_ids: Optional[List[str]] = None,
|
||||||
state_event_ids: Optional[List[str]] = None,
|
state_event_ids: Optional[List[str]] = None,
|
||||||
depth: Optional[int] = None,
|
depth: Optional[int] = None,
|
||||||
|
origin_server_ts: Optional[int] = None,
|
||||||
) -> Tuple[str, int]:
|
) -> Tuple[str, int]:
|
||||||
"""Helper for update_membership.
|
"""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.
|
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
|
Should normally be set to None, which will cause the depth to be calculated
|
||||||
based on the prev_events.
|
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:
|
Returns:
|
||||||
A tuple of the new event ID and stream ID.
|
A tuple of the new event ID and stream ID.
|
||||||
|
@ -785,6 +796,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
|
||||||
require_consent=require_consent,
|
require_consent=require_consent,
|
||||||
outlier=outlier,
|
outlier=outlier,
|
||||||
historical=historical,
|
historical=historical,
|
||||||
|
origin_server_ts=origin_server_ts,
|
||||||
)
|
)
|
||||||
|
|
||||||
latest_event_ids = await self.store.get_prev_events_for_room(room_id)
|
latest_event_ids = await self.store.get_prev_events_for_room(room_id)
|
||||||
|
@ -1030,6 +1042,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
|
||||||
content=content,
|
content=content,
|
||||||
require_consent=require_consent,
|
require_consent=require_consent,
|
||||||
outlier=outlier,
|
outlier=outlier,
|
||||||
|
origin_server_ts=origin_server_ts,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _should_perform_remote_join(
|
async def _should_perform_remote_join(
|
||||||
|
|
|
@ -268,15 +268,9 @@ class RoomStateEventRestServlet(TransactionRestServlet):
|
||||||
|
|
||||||
content = parse_json_object_from_request(request)
|
content = parse_json_object_from_request(request)
|
||||||
|
|
||||||
event_dict = {
|
origin_server_ts = None
|
||||||
"type": event_type,
|
if requester.app_service:
|
||||||
"content": content,
|
origin_server_ts = parse_integer(request, "ts")
|
||||||
"room_id": room_id,
|
|
||||||
"sender": requester.user.to_string(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if state_key is not None:
|
|
||||||
event_dict["state_key"] = state_key
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if event_type == EventTypes.Member:
|
if event_type == EventTypes.Member:
|
||||||
|
@ -287,8 +281,22 @@ class RoomStateEventRestServlet(TransactionRestServlet):
|
||||||
room_id=room_id,
|
room_id=room_id,
|
||||||
action=membership,
|
action=membership,
|
||||||
content=content,
|
content=content,
|
||||||
|
origin_server_ts=origin_server_ts,
|
||||||
)
|
)
|
||||||
else:
|
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,
|
event,
|
||||||
_,
|
_,
|
||||||
|
@ -333,10 +341,10 @@ class RoomSendEventRestServlet(TransactionRestServlet):
|
||||||
"sender": requester.user.to_string(),
|
"sender": requester.user.to_string(),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Twisted will have processed the args by now.
|
if requester.app_service:
|
||||||
assert request.args is not None
|
origin_server_ts = parse_integer(request, "ts")
|
||||||
if b"ts" in request.args and requester.app_service:
|
if origin_server_ts is not None:
|
||||||
event_dict["origin_server_ts"] = parse_integer(request, "ts", 0)
|
event_dict["origin_server_ts"] = origin_server_ts
|
||||||
|
|
||||||
try:
|
try:
|
||||||
(
|
(
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
import json
|
import json
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
|
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 urllib import parse as urlparse
|
||||||
|
|
||||||
from parameterized import param, parameterized
|
from parameterized import param, parameterized
|
||||||
|
@ -39,9 +39,10 @@ from synapse.api.constants import (
|
||||||
RoomTypes,
|
RoomTypes,
|
||||||
)
|
)
|
||||||
from synapse.api.errors import Codes, HttpResponseException
|
from synapse.api.errors import Codes, HttpResponseException
|
||||||
|
from synapse.appservice import ApplicationService
|
||||||
from synapse.handlers.pagination import PurgeStatus
|
from synapse.handlers.pagination import PurgeStatus
|
||||||
from synapse.rest import admin
|
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.server import HomeServer
|
||||||
from synapse.types import JsonDict, RoomAlias, UserID, create_requester
|
from synapse.types import JsonDict, RoomAlias, UserID, create_requester
|
||||||
from synapse.util import Clock
|
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):
|
class RoomJoinRatelimitTestCase(RoomBase):
|
||||||
user_id = "@sid1:red"
|
user_id = "@sid1:red"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue