Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Merge pull request #2291 from matrix-org/erikj/shutdown_room
Browse files Browse the repository at this point in the history
Add shutdown room API
  • Loading branch information
erikjohnston authored Jun 19, 2017
2 parents 3fafb7b + e5ae386 commit b5f665d
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 5 deletions.
4 changes: 4 additions & 0 deletions synapse/handlers/federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,10 @@ def on_invite_request(self, origin, pdu):
"""
event = pdu

is_blocked = yield self.store.is_room_blocked(event.room_id)
if is_blocked:
raise SynapseError(403, "This room has been blocked on this server")

event.internal_metadata.outlier = True
event.internal_metadata.invite_from_remote = True

Expand Down
21 changes: 17 additions & 4 deletions synapse/handlers/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class RoomCreationHandler(BaseHandler):
}

@defer.inlineCallbacks
def create_room(self, requester, config):
def create_room(self, requester, config, ratelimit=True):
""" Creates a new room.
Args:
Expand All @@ -75,7 +75,8 @@ def create_room(self, requester, config):
"""
user_id = requester.user.to_string()

yield self.ratelimit(requester)
if ratelimit:
yield self.ratelimit(requester)

if "room_alias_name" in config:
for wchar in string.whitespace:
Expand Down Expand Up @@ -167,6 +168,7 @@ def create_room(self, requester, config):
initial_state=initial_state,
creation_content=creation_content,
room_alias=room_alias,
power_level_content_override=config.get("power_level_content_override", {})
)

if "name" in config:
Expand Down Expand Up @@ -245,7 +247,8 @@ def _send_events_for_new_room(
invite_list,
initial_state,
creation_content,
room_alias
room_alias,
power_level_content_override,
):
def create(etype, content, **kwargs):
e = {
Expand Down Expand Up @@ -291,7 +294,15 @@ def send(etype, content, **kwargs):
ratelimit=False,
)

if (EventTypes.PowerLevels, '') not in initial_state:
# We treat the power levels override specially as this needs to be one
# of the first events that get sent into a room.
pl_content = initial_state.pop((EventTypes.PowerLevels, ''), None)
if pl_content is not None:
yield send(
etype=EventTypes.PowerLevels,
content=pl_content,
)
else:
power_level_content = {
"users": {
creator_id: 100,
Expand All @@ -316,6 +327,8 @@ def send(etype, content, **kwargs):
for invitee in invite_list:
power_level_content["users"][invitee] = 100

power_level_content.update(power_level_content_override)

yield send(
etype=EventTypes.PowerLevels,
content=power_level_content,
Expand Down
10 changes: 10 additions & 0 deletions synapse/handlers/room_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@ def _update_membership(
if not remote_room_hosts:
remote_room_hosts = []

if effective_membership_state not in ("leave", "ban",):
is_blocked = yield self.store.is_room_blocked(room_id)
if is_blocked:
raise SynapseError(403, "This room has been blocked on this server")

latest_event_ids = yield self.store.get_latest_event_ids_in_room(room_id)
current_state_ids = yield self.state_handler.get_current_state_ids(
room_id, latest_event_ids=latest_event_ids,
Expand Down Expand Up @@ -369,6 +374,11 @@ def send_membership_event(
# so don't really fit into the general auth process.
raise AuthError(403, "Guest access not allowed")

if event.membership not in (Membership.LEAVE, Membership.BAN):
is_blocked = yield self.store.is_room_blocked(room_id)
if is_blocked:
raise SynapseError(403, "This room has been blocked on this server")

yield message_handler.handle_new_client_event(
requester,
event,
Expand Down
116 changes: 115 additions & 1 deletion synapse/rest/client/v1/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@

from twisted.internet import defer

from synapse.api.constants import Membership
from synapse.api.errors import AuthError, SynapseError
from synapse.types import UserID
from synapse.types import UserID, create_requester
from synapse.http.servlet import parse_json_object_from_request

from .base import ClientV1RestServlet, client_path_patterns
Expand Down Expand Up @@ -157,6 +158,118 @@ def on_POST(self, request, target_user_id):
defer.returnValue((200, {}))


class ShutdownRoomRestServlet(ClientV1RestServlet):
"""Shuts down a room by removing all local users from the room and blocking
all future invites and joins to the room. Any local aliases will be repointed
to a new room created by `new_room_user_id` and kicked users will be auto
joined to the new room.
"""
PATTERNS = client_path_patterns("/admin/shutdown_room/(?P<room_id>[^/]+)")

DEFAULT_MESSAGE = (
"Sharing illegal content on this server is not permitted and rooms in"
" violatation will be blocked."
)

def __init__(self, hs):
super(ShutdownRoomRestServlet, self).__init__(hs)
self.store = hs.get_datastore()
self.handlers = hs.get_handlers()
self.state = hs.get_state_handler()

@defer.inlineCallbacks
def on_POST(self, request, room_id):
requester = yield self.auth.get_user_by_req(request)
is_admin = yield self.auth.is_server_admin(requester.user)
if not is_admin:
raise AuthError(403, "You are not a server admin")

content = parse_json_object_from_request(request)

new_room_user_id = content.get("new_room_user_id")
if not new_room_user_id:
raise SynapseError(400, "Please provide field `new_room_user_id`")

room_creator_requester = create_requester(new_room_user_id)

message = content.get("message", self.DEFAULT_MESSAGE)
room_name = content.get("room_name", "Content Violation Notification")

info = yield self.handlers.room_creation_handler.create_room(
room_creator_requester,
config={
"preset": "public_chat",
"name": room_name,
"power_level_content_override": {
"users_default": -10,
},
},
ratelimit=False,
)
new_room_id = info["room_id"]

msg_handler = self.handlers.message_handler
yield msg_handler.create_and_send_nonmember_event(
room_creator_requester,
{
"type": "m.room.message",
"content": {"body": message, "msgtype": "m.text"},
"room_id": new_room_id,
"sender": new_room_user_id,
},
ratelimit=False,
)

requester_user_id = requester.user.to_string()

logger.info("Shutting down room %r", room_id)

yield self.store.block_room(room_id, requester_user_id)

users = yield self.state.get_current_user_in_room(room_id)
kicked_users = []
for user_id in users:
if not self.hs.is_mine_id(user_id):
continue

logger.info("Kicking %r from %r...", user_id, room_id)

target_requester = create_requester(user_id)
yield self.handlers.room_member_handler.update_membership(
requester=target_requester,
target=target_requester.user,
room_id=room_id,
action=Membership.LEAVE,
content={},
ratelimit=False
)

yield self.handlers.room_member_handler.forget(target_requester.user, room_id)

yield self.handlers.room_member_handler.update_membership(
requester=target_requester,
target=target_requester.user,
room_id=new_room_id,
action=Membership.JOIN,
content={},
ratelimit=False
)

kicked_users.append(user_id)

aliases_for_room = yield self.store.get_aliases_for_room(room_id)

yield self.store.update_aliases_for_room(
room_id, new_room_id, requester_user_id
)

defer.returnValue((200, {
"kicked_users": kicked_users,
"local_aliases": aliases_for_room,
"new_room_id": new_room_id,
}))


class ResetPasswordRestServlet(ClientV1RestServlet):
"""Post request to allow an administrator reset password for a user.
This need a user have a administrator access in Synapse.
Expand Down Expand Up @@ -353,3 +466,4 @@ def register_servlets(hs, http_server):
ResetPasswordRestServlet(hs).register(http_server)
GetUsersPaginatedRestServlet(hs).register(http_server)
SearchUsersRestServlet(hs).register(http_server)
ShutdownRoomRestServlet(hs).register(http_server)
14 changes: 14 additions & 0 deletions synapse/storage/directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,17 @@ def get_aliases_for_room(self, room_id):
"room_alias",
desc="get_aliases_for_room",
)

