Compare commits
4 Commits
076eac4094
...
1fde1a72b8
Author | SHA1 | Date |
---|---|---|
Erik Johnston | 1fde1a72b8 | |
Erik Johnston | 4d9f5bec83 | |
Erik Johnston | 628e84dd8d | |
Erik Johnston | 6c52df437a |
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue