Compare commits

...

4 Commits

Author SHA1 Message Date
Erik Johnston 1fde1a72b8 Docs 2020-11-17 10:57:19 +00:00
Erik Johnston 4d9f5bec83 Add tests 2020-11-17 10:57:19 +00:00
Erik Johnston 628e84dd8d Fixup 2020-11-17 10:57:19 +00:00
Erik Johnston 6c52df437a
Update changelog.d/8756.feature
Co-authored-by: Matthew Hodgson <matthew@matrix.org>
2020-11-17 10:19:41 +00:00
5 changed files with 203 additions and 13 deletions

View File

@ -1 +1 @@
Add admin API that let's server admins get power in rooms that local users have power in.
Add admin API that lets server admins get power in rooms in which local users have power.

View File

@ -451,3 +451,19 @@ The following fields are returned in the JSON response body:
* `local_aliases` - An array of strings representing the local aliases that were migrated from
the old room to the new.
* `new_room_id` - A string representing the room ID of the new room.
# Make Room Admin API
Grants the server admin power in a room if a local user has power in the room;
inviting the server admin if they're not in the room and its not a publically
joinable room.
The caller can optionally specify another user to be granted power, e.g.:
```
POST /_synapse/admin/v1/make_room_admin/<room_id_or_alias>
{
"user_id": "@foo:example.com"
}
```

View File

