Remove deprecated `/_matrix/client/*/admin` endpoints (#8785)

These are now only available via `/_synapse/admin/v1`.
pull/8823/head
Dirk Klimpel 2020-11-25 22:26:11 +01:00 committed by GitHub
parent 2b110dda2a
commit 3f0ff53158
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 176 additions and 68 deletions

View File

@ -105,6 +105,28 @@ shown below:
return {"localpart": localpart} 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 Upgrading to v1.23.0
==================== ====================

1
changelog.d/8785.removal Normal file
View File

@ -0,0 +1 @@
Remove old `/_matrix/client/*/admin` endpoints which was deprecated since Synapse 1.20.0.

View File

@ -176,6 +176,13 @@ The api is::
GET /_synapse/admin/v1/whois/<user_id> 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 To use it, you will need to authenticate by providing an ``access_token`` for a
server admin: see `README.rst <README.rst>`_. server admin: see `README.rst <README.rst>`_.

View File

@ -37,7 +37,7 @@ def request_registration(
exit=sys.exit, exit=sys.exit,
): ):
url = "%s/_matrix/client/r0/admin/register" % (server_location,) url = "%s/_synapse/admin/v1/register" % (server_location,)
# Get the nonce # Get the nonce
r = requests.get(url, verify=False) r = requests.get(url, verify=False)

View File

