mirror of
https://github.com/element-hq/synapse.git
synced 2024-12-14 11:57:44 +00:00
Remove deprecated /_matrix/client/*/admin
endpoints (#8785)
These are now only available via `/_synapse/admin/v1`.
This commit is contained in:
parent
2b110dda2a
commit
3f0ff53158
16 changed files with 176 additions and 68 deletions
22
UPGRADE.rst
22
UPGRADE.rst
|
@ -105,6 +105,28 @@ shown below:
|
|||
|
||||
return {"localpart": localpart}
|
||||
|
||||
Removal historical Synapse Admin API
|
||||
------------------------------------
|
||||
|
||||
Historically, the Synapse Admin API has been accessible under:
|
||||
|
||||
* ``/_matrix/client/api/v1/admin``
|
||||
* ``/_matrix/client/unstable/admin``
|
||||
* ``/_matrix/client/r0/admin``
|
||||
* ``/_synapse/admin/v1``
|
||||
|
||||
The endpoints with ``/_matrix/client/*`` prefixes have been removed as of v1.24.0.
|
||||
The Admin API is now only accessible under:
|
||||
|
||||
* ``/_synapse/admin/v1``
|
||||
|
||||
The only exception is the `/admin/whois` endpoint, which is
|
||||
`also available via the client-server API <https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid>`_.
|
||||
|
||||
The deprecation of the old endpoints was announced with Synapse 1.20.0 (released
|
||||
on 2020-09-22) and makes it easier for homeserver admins to lock down external
|
||||
access to the Admin API endpoints.
|
||||
|
||||
Upgrading to v1.23.0
|
||||
====================
|
||||
|
||||
|
|
1
changelog.d/8785.removal
Normal file
1
changelog.d/8785.removal
Normal file
|
@ -0,0 +1 @@
|
|||
Remove old `/_matrix/client/*/admin` endpoints which was deprecated since Synapse 1.20.0.
|
|
@ -176,6 +176,13 @@ The api is::
|
|||
|
||||
GET /_synapse/admin/v1/whois/<user_id>
|
||||
|
||||
and::
|
||||
|
||||
GET /_matrix/client/r0/admin/whois/<userId>
|
||||
|
||||
See also: `Client Server API Whois
|
||||
<https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid>`_
|
||||
|
||||
To use it, you will need to authenticate by providing an ``access_token`` for a
|
||||
server admin: see `README.rst <README.rst>`_.
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ def request_registration(
|
|||
exit=sys.exit,
|
||||
):
|
||||
|
||||
url = "%s/_matrix/client/r0/admin/register" % (server_location,)
|
||||
url = "%s/_synapse/admin/v1/register" % (server_location,)
|
||||
|
||||
# Get the nonce
|
||||
r = requests.get(url, verify=False)
|
||||
|
|
|
@ -21,11 +21,7 @@ import synapse
|
|||
from synapse.api.errors import Codes, NotFoundError, SynapseError
|
||||
from synapse.http.server import JsonResource
|
||||
from synapse.http.servlet import RestServlet, parse_json_object_from_request
|
||||
from synapse.rest.admin._base import (
|
||||
admin_patterns,
|
||||
assert_requester_is_admin,
|
||||
historical_admin_path_patterns,
|
||||
)
|
||||
from synapse.rest.admin._base import admin_patterns, assert_requester_is_admin
|
||||
from synapse.rest.admin.devices import (
|
||||
DeleteDevicesRestServlet,
|
||||
DeviceRestServlet,
|
||||
|
@ -84,7 +80,7 @@ class VersionServlet(RestServlet):
|
|||
|
||||
|
||||
class PurgeHistoryRestServlet(RestServlet):
|
||||
PATTERNS = historical_admin_path_patterns(
|
||||
PATTERNS = admin_patterns(
|
||||
"/purge_history/(?P<room_id>[^/]*)(/(?P<event_id>[^/]+))?"
|
||||
)
|
||||
|
||||
|
@ -169,9 +165,7 @@ class PurgeHistoryRestServlet(RestServlet):
|
|||
|
||||
|
||||
class PurgeHistoryStatusRestServlet(RestServlet):
|
||||
PATTERNS = historical_admin_path_patterns(
|
||||
"/purge_history_status/(?P<purge_id>[^/]+)"
|
||||
)
|
||||
PATTERNS = admin_patterns("/purge_history_status/(?P<purge_id>[^/]+)")
|
||||
|
||||
def __init__(self, hs):
|
||||
"""
|
||||
|
|
|
@ -22,28 +22,6 @@ from synapse.api.errors import AuthError
|
|||
from synapse.types import UserID
|
||||
|
||||
|
||||
def historical_admin_path_patterns(path_regex):
|
||||
"""Returns the list of patterns for an admin endpoint, including historical ones
|
||||
|
||||
This is a backwards-compatibility hack. Previously, the Admin API was exposed at
|
||||
various paths under /_matrix/client. This function returns a list of patterns
|
||||
matching those paths (as well as the new one), so that existing scripts which rely
|
||||
on the endpoints being available there are not broken.
|
||||
|
||||
Note that this should only be used for existing endpoints: new ones should just
|
||||
register for the /_synapse/admin path.
|
||||
"""
|
||||
return [
|
||||
re.compile(prefix + path_regex)
|
||||
for prefix in (
|
||||
"^/_synapse/admin/v1",
|
||||
"^/_matrix/client/api/v1/admin",
|
||||
"^/_matrix/client/unstable/admin",
|
||||
"^/_matrix/client/r0/admin",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def admin_patterns(path_regex: str, version: str = "v1"):
|
||||
"""Returns the list of patterns for an admin endpoint
|
||||
|
||||
|
|
|
@ -16,10 +16,7 @@ import logging
|
|||
|
||||
from synapse.api.errors import SynapseError
|
||||
from synapse.http.servlet import RestServlet
|
||||
from synapse.rest.admin._base import (
|
||||
assert_user_is_admin,
|
||||
historical_admin_path_patterns,
|
||||
)
|
||||
from synapse.rest.admin._base import admin_patterns, assert_user_is_admin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -28,7 +25,7 @@ class DeleteGroupAdminRestServlet(RestServlet):
|
|||
"""Allows deleting of local groups
|
||||
"""
|
||||
|
||||
PATTERNS = historical_admin_path_patterns("/delete_group/(?P<group_id>[^/]*)")
|
||||
PATTERNS = admin_patterns("/delete_group/(?P<group_id>[^/]*)")
|
||||
|
||||
def __init__(self, hs):
|
||||
self.group_server = hs.get_groups_server_handler()
|
||||
|
|
|
@ -22,7 +22,6 @@ from synapse.rest.admin._base import (
|
|||
admin_patterns,
|
||||
assert_requester_is_admin,
|
||||
assert_user_is_admin,
|
||||
historical_admin_path_patterns,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -34,10 +33,10 @@ class QuarantineMediaInRoom(RestServlet):
|
|||
"""
|
||||
|
||||
PATTERNS = (
|
||||
historical_admin_path_patterns("/room/(?P<room_id>[^/]+)/media/quarantine")
|
||||
admin_patterns("/room/(?P<room_id>[^/]+)/media/quarantine")
|
||||
+
|
||||
# This path kept around for legacy reasons
|
||||
historical_admin_path_patterns("/quarantine_media/(?P<room_id>[^/]+)")
|
||||
admin_patterns("/quarantine_media/(?P<room_id>[^/]+)")
|
||||
)
|
||||
|
||||
def __init__(self, hs):
|
||||
|
@ -63,9 +62,7 @@ class QuarantineMediaByUser(RestServlet):
|
|||
this server.
|
||||
"""
|
||||
|
||||
PATTERNS = historical_admin_path_patterns(
|
||||
"/user/(?P<user_id>[^/]+)/media/quarantine"
|
||||
)
|
||||
PATTERNS = admin_patterns("/user/(?P<user_id>[^/]+)/media/quarantine")
|
||||
|
||||
def __init__(self, hs):
|
||||
self.store = hs.get_datastore()
|
||||
|
@ -90,7 +87,7 @@ class QuarantineMediaByID(RestServlet):
|
|||
it via this server.
|
||||
"""
|
||||
|
||||
PATTERNS = historical_admin_path_patterns(
|
||||
PATTERNS = admin_patterns(
|
||||
"/media/quarantine/(?P<server_name>[^/]+)/(?P<media_id>[^/]+)"
|
||||
)
|
||||
|
||||
|
@ -116,7 +113,7 @@ class ListMediaInRoom(RestServlet):
|
|||
"""Lists all of the media in a given room.
|
||||
"""
|
||||
|
||||
PATTERNS = historical_admin_path_patterns("/room/(?P<room_id>[^/]+)/media")
|
||||
PATTERNS = admin_patterns("/room/(?P<room_id>[^/]+)/media")
|
||||
|
||||
def __init__(self, hs):
|
||||
self.store = hs.get_datastore()
|
||||
|
@ -134,7 +131,7 @@ class ListMediaInRoom(RestServlet):
|
|||
|
||||
|
||||
class PurgeMediaCacheRestServlet(RestServlet):
|
||||
PATTERNS = historical_admin_path_patterns("/purge_media_cache")
|
||||
PATTERNS = admin_patterns("/purge_media_cache")
|
||||
|
||||
def __init__(self, hs):
|
||||
self.media_repository = hs.get_media_repository()
|
||||
|
|
|
@ -29,7 +29,6 @@ from synapse.rest.admin._base import (
|
|||
admin_patterns,
|
||||
assert_requester_is_admin,
|
||||
assert_user_is_admin,
|
||||
historical_admin_path_patterns,
|
||||
)
|
||||
from synapse.storage.databases.main.room import RoomSortOrder
|
||||
from synapse.types import RoomAlias, RoomID, UserID, create_requester
|
||||
|
@ -44,7 +43,7 @@ class ShutdownRoomRestServlet(RestServlet):
|
|||
joined to the new room.
|
||||
"""
|
||||
|
||||
PATTERNS = historical_admin_path_patterns("/shutdown_room/(?P<room_id>[^/]+)")
|
||||
PATTERNS = admin_patterns("/shutdown_room/(?P<room_id>[^/]+)")
|
||||
|
||||
def __init__(self, hs):
|
||||
self.hs = hs
|
||||
|
|
|
@ -33,8 +33,8 @@ from synapse.rest.admin._base import (
|
|||
admin_patterns,
|
||||
assert_requester_is_admin,
|
||||
assert_user_is_admin,
|
||||
historical_admin_path_patterns,
|
||||
)
|
||||
from synapse.rest.client.v2_alpha._base import client_patterns
|
||||
from synapse.types import JsonDict, UserID
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -55,7 +55,7 @@ _GET_PUSHERS_ALLOWED_KEYS = {
|
|||
|
||||
|
||||
class UsersRestServlet(RestServlet):
|
||||
PATTERNS = historical_admin_path_patterns("/users/(?P<user_id>[^/]*)$")
|
||||
PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)$")
|
||||
|
||||
def __init__(self, hs):
|
||||
self.hs = hs
|
||||
|
@ -338,7 +338,7 @@ class UserRegisterServlet(RestServlet):
|
|||
nonce to the time it was generated, in int seconds.
|
||||
"""
|
||||
|
||||
PATTERNS = historical_admin_path_patterns("/register")
|
||||
PATTERNS = admin_patterns("/register")
|
||||
NONCE_TIMEOUT = 60
|
||||
|
||||
def __init__(self, hs):
|
||||
|
@ -461,7 +461,14 @@ class UserRegisterServlet(RestServlet):
|
|||
|
||||
|
||||
class WhoisRestServlet(RestServlet):
|
||||
PATTERNS = historical_admin_path_patterns("/whois/(?P<user_id>[^/]*)")
|
||||
path_regex = "/whois/(?P<user_id>[^/]*)$"
|
||||
PATTERNS = (
|
||||
admin_patterns(path_regex)
|
||||
+
|
||||
# URL for spec reason
|
||||
# https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid
|
||||
client_patterns("/admin" + path_regex, v1=True)
|
||||
)
|
||||
|
||||
def __init__(self, hs):
|
||||
self.hs = hs
|
||||
|
@ -485,7 +492,7 @@ class WhoisRestServlet(RestServlet):
|
|||
|
||||
|
||||
class DeactivateAccountRestServlet(RestServlet):
|
||||
PATTERNS = historical_admin_path_patterns("/deactivate/(?P<target_user_id>[^/]*)")
|
||||
PATTERNS = admin_patterns("/deactivate/(?P<target_user_id>[^/]*)")
|
||||
|
||||
def __init__(self, hs):
|
||||
self._deactivate_account_handler = hs.get_deactivate_account_handler()
|
||||
|
@ -516,7 +523,7 @@ class DeactivateAccountRestServlet(RestServlet):
|
|||
|
||||
|
||||
class AccountValidityRenewServlet(RestServlet):
|
||||
PATTERNS = historical_admin_path_patterns("/account_validity/validity$")
|
||||
PATTERNS = admin_patterns("/account_validity/validity$")
|
||||
|
||||
def __init__(self, hs):
|
||||
"""
|
||||
|
@ -559,9 +566,7 @@ class ResetPasswordRestServlet(RestServlet):
|
|||
200 OK with empty object if success otherwise an error.
|
||||
"""
|
||||
|
||||
PATTERNS = historical_admin_path_patterns(
|
||||
"/reset_password/(?P<target_user_id>[^/]*)"
|
||||
)
|
||||
PATTERNS = admin_patterns("/reset_password/(?P<target_user_id>[^/]*)")
|
||||
|
||||
def __init__(self, hs):
|
||||
self.store = hs.get_datastore()
|
||||
|
@ -603,7 +608,7 @@ class SearchUsersRestServlet(RestServlet):
|
|||
200 OK with json object {list[dict[str, Any]], count} or empty object.
|
||||
"""
|
||||
|
||||
PATTERNS = historical_admin_path_patterns("/search_users/(?P<target_user_id>[^/]*)")
|
||||
PATTERNS = admin_patterns("/search_users/(?P<target_user_id>[^/]*)")
|
||||
|
||||
def __init__(self, hs):
|
||||
self.hs = hs
|
||||
|
|
|
@ -100,7 +100,7 @@ class DeleteGroupTestCase(unittest.HomeserverTestCase):
|
|||
self.assertIn(group_id, self._get_groups_user_is_in(self.other_user_token))
|
||||
|
||||
# Now delete the group
|
||||
url = "/admin/delete_group/" + group_id
|
||||
url = "/_synapse/admin/v1/delete_group/" + group_id
|
||||
request, channel = self.make_request(
|
||||
"POST",
|
||||
url.encode("ascii"),
|
||||
|
|
|
@ -78,7 +78,7 @@ class ShutdownRoomTestCase(unittest.HomeserverTestCase):
|
|||
)
|
||||
|
||||
# Test that the admin can still send shutdown
|
||||
url = "admin/shutdown_room/" + room_id
|
||||
url = "/_synapse/admin/v1/shutdown_room/" + room_id
|
||||
request, channel = self.make_request(
|
||||
"POST",
|
||||
url.encode("ascii"),
|
||||
|
@ -112,7 +112,7 @@ class ShutdownRoomTestCase(unittest.HomeserverTestCase):
|
|||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
|
||||
# Test that the admin can still send shutdown
|
||||
url = "admin/shutdown_room/" + room_id
|
||||
url = "/_synapse/admin/v1/shutdown_room/" + room_id
|
||||
request, channel = self.make_request(
|
||||
"POST",
|
||||
url.encode("ascii"),
|
||||
|
|
|
@ -41,7 +41,7 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
|
|||
|
||||
def make_homeserver(self, reactor, clock):
|
||||
|
||||
self.url = "/_matrix/client/r0/admin/register"
|
||||
self.url = "/_synapse/admin/v1/register"
|
||||
|
||||
self.registration_handler = Mock()
|
||||
self.identity_handler = Mock()
|
||||
|
@ -1768,3 +1768,111 @@ class UserTokenRestTestCase(unittest.HomeserverTestCase):
|
|||
# though the MAU limit would stop the user doing so.
|
||||
puppet_token = self._get_token()
|
||||
self.helper.join(room_id, user=self.other_user, tok=puppet_token)
|
||||
|
||||
|
||||
class WhoisRestTestCase(unittest.HomeserverTestCase):
|
||||
|
||||
servlets = [
|
||||
synapse.rest.admin.register_servlets,
|
||||
login.register_servlets,
|
||||
]
|
||||
|
||||
def prepare(self, reactor, clock, hs):
|
||||
self.store = hs.get_datastore()
|
||||
|
||||
self.admin_user = self.register_user("admin", "pass", admin=True)
|
||||
self.admin_user_tok = self.login("admin", "pass")
|
||||
|
||||
self.other_user = self.register_user("user", "pass")
|
||||
self.url1 = "/_synapse/admin/v1/whois/%s" % urllib.parse.quote(self.other_user)
|
||||
self.url2 = "/_matrix/client/r0/admin/whois/%s" % urllib.parse.quote(
|
||||
self.other_user
|
||||
)
|
||||
|
||||
def test_no_auth(self):
|
||||
"""
|
||||
Try to get information of an user without authentication.
|
||||
"""
|
||||
request, channel = self.make_request("GET", self.url1, b"{}")
|
||||
self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
|
||||
self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
|
||||
|
||||
request, channel = self.make_request("GET", self.url2, b"{}")
|
||||
self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
|
||||
self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
|
||||
|
||||
def test_requester_is_not_admin(self):
|
||||
"""
|
||||
If the user is not a server admin, an error is returned.
|
||||
"""
|
||||
self.register_user("user2", "pass")
|
||||
other_user2_token = self.login("user2", "pass")
|
||||
|
||||
request, channel = self.make_request(
|
||||
"GET", self.url1, access_token=other_user2_token,
|
||||
)
|
||||
self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
|
||||
|
||||
request, channel = self.make_request(
|
||||
"GET", self.url2, access_token=other_user2_token,
|
||||
)
|
||||
self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
|
||||
|
||||
def test_user_is_not_local(self):
|
||||
"""
|
||||
Tests that a lookup for a user that is not a local returns a 400
|
||||
"""
|
||||
url1 = "/_synapse/admin/v1/whois/@unknown_person:unknown_domain"
|
||||
url2 = "/_matrix/client/r0/admin/whois/@unknown_person:unknown_domain"
|
||||
|
||||
request, channel = self.make_request(
|
||||
"GET", url1, access_token=self.admin_user_tok,
|
||||
)
|
||||
self.assertEqual(400, channel.code, msg=channel.json_body)
|
||||
self.assertEqual("Can only whois a local user", channel.json_body["error"])
|
||||
|
||||
request, channel = self.make_request(
|
||||
"GET", url2, access_token=self.admin_user_tok,
|
||||
)
|
||||
self.assertEqual(400, channel.code, msg=channel.json_body)
|
||||
self.assertEqual("Can only whois a local user", channel.json_body["error"])
|
||||
|
||||
def test_get_whois_admin(self):
|
||||
"""
|
||||
The lookup should succeed for an admin.
|
||||
"""
|
||||
request, channel = self.make_request(
|
||||
"GET", self.url1, access_token=self.admin_user_tok,
|
||||
)
|
||||
self.assertEqual(200, channel.code, msg=channel.json_body)
|
||||
self.assertEqual(self.other_user, channel.json_body["user_id"])
|
||||
self.assertIn("devices", channel.json_body)
|
||||
|
||||
request, channel = self.make_request(
|
||||
"GET", self.url2, access_token=self.admin_user_tok,
|
||||
)
|
||||
self.assertEqual(200, channel.code, msg=channel.json_body)
|
||||
self.assertEqual(self.other_user, channel.json_body["user_id"])
|
||||
self.assertIn("devices", channel.json_body)
|
||||
|
||||
def test_get_whois_user(self):
|
||||
"""
|
||||
The lookup should succeed for a normal user looking up their own information.
|
||||
"""
|
||||
other_user_token = self.login("user", "pass")
|
||||
|
||||
request, channel = self.make_request(
|
||||
"GET", self.url1, access_token=other_user_token,
|
||||
)
|
||||
self.assertEqual(200, channel.code, msg=channel.json_body)
|
||||
self.assertEqual(self.other_user, channel.json_body["user_id"])
|
||||
self.assertIn("devices", channel.json_body)
|
||||
|
||||
request, channel = self.make_request(
|
||||
"GET", self.url2, access_token=other_user_token,
|
||||
)
|
||||
self.assertEqual(200, channel.code, msg=channel.json_body)
|
||||
self.assertEqual(self.other_user, channel.json_body["user_id"])
|
||||
self.assertIn("devices", channel.json_body)
|
||||
|
|
|
@ -342,7 +342,7 @@ class AccountValidityTestCase(unittest.HomeserverTestCase):
|
|||
self.register_user("admin", "adminpassword", admin=True)
|
||||
admin_tok = self.login("admin", "adminpassword")
|
||||
|
||||
url = "/_matrix/client/unstable/admin/account_validity/validity"
|
||||
url = "/_synapse/admin/v1/account_validity/validity"
|
||||
params = {"user_id": user_id}
|
||||
request_data = json.dumps(params)
|
||||
request, channel = self.make_request(
|
||||
|
@ -362,7 +362,7 @@ class AccountValidityTestCase(unittest.HomeserverTestCase):
|
|||
self.register_user("admin", "adminpassword", admin=True)
|
||||
admin_tok = self.login("admin", "adminpassword")
|
||||
|
||||
url = "/_matrix/client/unstable/admin/account_validity/validity"
|
||||
url = "/_synapse/admin/v1/account_validity/validity"
|
||||
params = {
|
||||
"user_id": user_id,
|
||||
"expiration_ts": 0,
|
||||
|
@ -389,7 +389,7 @@ class AccountValidityTestCase(unittest.HomeserverTestCase):
|
|||
self.register_user("admin", "adminpassword", admin=True)
|
||||
admin_tok = self.login("admin", "adminpassword")
|
||||
|
||||
url = "/_matrix/client/unstable/admin/account_validity/validity"
|
||||
url = "/_synapse/admin/v1/account_validity/validity"
|
||||
params = {
|
||||
"user_id": user_id,
|
||||
"expiration_ts": 0,
|
||||
|
|
|
@ -416,7 +416,7 @@ class ClientIpAuthTestCase(unittest.HomeserverTestCase):
|
|||
self.reactor,
|
||||
self.site,
|
||||
"GET",
|
||||
"/_matrix/client/r0/admin/users/" + self.user_id,
|
||||
"/_synapse/admin/v1/users/" + self.user_id,
|
||||
access_token=access_token,
|
||||
custom_headers=headers1.items(),
|
||||
**make_request_args,
|
||||
|
|
|
@ -554,7 +554,7 @@ class HomeserverTestCase(TestCase):
|
|||
self.hs.config.registration_shared_secret = "shared"
|
||||
|
||||
# Create the user
|
||||
request, channel = self.make_request("GET", "/_matrix/client/r0/admin/register")
|
||||
request, channel = self.make_request("GET", "/_synapse/admin/v1/register")
|
||||
self.assertEqual(channel.code, 200, msg=channel.result)
|
||||
nonce = channel.json_body["nonce"]
|
||||
|
||||
|
@ -580,7 +580,7 @@ class HomeserverTestCase(TestCase):
|
|||
}
|
||||
)
|
||||
request, channel = self.make_request(
|
||||
"POST", "/_matrix/client/r0/admin/register", body.encode("utf8")
|
||||
"POST", "/_synapse/admin/v1/register", body.encode("utf8")
|
||||
)
|
||||
self.assertEqual(channel.code, 200, channel.json_body)
|
||||
|
||||
|
|
Loading…
Reference in a new issue