@ -42,6 +42,7 @@ from synapse.rest.admin.rooms import (
DeleteRoomRestServlet,
JoinRoomAliasServlet,
ListRoomRestServlet,
MakeRoomAdminRoomServlet,
RoomMembersRestServlet,
RoomRestServlet,
ShutdownRoomRestServlet,
@ -232,6 +233,7 @@ def register_servlets(hs, http_server):
EventReportDetailRestServlet(hs).register(http_server)
EventReportsRestServlet(hs).register(http_server)
PushersRestServlet(hs).register(http_server)
MakeRoomAdminRoomServlet(hs).register(http_server)
def register_servlets_for_client_rest_resource(hs, http_server):

View File

@ -17,7 +17,7 @@ from http import HTTPStatus
from typing import TYPE_CHECKING, List, Optional
from synapse.api.constants import EventTypes, JoinRules, Membership
from synapse.api.errors import Codes, NotFoundError, SynapseError
from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError
from synapse.http.servlet import (
RestServlet,
assert_params_in_dict,
@ -344,6 +344,17 @@ class JoinRoomAliasServlet(RestServlet):
class MakeRoomAdminRoomServlet(RestServlet):
"""Allows a server admin to get power in a room if a local user has power in
a room. Will also invite the user if they're not in the room and its a
private room. Can specify another user (rather than the admin user) to be
granted power, e.g.:
POST /_synapse/admin/v1/make_room_admin/<room_id_or_alias>
{
"user_id": "@foo:example.com"
}
"""
PATTERNS = admin_patterns("/make_room_admin/(?P<room_identifier>[^/]*)")
def __init__(self, hs: "HomeServer"):
@ -359,6 +370,7 @@ class MakeRoomAdminRoomServlet(RestServlet):
await assert_user_is_admin(self.auth, requester.user)
content = parse_json_object_from_request(request, allow_empty_body=True)
# Resolve to a room ID, if necessary.
if RoomID.is_valid(room_identifier):
room_id = room_identifier
elif RoomAlias.is_valid(room_identifier):
@ -370,10 +382,11 @@ class MakeRoomAdminRoomServlet(RestServlet):
400, "%s was not legal room ID or room alias" % (room_identifier,)
)
# Which user to grant room admin rights to.
user_to_add = content.get("user_id", requester.user.to_string())
# Figure out which local users currently have power in the room, if any.
room_state = await self.state_handler.get_current_state(room_id)
if not room_state:
raise SynapseError(400, "Server not in room")
@ -381,33 +394,59 @@ class MakeRoomAdminRoomServlet(RestServlet):
power_levels = room_state.get((EventTypes.PowerLevels, ""))
if power_levels is not None:
# We pick the local user with the highest power.
user_power = power_levels.content.get("users", {})
admin_users = [
user_id
for user_id, power in power_levels.content.get("users")
for user_id, power in user_power.items()
if self.is_mine_id(user_id)
]
admin_users.sort(key=lambda user: power_levels.content.get("users")[user])
admin_users.sort(key=lambda user: user_power[user])
if not admin_users:
raise SynapseError(400, "No local admin user in room")
admin_user_id = admin_users[-1]
pl_content = power_levels.content
else:
# If there is no power level events then the creator has rights.
pl_content = {}
admin_user_id = create_event.sender
if not self.is_mine_id(admin_user_id):
raise SynapseError(400, "No local admin user in room")
raise SynapseError(
400, "No local admin user in room",
)
# Grant the user power equal to the room admin by attempting to send an
# updated power level event.
new_pl_content = dict(pl_content)
new_pl_content["users"] = dict(pl_content.get("users", {}))
new_pl_content["users"][user_to_add] = new_pl_content["users"][admin_user_id]
fake_requester = create_requester(
admin_user_id, authenticated_entity=requester.authenticated_entity,
)
new_pl_content = dict(pl_content)
new_pl_content["users"] = dict(pl_content.get("users", {}))
new_pl_content["users"][user_to_add] = new_pl_content["users"][admin_user_id]
await self.event_creation_handler.create_and_send_nonmember_event(
fake_requester, event_dict=new_pl_content,
)
try:
await self.event_creation_handler.create_and_send_nonmember_event(
fake_requester,
event_dict={
"content": new_pl_content,
"sender": admin_user_id,
"type": EventTypes.PowerLevels,
"state_key": "",
"room_id": room_id,
},
)
except AuthError:
# The admin user we found turned out not to have enough power.
raise SynapseError(
400, "No local admin user in room with power to update power levels."
)
# Now we check if the user we're granting admin rights to is already in
# the room. If not and its not a public room we invite them.
member_event = room_state.get((EventTypes.Member, user_to_add))
is_joined = False
if member_event:

View File

@ -20,6 +20,7 @@ from typing import List, Optional
from mock import Mock
import synapse.rest.admin
from synapse.api.constants import EventTypes, Membership
from synapse.api.errors import Codes
from synapse.rest.client.v1 import directory, events, login, room
@ -1437,6 +1438,138 @@ class JoinAliasRoomTestCase(unittest.HomeserverTestCase):
self.assertEqual(private_room_id, channel.json_body["joined_rooms"][0])
class MakeRoomAdminTestCase(unittest.HomeserverTestCase):
servlets = [
synapse.rest.admin.register_servlets,
room.register_servlets,
login.register_servlets,
]
def prepare(self, reactor, clock, homeserver):
self.admin_user = self.register_user("admin", "pass", admin=True)
self.admin_user_tok = self.login("admin", "pass")
self.creator = self.register_user("creator", "test")
self.creator_tok = self.login("creator", "test")
self.second_user_id = self.register_user("second", "test")
self.second_tok = self.login("second", "test")
self.public_room_id = self.helper.create_room_as(
self.creator, tok=self.creator_tok, is_public=True
)
self.url = "/_synapse/admin/v1/make_room_admin/{}".format(self.public_room_id)
def test_public_room(self):
"""Test that getting admin in a public room works.
"""
room_id = self.helper.create_room_as(
self.creator, tok=self.creator_tok, is_public=True
)
request, channel = self.make_request(
"POST",
"/_synapse/admin/v1/make_room_admin/{}".format(room_id),
content={},
access_token=self.admin_user_tok,
)
self.render(request)
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
# Now we test that we can join the room and ban a user.
self.helper.join(room_id, self.admin_user, tok=self.admin_user_tok)
self.helper.change_membership(
room_id,
self.admin_user,
"@test:test",
Membership.BAN,
tok=self.admin_user_tok,
)
def test_private_room(self):
"""Test that getting admin in a private room works and we get invited.
"""
room_id = self.helper.create_room_as(
self.creator, tok=self.creator_tok, is_public=False,
)
request, channel = self.make_request(
"POST",
"/_synapse/admin/v1/make_room_admin/{}".format(room_id),
content={},
access_token=self.admin_user_tok,
)
self.render(request)
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
# Now we test that we can join the room (we should have received an
# inviate) and can ban a user.
self.helper.join(room_id, self.admin_user, tok=self.admin_user_tok)
self.helper.change_membership(
room_id,
self.admin_user,
"@test:test",
Membership.BAN,
tok=self.admin_user_tok,
)
def test_other_user(self):
"""Test that giving admin in a public room works to a non-admin user works.
"""
room_id = self.helper.create_room_as(
self.creator, tok=self.creator_tok, is_public=True
)
request, channel = self.make_request(
"POST",
"/_synapse/admin/v1/make_room_admin/{}".format(room_id),
content={"user_id": self.second_user_id},
access_token=self.admin_user_tok,
)
self.render(request)
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
# Now we test that we can join the room and ban a user.
self.helper.join(room_id, self.second_user_id, tok=self.second_tok)
self.helper.change_membership(
room_id,
self.second_user_id,
"@test:test",
Membership.BAN,
tok=self.second_tok,
)
def test_not_enough_power(self):
"""Test that we get a sensible error if there are no local room admins.
"""
room_id = self.helper.create_room_as(
self.creator, tok=self.creator_tok, is_public=True
)
# The creator drops admin rights in the room.
pl = self.helper.get_state(
room_id, EventTypes.PowerLevels, tok=self.creator_tok
)
pl["users"][self.creator] = 0
self.helper.send_state(
room_id, EventTypes.PowerLevels, body=pl, tok=self.creator_tok
)
request, channel = self.make_request(
"POST",
"/_synapse/admin/v1/make_room_admin/{}".format(room_id),
content={},
access_token=self.admin_user_tok,
)
self.render(request)
# We expect this to fail with a 400 as there are no room admins.
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
PURGE_TABLES = [
"current_state_events",
"event_backward_extremities",