@ -21,11 +21,7 @@ import synapse
from synapse.api.errors import Codes, NotFoundError, SynapseError from synapse.api.errors import Codes, NotFoundError, SynapseError
from synapse.http.server import JsonResource from synapse.http.server import JsonResource
from synapse.http.servlet import RestServlet, parse_json_object_from_request from synapse.http.servlet import RestServlet, parse_json_object_from_request
from synapse.rest.admin._base import ( from synapse.rest.admin._base import admin_patterns, assert_requester_is_admin
admin_patterns,
assert_requester_is_admin,
historical_admin_path_patterns,
)
from synapse.rest.admin.devices import ( from synapse.rest.admin.devices import (
DeleteDevicesRestServlet, DeleteDevicesRestServlet,
DeviceRestServlet, DeviceRestServlet,
@ -84,7 +80,7 @@ class VersionServlet(RestServlet):
class PurgeHistoryRestServlet(RestServlet): class PurgeHistoryRestServlet(RestServlet):
PATTERNS = historical_admin_path_patterns( PATTERNS = admin_patterns(
"/purge_history/(?P<room_id>[^/]*)(/(?P<event_id>[^/]+))?" "/purge_history/(?P<room_id>[^/]*)(/(?P<event_id>[^/]+))?"
) )
@ -169,9 +165,7 @@ class PurgeHistoryRestServlet(RestServlet):
class PurgeHistoryStatusRestServlet(RestServlet): class PurgeHistoryStatusRestServlet(RestServlet):
PATTERNS = historical_admin_path_patterns( PATTERNS = admin_patterns("/purge_history_status/(?P<purge_id>[^/]+)")
"/purge_history_status/(?P<purge_id>[^/]+)"
)
def __init__(self, hs): def __init__(self, hs):
""" """

View File

@ -22,28 +22,6 @@ from synapse.api.errors import AuthError
from synapse.types import UserID 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"): def admin_patterns(path_regex: str, version: str = "v1"):
"""Returns the list of patterns for an admin endpoint """Returns the list of patterns for an admin endpoint

View File

@ -16,10 +16,7 @@ import logging
from synapse.api.errors import SynapseError from synapse.api.errors import SynapseError
from synapse.http.servlet import RestServlet from synapse.http.servlet import RestServlet
from synapse.rest.admin._base import ( from synapse.rest.admin._base import admin_patterns, assert_user_is_admin
assert_user_is_admin,
historical_admin_path_patterns,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -28,7 +25,7 @@ class DeleteGroupAdminRestServlet(RestServlet):
"""Allows deleting of local groups """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): def __init__(self, hs):
self.group_server = hs.get_groups_server_handler() self.group_server = hs.get_groups_server_handler()

View File

@ -22,7 +22,6 @@ from synapse.rest.admin._base import (
admin_patterns, admin_patterns,
assert_requester_is_admin, assert_requester_is_admin,
assert_user_is_admin, assert_user_is_admin,
historical_admin_path_patterns,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -34,10 +33,10 @@ class QuarantineMediaInRoom(RestServlet):
""" """
PATTERNS = ( 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 # 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): def __init__(self, hs):
@ -63,9 +62,7 @@ class QuarantineMediaByUser(RestServlet):
this server. this server.
""" """
PATTERNS = historical_admin_path_patterns( PATTERNS = admin_patterns("/user/(?P<user_id>[^/]+)/media/quarantine")
"/user/(?P<user_id>[^/]+)/media/quarantine"
)
def __init__(self, hs): def __init__(self, hs):
self.store = hs.get_datastore() self.store = hs.get_datastore()
@ -90,7 +87,7 @@ class QuarantineMediaByID(RestServlet):
it via this server. it via this server.
""" """
PATTERNS = historical_admin_path_patterns( PATTERNS = admin_patterns(
"/media/quarantine/(?P<server_name>[^/]+)/(?P<media_id>[^/]+)" "/media/quarantine/(?P<server_name>[^/]+)/(?P<media_id>[^/]+)"
) )
@ -116,7 +113,7 @@ class ListMediaInRoom(RestServlet):
"""Lists all of the media in a given room. """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): def __init__(self, hs):
self.store = hs.get_datastore() self.store = hs.get_datastore()
@ -134,7 +131,7 @@ class ListMediaInRoom(RestServlet):
class PurgeMediaCacheRestServlet(RestServlet): class PurgeMediaCacheRestServlet(RestServlet):
PATTERNS = historical_admin_path_patterns("/purge_media_cache") PATTERNS = admin_patterns("/purge_media_cache")
def __init__(self, hs): def __init__(self, hs):
self.media_repository = hs.get_media_repository() self.media_repository = hs.get_media_repository()

View File

@ -29,7 +29,6 @@ from synapse.rest.admin._base import (
admin_patterns, admin_patterns,
assert_requester_is_admin, assert_requester_is_admin,
assert_user_is_admin, assert_user_is_admin,
historical_admin_path_patterns,
) )
from synapse.storage.databases.main.room import RoomSortOrder from synapse.storage.databases.main.room import RoomSortOrder
from synapse.types import RoomAlias, RoomID, UserID, create_requester from synapse.types import RoomAlias, RoomID, UserID, create_requester
@ -44,7 +43,7 @@ class ShutdownRoomRestServlet(RestServlet):
joined to the new room. 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): def __init__(self, hs):
self.hs = hs self.hs = hs

View File

@ -33,8 +33,8 @@ from synapse.rest.admin._base import (
admin_patterns, admin_patterns,
assert_requester_is_admin, assert_requester_is_admin,
assert_user_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 from synapse.types import JsonDict, UserID
if TYPE_CHECKING: if TYPE_CHECKING:
@ -55,7 +55,7 @@ _GET_PUSHERS_ALLOWED_KEYS = {
class UsersRestServlet(RestServlet): class UsersRestServlet(RestServlet):
PATTERNS = historical_admin_path_patterns("/users/(?P<user_id>[^/]*)$") PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)$")
def __init__(self, hs): def __init__(self, hs):
self.hs = hs self.hs = hs
@ -338,7 +338,7 @@ class UserRegisterServlet(RestServlet):
nonce to the time it was generated, in int seconds. nonce to the time it was generated, in int seconds.
""" """
PATTERNS = historical_admin_path_patterns("/register") PATTERNS = admin_patterns("/register")
NONCE_TIMEOUT = 60 NONCE_TIMEOUT = 60
def __init__(self, hs): def __init__(self, hs):
@ -461,7 +461,14 @@ class UserRegisterServlet(RestServlet):
class WhoisRestServlet(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): def __init__(self, hs):
self.hs = hs self.hs = hs
@ -485,7 +492,7 @@ class WhoisRestServlet(RestServlet):
class DeactivateAccountRestServlet(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): def __init__(self, hs):
self._deactivate_account_handler = hs.get_deactivate_account_handler() self._deactivate_account_handler = hs.get_deactivate_account_handler()
@ -516,7 +523,7 @@ class DeactivateAccountRestServlet(RestServlet):
class AccountValidityRenewServlet(RestServlet): class AccountValidityRenewServlet(RestServlet):
PATTERNS = historical_admin_path_patterns("/account_validity/validity$") PATTERNS = admin_patterns("/account_validity/validity$")
def __init__(self, hs): def __init__(self, hs):
""" """
@ -559,9 +566,7 @@ class ResetPasswordRestServlet(RestServlet):
200 OK with empty object if success otherwise an error. 200 OK with empty object if success otherwise an error.
""" """
PATTERNS = historical_admin_path_patterns( PATTERNS = admin_patterns("/reset_password/(?P<target_user_id>[^/]*)")
"/reset_password/(?P<target_user_id>[^/]*)"
)
def __init__(self, hs): def __init__(self, hs):
self.store = hs.get_datastore() 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. 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): def __init__(self, hs):
self.hs = hs self.hs = hs

View File

@ -100,7 +100,7 @@ class DeleteGroupTestCase(unittest.HomeserverTestCase):
self.assertIn(group_id, self._get_groups_user_is_in(self.other_user_token)) self.assertIn(group_id, self._get_groups_user_is_in(self.other_user_token))
# Now delete the group # Now delete the group
url = "/admin/delete_group/" + group_id url = "/_synapse/admin/v1/delete_group/" + group_id
request, channel = self.make_request( request, channel = self.make_request(
"POST", "POST",
url.encode("ascii"), url.encode("ascii"),

View File

@ -78,7 +78,7 @@ class ShutdownRoomTestCase(unittest.HomeserverTestCase):
) )
# Test that the admin can still send shutdown # 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( request, channel = self.make_request(
"POST", "POST",
url.encode("ascii"), url.encode("ascii"),
@ -112,7 +112,7 @@ class ShutdownRoomTestCase(unittest.HomeserverTestCase):
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
# Test that the admin can still send shutdown # 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( request, channel = self.make_request(
"POST", "POST",
url.encode("ascii"), url.encode("ascii"),

View File

@ -41,7 +41,7 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
def make_homeserver(self, reactor, clock): def make_homeserver(self, reactor, clock):
self.url = "/_matrix/client/r0/admin/register" self.url = "/_synapse/admin/v1/register"
self.registration_handler = Mock() self.registration_handler = Mock()
self.identity_handler = Mock() self.identity_handler = Mock()
@ -1768,3 +1768,111 @@ class UserTokenRestTestCase(unittest.HomeserverTestCase):
# though the MAU limit would stop the user doing so. # though the MAU limit would stop the user doing so.
puppet_token = self._get_token() puppet_token = self._get_token()
self.helper.join(room_id, user=self.other_user, tok=puppet_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)

View File

@ -342,7 +342,7 @@ class AccountValidityTestCase(unittest.HomeserverTestCase):
self.register_user("admin", "adminpassword", admin=True) self.register_user("admin", "adminpassword", admin=True)
admin_tok = self.login("admin", "adminpassword") 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} params = {"user_id": user_id}
request_data = json.dumps(params) request_data = json.dumps(params)
request, channel = self.make_request( request, channel = self.make_request(
@ -362,7 +362,7 @@ class AccountValidityTestCase(unittest.HomeserverTestCase):
self.register_user("admin", "adminpassword", admin=True) self.register_user("admin", "adminpassword", admin=True)
admin_tok = self.login("admin", "adminpassword") admin_tok = self.login("admin", "adminpassword")
url = "/_matrix/client/unstable/admin/account_validity/validity" url = "/_synapse/admin/v1/account_validity/validity"
params = { params = {
"user_id": user_id, "user_id": user_id,
"expiration_ts": 0, "expiration_ts": 0,
@ -389,7 +389,7 @@ class AccountValidityTestCase(unittest.HomeserverTestCase):
self.register_user("admin", "adminpassword", admin=True) self.register_user("admin", "adminpassword", admin=True)
admin_tok = self.login("admin", "adminpassword") admin_tok = self.login("admin", "adminpassword")
url = "/_matrix/client/unstable/admin/account_validity/validity" url = "/_synapse/admin/v1/account_validity/validity"
params = { params = {
"user_id": user_id, "user_id": user_id,
"expiration_ts": 0, "expiration_ts": 0,

View File

@ -416,7 +416,7 @@ class ClientIpAuthTestCase(unittest.HomeserverTestCase):
self.reactor, self.reactor,
self.site, self.site,
"GET", "GET",
"/_matrix/client/r0/admin/users/" + self.user_id, "/_synapse/admin/v1/users/" + self.user_id,
access_token=access_token, access_token=access_token,
custom_headers=headers1.items(), custom_headers=headers1.items(),
**make_request_args, **make_request_args,

View File

@ -554,7 +554,7 @@ class HomeserverTestCase(TestCase):
self.hs.config.registration_shared_secret = "shared" self.hs.config.registration_shared_secret = "shared"
# Create the user # 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) self.assertEqual(channel.code, 200, msg=channel.result)
nonce = channel.json_body["nonce"] nonce = channel.json_body["nonce"]
@ -580,7 +580,7 @@ class HomeserverTestCase(TestCase):
} }
) )
request, channel = self.make_request( 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) self.assertEqual(channel.code, 200, channel.json_body)