Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3faa0974be | |||
| 858ef5e144 | |||
| 5ffd68dca1 | |||
| f1c9ded738 | |||
| 97659b7489 | |||
| c570f24acc | |||
| eadfda3ebc | |||
| 0a4e541dc5 | |||
| b79d69796c | |||
| 393a811a41 | |||
| 2df82ae451 |
+27
@@ -1,3 +1,30 @@
|
||||
Synapse 1.19.2 (2020-09-16)
|
||||
===========================
|
||||
|
||||
Due to the issue below server admins are encouraged to upgrade as soon as possible.
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Fix joining rooms over federation that include malformed events. ([\#8324](https://github.com/matrix-org/synapse/issues/8324))
|
||||
|
||||
|
||||
Synapse 1.19.1 (2020-08-27)
|
||||
===========================
|
||||
|
||||
No significant changes.
|
||||
|
||||
|
||||
Synapse 1.19.1rc1 (2020-08-25)
|
||||
==============================
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Fix a bug introduced in v1.19.0 where appservices with ratelimiting disabled would still be ratelimited when joining rooms. ([\#8139](https://github.com/matrix-org/synapse/issues/8139))
|
||||
- Fix a bug introduced in v1.19.0 that would cause e.g. profile updates to fail due to incorrect application of rate limits on join requests. ([\#8153](https://github.com/matrix-org/synapse/issues/8153))
|
||||
|
||||
|
||||
Synapse 1.19.0 (2020-08-17)
|
||||
===========================
|
||||
|
||||
|
||||
Vendored
+12
@@ -1,3 +1,15 @@
|
||||
matrix-synapse-py3 (1.19.2) stable; urgency=medium
|
||||
|
||||
* New synapse release 1.19.2.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Wed, 16 Sep 2020 12:50:30 +0100
|
||||
|
||||
matrix-synapse-py3 (1.19.1) stable; urgency=medium
|
||||
|
||||
* New synapse release 1.19.1.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Thu, 27 Aug 2020 10:50:19 +0100
|
||||
|
||||
matrix-synapse-py3 (1.19.0) stable; urgency=medium
|
||||
|
||||
[ Synapse Packaging team ]
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
__version__ = "1.19.0"
|
||||
__version__ = "1.19.2"
|
||||
|
||||
if bool(os.environ.get("SYNAPSE_TEST_PATCH_LOG_CONTEXTS", False)):
|
||||
# We import here so that we don't have to install a bunch of deps when
|
||||
|
||||
@@ -17,6 +17,7 @@ from collections import OrderedDict
|
||||
from typing import Any, Optional, Tuple
|
||||
|
||||
from synapse.api.errors import LimitExceededError
|
||||
from synapse.types import Requester
|
||||
from synapse.util import Clock
|
||||
|
||||
|
||||
@@ -43,6 +44,42 @@ class Ratelimiter(object):
|
||||
# * The rate_hz of this particular entry. This can vary per request
|
||||
self.actions = OrderedDict() # type: OrderedDict[Any, Tuple[float, int, float]]
|
||||
|
||||
def can_requester_do_action(
|
||||
self,
|
||||
requester: Requester,
|
||||
rate_hz: Optional[float] = None,
|
||||
burst_count: Optional[int] = None,
|
||||
update: bool = True,
|
||||
_time_now_s: Optional[int] = None,
|
||||
) -> Tuple[bool, float]:
|
||||
"""Can the requester perform the action?
|
||||
|
||||
Args:
|
||||
requester: The requester to key off when rate limiting. The user property
|
||||
will be used.
|
||||
rate_hz: The long term number of actions that can be performed in a second.
|
||||
Overrides the value set during instantiation if set.
|
||||
burst_count: How many actions that can be performed before being limited.
|
||||
Overrides the value set during instantiation if set.
|
||||
update: Whether to count this check as performing the action
|
||||
_time_now_s: The current time. Optional, defaults to the current time according
|
||||
to self.clock. Only used by tests.
|
||||
|
||||
Returns:
|
||||
A tuple containing:
|
||||
* A bool indicating if they can perform the action now
|
||||
* The reactor timestamp for when the action can be performed next.
|
||||
-1 if rate_hz is less than or equal to zero
|
||||
"""
|
||||
# Disable rate limiting of users belonging to any AS that is configured
|
||||
# not to be rate limited in its registration file (rate_limited: true|false).
|
||||
if requester.app_service and not requester.app_service.is_rate_limited():
|
||||
return True, -1.0
|
||||
|
||||
return self.can_do_action(
|
||||
requester.user.to_string(), rate_hz, burst_count, update, _time_now_s
|
||||
)
|
||||
|
||||
def can_do_action(
|
||||
self,
|
||||
key: Any,
|
||||
|
||||
@@ -54,7 +54,7 @@ from synapse.events import EventBase, builder
|
||||
from synapse.federation.federation_base import FederationBase, event_from_pdu_json
|
||||
from synapse.logging.context import make_deferred_yieldable, preserve_fn
|
||||
from synapse.logging.utils import log_function
|
||||
from synapse.types import JsonDict
|
||||
from synapse.types import JsonDict, get_domain_from_id
|
||||
from synapse.util import unwrapFirstError
|
||||
from synapse.util.caches.expiringcache import ExpiringCache
|
||||
from synapse.util.retryutils import NotRetryingDestination
|
||||
@@ -217,11 +217,8 @@ class FederationClient(FederationBase):
|
||||
for p in transaction_data["pdus"]
|
||||
]
|
||||
|
||||
# FIXME: We should handle signature failures more gracefully.
|
||||
pdus[:] = await make_deferred_yieldable(
|
||||
defer.gatherResults(
|
||||
self._check_sigs_and_hashes(room_version, pdus), consumeErrors=True,
|
||||
).addErrback(unwrapFirstError)
|
||||
pdus[:] = await self._check_sigs_and_hash_and_fetch(
|
||||
dest, pdus, outlier=True, room_version=room_version
|
||||
)
|
||||
|
||||
return pdus
|
||||
@@ -386,10 +383,11 @@ class FederationClient(FederationBase):
|
||||
pdu.event_id, allow_rejected=True, allow_none=True
|
||||
)
|
||||
|
||||
if not res and pdu.origin != origin:
|
||||
pdu_origin = get_domain_from_id(pdu.sender)
|
||||
if not res and pdu_origin != origin:
|
||||
try:
|
||||
res = await self.get_pdu(
|
||||
destinations=[pdu.origin],
|
||||
destinations=[pdu_origin],
|
||||
event_id=pdu.event_id,
|
||||
room_version=room_version,
|
||||
outlier=outlier,
|
||||
|
||||
@@ -937,9 +937,18 @@ class FederationHandler(BaseHandler):
|
||||
|
||||
return events
|
||||
|
||||
async def maybe_backfill(self, room_id, current_depth):
|
||||
async def maybe_backfill(self, room_id: str, current_depth: int, limit: int):
|
||||
"""Checks the database to see if we should backfill before paginating,
|
||||
and if so do.
|
||||
|
||||
Args:
|
||||
room_id
|
||||
current_depth: The depth from which we're paginating from. This is
|
||||
used to decide if we should backfill and what extremities to
|
||||
use.
|
||||
limit: The number of events that the pagination request will
|
||||
return. This is used as part of the heuristic to decide if we
|
||||
should back paginate.
|
||||
"""
|
||||
extremities = await self.store.get_oldest_events_with_depth_in_room(room_id)
|
||||
|
||||
@@ -998,16 +1007,47 @@ class FederationHandler(BaseHandler):
|
||||
sorted_extremeties_tuple = sorted(extremities.items(), key=lambda e: -int(e[1]))
|
||||
max_depth = sorted_extremeties_tuple[0][1]
|
||||
|
||||
# If we're approaching an extremity we trigger a backfill, otherwise we
|
||||
# no-op.
|
||||
if current_depth - 2 * limit > max_depth:
|
||||
logger.debug(
|
||||
"Not backfilling as we don't need to. %d < %d - 2 * %d",
|
||||
max_depth,
|
||||
current_depth,
|
||||
limit,
|
||||
)
|
||||
return
|
||||
|
||||
logger.debug(
|
||||
"room_id: %s, backfill: current_depth: %s, max_depth: %s, extrems: %s",
|
||||
room_id,
|
||||
current_depth,
|
||||
max_depth,
|
||||
sorted_extremeties_tuple,
|
||||
)
|
||||
|
||||
# We ignore extremities that have a greater depth than our current depth
|
||||
# as:
|
||||
# 1. we don't really care about getting events that has happened
|
||||
# before our current position; and
|
||||
# 2. we have likely previously tried and failed to backfill from that
|
||||
# extremity, so to avoid getting "stuck" requesting the same
|
||||
# backfill repeatedly we drop those extremities.
|
||||
filtered_sorted_extremeties_tuple = [
|
||||
t for t in sorted_extremeties_tuple if int(t[1]) <= current_depth
|
||||
]
|
||||
|
||||
# However, we need to check that the filtered extremities are non-empty.
|
||||
# If they are empty then either we can a) bail or b) still attempt to
|
||||
# backill. We opt to try backfilling anyway just in case we do get
|
||||
# relevant events.
|
||||
if filtered_sorted_extremeties_tuple:
|
||||
sorted_extremeties_tuple = filtered_sorted_extremeties_tuple
|
||||
|
||||
# We don't want to specify too many extremities as it causes the backfill
|
||||
# request URI to be too long.
|
||||
extremities = dict(sorted_extremeties_tuple[:5])
|
||||
|
||||
if current_depth > max_depth:
|
||||
logger.debug(
|
||||
"Not backfilling as we don't need to. %d < %d", max_depth, current_depth
|
||||
)
|
||||
return
|
||||
|
||||
# Now we need to decide which hosts to hit first.
|
||||
|
||||
# First we try hosts that are already in the room
|
||||
|
||||
@@ -335,7 +335,7 @@ class PaginationHandler(object):
|
||||
if room_token.topological:
|
||||
max_topo = room_token.topological
|
||||
else:
|
||||
max_topo = await self.store.get_max_topological_token(
|
||||
max_topo = await self.store.get_current_topological_token(
|
||||
room_id, room_token.stream
|
||||
)
|
||||
|
||||
@@ -351,7 +351,7 @@ class PaginationHandler(object):
|
||||
source_config.from_key = str(leave_token)
|
||||
|
||||
await self.hs.get_handlers().federation_handler.maybe_backfill(
|
||||
room_id, max_topo
|
||||
room_id, max_topo, limit=pagin_config.limit,
|
||||
)
|
||||
|
||||
events, next_key = await self.store.paginate_room_events(
|
||||
|
||||
@@ -210,24 +210,40 @@ class RoomMemberHandler(object):
|
||||
_, stream_id = await self.store.get_event_ordering(duplicate.event_id)
|
||||
return duplicate.event_id, stream_id
|
||||
|
||||
stream_id = await self.event_creation_handler.handle_new_client_event(
|
||||
requester, event, context, extra_users=[target], ratelimit=ratelimit,
|
||||
)
|
||||
|
||||
prev_state_ids = await context.get_prev_state_ids()
|
||||
|
||||
prev_member_event_id = prev_state_ids.get((EventTypes.Member, user_id), None)
|
||||
|
||||
newly_joined = False
|
||||
if event.membership == Membership.JOIN:
|
||||
# Only fire user_joined_room if the user has actually joined the
|
||||
# room. Don't bother if the user is just changing their profile
|
||||
# info.
|
||||
newly_joined = True
|
||||
if prev_member_event_id:
|
||||
prev_member_event = await self.store.get_event(prev_member_event_id)
|
||||
newly_joined = prev_member_event.membership != Membership.JOIN
|
||||
|
||||
# Only rate-limit if the user actually joined the room, otherwise we'll end
|
||||
# up blocking profile updates.
|
||||
if newly_joined:
|
||||
await self._user_joined_room(target, room_id)
|
||||
time_now_s = self.clock.time()
|
||||
(
|
||||
allowed,
|
||||
time_allowed,
|
||||
) = self._join_rate_limiter_local.can_requester_do_action(requester)
|
||||
|
||||
if not allowed:
|
||||
raise LimitExceededError(
|
||||
retry_after_ms=int(1000 * (time_allowed - time_now_s))
|
||||
)
|
||||
|
||||
stream_id = await self.event_creation_handler.handle_new_client_event(
|
||||
requester, event, context, extra_users=[target], ratelimit=ratelimit,
|
||||
)
|
||||
|
||||
if event.membership == Membership.JOIN and newly_joined:
|
||||
# Only fire user_joined_room if the user has actually joined the
|
||||
# room. Don't bother if the user is just changing their profile
|
||||
# info.
|
||||
await self._user_joined_room(target, room_id)
|
||||
elif event.membership == Membership.LEAVE:
|
||||
if prev_member_event_id:
|
||||
prev_member_event = await self.store.get_event(prev_member_event_id)
|
||||
@@ -457,22 +473,12 @@ class RoomMemberHandler(object):
|
||||
# so don't really fit into the general auth process.
|
||||
raise AuthError(403, "Guest access not allowed")
|
||||
|
||||
if is_host_in_room:
|
||||
if not is_host_in_room:
|
||||
time_now_s = self.clock.time()
|
||||
allowed, time_allowed = self._join_rate_limiter_local.can_do_action(
|
||||
requester.user.to_string(),
|
||||
)
|
||||
|
||||
if not allowed:
|
||||
raise LimitExceededError(
|
||||
retry_after_ms=int(1000 * (time_allowed - time_now_s))
|
||||
)
|
||||
|
||||
else:
|
||||
time_now_s = self.clock.time()
|
||||
allowed, time_allowed = self._join_rate_limiter_remote.can_do_action(
|
||||
requester.user.to_string(),
|
||||
)
|
||||
(
|
||||
allowed,
|
||||
time_allowed,
|
||||
) = self._join_rate_limiter_remote.can_requester_do_action(requester,)
|
||||
|
||||
if not allowed:
|
||||
raise LimitExceededError(
|
||||
|
||||
@@ -605,23 +605,20 @@ class StreamWorkerStore(EventsWorkerStore, SQLBaseStore):
|
||||
lambda row: "t%d-%d" % (row["topological_ordering"], row["stream_ordering"])
|
||||
)
|
||||
|
||||
def get_max_topological_token(self, room_id, stream_key):
|
||||
"""Get the max topological token in a room before the given stream
|
||||
def get_current_topological_token(self, room_id, stream_key):
|
||||
"""Gets the topological token in a room after or at the given stream
|
||||
ordering.
|
||||
|
||||
Args:
|
||||
room_id (str)
|
||||
stream_key (int)
|
||||
|
||||
Returns:
|
||||
Deferred[int]
|
||||
room_id
|
||||
stream_key
|
||||
"""
|
||||
sql = (
|
||||
"SELECT coalesce(max(topological_ordering), 0) FROM events"
|
||||
" WHERE room_id = ? AND stream_ordering < ?"
|
||||
"SELECT coalesce(MIN(topological_ordering), 0) FROM events"
|
||||
" WHERE room_id = ? AND stream_ordering >= ?"
|
||||
)
|
||||
return self.db_pool.execute(
|
||||
"get_max_topological_token", None, sql, room_id, stream_key
|
||||
"get_current_topological_token", None, sql, room_id, stream_key
|
||||
).addCallback(lambda r: r[0][0] if r else 0)
|
||||
|
||||
def _get_max_topological_txn(self, txn, room_id):
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from synapse.api.ratelimiting import LimitExceededError, Ratelimiter
|
||||
from synapse.appservice import ApplicationService
|
||||
from synapse.types import create_requester
|
||||
|
||||
from tests import unittest
|
||||
|
||||
@@ -18,6 +20,77 @@ class TestRatelimiter(unittest.TestCase):
|
||||
self.assertTrue(allowed)
|
||||
self.assertEquals(20.0, time_allowed)
|
||||
|
||||
def test_allowed_user_via_can_requester_do_action(self):
|
||||
user_requester = create_requester("@user:example.com")
|
||||
limiter = Ratelimiter(clock=None, rate_hz=0.1, burst_count=1)
|
||||
allowed, time_allowed = limiter.can_requester_do_action(
|
||||
user_requester, _time_now_s=0
|
||||
)
|
||||
self.assertTrue(allowed)
|
||||
self.assertEquals(10.0, time_allowed)
|
||||
|
||||
allowed, time_allowed = limiter.can_requester_do_action(
|
||||
user_requester, _time_now_s=5
|
||||
)
|
||||
self.assertFalse(allowed)
|
||||
self.assertEquals(10.0, time_allowed)
|
||||
|
||||
allowed, time_allowed = limiter.can_requester_do_action(
|
||||
user_requester, _time_now_s=10
|
||||
)
|
||||
self.assertTrue(allowed)
|
||||
self.assertEquals(20.0, time_allowed)
|
||||
|
||||
def test_allowed_appservice_ratelimited_via_can_requester_do_action(self):
|
||||
appservice = ApplicationService(
|
||||
None, "example.com", id="foo", rate_limited=True,
|
||||
)
|
||||
as_requester = create_requester("@user:example.com", app_service=appservice)
|
||||
|
||||
limiter = Ratelimiter(clock=None, rate_hz=0.1, burst_count=1)
|
||||
allowed, time_allowed = limiter.can_requester_do_action(
|
||||
as_requester, _time_now_s=0
|
||||
)
|
||||
self.assertTrue(allowed)
|
||||
self.assertEquals(10.0, time_allowed)
|
||||
|
||||
allowed, time_allowed = limiter.can_requester_do_action(
|
||||
as_requester, _time_now_s=5
|
||||
)
|
||||
self.assertFalse(allowed)
|
||||
self.assertEquals(10.0, time_allowed)
|
||||
|
||||
allowed, time_allowed = limiter.can_requester_do_action(
|
||||
as_requester, _time_now_s=10
|
||||
)
|
||||
self.assertTrue(allowed)
|
||||
self.assertEquals(20.0, time_allowed)
|
||||
|
||||
def test_allowed_appservice_via_can_requester_do_action(self):
|
||||
appservice = ApplicationService(
|
||||
None, "example.com", id="foo", rate_limited=False,
|
||||
)
|
||||
as_requester = create_requester("@user:example.com", app_service=appservice)
|
||||
|
||||
limiter = Ratelimiter(clock=None, rate_hz=0.1, burst_count=1)
|
||||
allowed, time_allowed = limiter.can_requester_do_action(
|
||||
as_requester, _time_now_s=0
|
||||
)
|
||||
self.assertTrue(allowed)
|
||||
self.assertEquals(-1, time_allowed)
|
||||
|
||||
allowed, time_allowed = limiter.can_requester_do_action(
|
||||
as_requester, _time_now_s=5
|
||||
)
|
||||
self.assertTrue(allowed)
|
||||
self.assertEquals(-1, time_allowed)
|
||||
|
||||
allowed, time_allowed = limiter.can_requester_do_action(
|
||||
as_requester, _time_now_s=10
|
||||
)
|
||||
self.assertTrue(allowed)
|
||||
self.assertEquals(-1, time_allowed)
|
||||
|
||||
def test_allowed_via_ratelimit(self):
|
||||
limiter = Ratelimiter(clock=None, rate_hz=0.1, burst_count=1)
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ from synapse.api.constants import EventContentFields, EventTypes, Membership
|
||||
from synapse.handlers.pagination import PurgeStatus
|
||||
from synapse.rest.client.v1 import directory, login, profile, room
|
||||
from synapse.rest.client.v2_alpha import account
|
||||
from synapse.types import JsonDict, RoomAlias
|
||||
from synapse.types import JsonDict, RoomAlias, UserID
|
||||
from synapse.util.stringutils import random_string
|
||||
|
||||
from tests import unittest
|
||||
@@ -675,6 +675,91 @@ class RoomMemberStateTestCase(RoomBase):
|
||||
self.assertEquals(json.loads(content), channel.json_body)
|
||||
|
||||
|
||||
class RoomJoinRatelimitTestCase(RoomBase):
|
||||
user_id = "@sid1:red"
|
||||
|
||||
servlets = [
|
||||
profile.register_servlets,
|
||||
room.register_servlets,
|
||||
]
|
||||
|
||||
@unittest.override_config(
|
||||
{"rc_joins": {"local": {"per_second": 3, "burst_count": 3}}}
|
||||
)
|
||||
def test_join_local_ratelimit(self):
|
||||
"""Tests that local joins are actually rate-limited."""
|
||||
for i in range(5):
|
||||
self.helper.create_room_as(self.user_id)
|
||||
|
||||
self.helper.create_room_as(self.user_id, expect_code=429)
|
||||
|
||||
@unittest.override_config(
|
||||
{"rc_joins": {"local": {"per_second": 3, "burst_count": 3}}}
|
||||
)
|
||||
def test_join_local_ratelimit_profile_change(self):
|
||||
"""Tests that sending a profile update into all of the user's joined rooms isn't
|
||||
rate-limited by the rate-limiter on joins."""
|
||||
|
||||
# Create and join more rooms than the rate-limiting config allows in a second.
|
||||
room_ids = [
|
||||
self.helper.create_room_as(self.user_id),
|
||||
self.helper.create_room_as(self.user_id),
|
||||
self.helper.create_room_as(self.user_id),
|
||||
]
|
||||
self.reactor.advance(1)
|
||||
room_ids = room_ids + [
|
||||
self.helper.create_room_as(self.user_id),
|
||||
self.helper.create_room_as(self.user_id),
|
||||
self.helper.create_room_as(self.user_id),
|
||||
]
|
||||
|
||||
# Create a profile for the user, since it hasn't been done on registration.
|
||||
store = self.hs.get_datastore()
|
||||
store.create_profile(UserID.from_string(self.user_id).localpart)
|
||||
|
||||
# Update the display name for the user.
|
||||
path = "/_matrix/client/r0/profile/%s/displayname" % self.user_id
|
||||
request, channel = self.make_request("PUT", path, {"displayname": "John Doe"})
|
||||
self.render(request)
|
||||
self.assertEquals(channel.code, 200, channel.json_body)
|
||||
|
||||
# Check that all the rooms have been sent a profile update into.
|
||||
for room_id in room_ids:
|
||||
path = "/_matrix/client/r0/rooms/%s/state/m.room.member/%s" % (
|
||||
room_id,
|
||||
self.user_id,
|
||||
)
|
||||
|
||||
request, channel = self.make_request("GET", path)
|
||||
self.render(request)
|
||||
self.assertEquals(channel.code, 200)
|
||||
|
||||
self.assertIn("displayname", channel.json_body)
|
||||
self.assertEquals(channel.json_body["displayname"], "John Doe")
|
||||
|
||||
@unittest.override_config(
|
||||
{"rc_joins": {"local": {"per_second": 3, "burst_count": 3}}}
|
||||
)
|
||||
def test_join_local_ratelimit_idempotent(self):
|
||||
"""Tests that the room join endpoints remain idempotent despite rate-limiting
|
||||
on room joins."""
|
||||
room_id = self.helper.create_room_as(self.user_id)
|
||||
|
||||
# Let's test both paths to be sure.
|
||||
paths_to_test = [
|
||||
"/_matrix/client/r0/rooms/%s/join",
|
||||
"/_matrix/client/r0/join/%s",
|
||||
]
|
||||
|
||||
for path in paths_to_test:
|
||||
# Make sure we send more requests than the rate-limiting config would allow
|
||||
# if all of these requests ended up joining the user to a room.
|
||||
for i in range(6):
|
||||
request, channel = self.make_request("POST", path % room_id, {})
|
||||
self.render(request)
|
||||
self.assertEquals(channel.code, 200)
|
||||
|
||||
|
||||
class RoomMessagesTestCase(RoomBase):
|
||||
""" Tests /rooms/$room_id/messages/$user_id/$msg_id REST events. """
|
||||
|
||||
|
||||
@@ -39,7 +39,9 @@ class RestHelper(object):
|
||||
resource = attr.ib()
|
||||
auth_user_id = attr.ib()
|
||||
|
||||
def create_room_as(self, room_creator=None, is_public=True, tok=None):
|
||||
def create_room_as(
|
||||
self, room_creator=None, is_public=True, tok=None, expect_code=200,
|
||||
):
|
||||
temp_id = self.auth_user_id
|
||||
self.auth_user_id = room_creator
|
||||
path = "/_matrix/client/r0/createRoom"
|
||||
@@ -54,9 +56,11 @@ class RestHelper(object):
|
||||
)
|
||||
render(request, self.resource, self.hs.get_reactor())
|
||||
|
||||
assert channel.result["code"] == b"200", channel.result
|
||||
assert channel.result["code"] == b"%d" % expect_code, channel.result
|
||||
self.auth_user_id = temp_id
|
||||
return channel.json_body["room_id"]
|
||||
|
||||
if expect_code == 200:
|
||||
return channel.json_body["room_id"]
|
||||
|
||||
def invite(self, room=None, src=None, targ=None, expect_code=200, tok=None):
|
||||
self.change_membership(
|
||||
|
||||
Reference in New Issue
Block a user