def update_aliases_for_room(self, old_room_id, new_room_id, creator):
def _update_aliases_for_room_txn(txn):
sql = "UPDATE room_aliases SET room_id = ?, creator = ? WHERE room_id = ?"
txn.execute(sql, (new_room_id, creator, old_room_id,))
self._invalidate_cache_and_stream(
txn, self.get_aliases_for_room, (old_room_id,)
)
self._invalidate_cache_and_stream(
txn, self.get_aliases_for_room, (new_room_id,)
)
return self.runInteraction(
"_update_aliases_for_room_txn", _update_aliases_for_room_txn
)
24 changes: 24 additions & 0 deletions synapse/storage/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,3 +507,27 @@ def get_ratelimit_for_user(self, user_id):
))
else:
defer.returnValue(None)

@cached(max_entries=10000)
def is_room_blocked(self, room_id):
return self._simple_select_one_onecol(
table="blocked_rooms",
keyvalues={
"room_id": room_id,
},
retcol="1",
allow_none=True,
desc="is_room_blocked",
)

@defer.inlineCallbacks
def block_room(self, room_id, user_id):
yield self._simple_insert(
table="blocked_rooms",
values={
"room_id": room_id,
"user_id": user_id,
},
desc="block_room",
)
self.is_room_blocked.invalidate((room_id,))
21 changes: 21 additions & 0 deletions synapse/storage/schema/delta/43/blocked_rooms.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* Copyright 2017 Vector Creations Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

CREATE TABLE blocked_rooms (
room_id TEXT NOT NULL,
user_id TEXT NOT NULL -- Admin who blocked the room
);

CREATE UNIQUE INDEX blocked_rooms_idx ON blocked_rooms(room_id);

0 comments on commit b5f665d

Please sign in to comment.