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 #3232 from matrix-org/rav/server_notices_room
Browse files Browse the repository at this point in the history
Infrastructure for a server notices room
  • Loading branch information
richvdh authored May 18, 2018
2 parents ed3125b + 011e1f4 commit 6d9dc67
Show file tree
Hide file tree
Showing 10 changed files with 309 additions and 7 deletions.
5 changes: 4 additions & 1 deletion synapse/config/homeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from .groups import GroupsConfig
from .user_directory import UserDirectoryConfig
from .consent_config import ConsentConfig
from .server_notices_config import ServerNoticesConfig


class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
Expand All @@ -47,7 +48,9 @@ class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
JWTConfig, PasswordConfig, EmailConfig,
WorkerConfig, PasswordAuthProviderConfig, PushConfig,
SpamCheckerConfig, GroupsConfig, UserDirectoryConfig,
ConsentConfig):
ConsentConfig,
ServerNoticesConfig,
):
pass


Expand Down
77 changes: 77 additions & 0 deletions synapse/config/server_notices_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
# Copyright 2018 New Vector 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.
from ._base import Config
from synapse.types import UserID

DEFAULT_CONFIG = """\
# Server Notices room configuration
#
# Uncomment this section to enable a room which can be used to send notices
# from the server to users. It is a special room which cannot be left; notices
# come from a special "notices" user id.
#
# If you uncomment this section, you *must* define the system_mxid_localpart
# setting, which defines the id of the user which will be used to send the
# notices.
#
# It's also possible to override the room name, or the display name of the
# "notices" user.
#
# server_notices:
# system_mxid_localpart: notices
# system_mxid_display_name: "Server Notices"
# room_name: "Server Notices"
"""


class ServerNoticesConfig(Config):
"""Configuration for the server notices room.
Attributes:
server_notices_mxid (str|None):
The MXID to use for server notices.
None if server notices are not enabled.
server_notices_mxid_display_name (str|None):
The display name to use for the server notices user.
None if server notices are not enabled.
server_notices_room_name (str|None):
The name to use for the server notices room.
None if server notices are not enabled.
"""
def __init__(self):
super(ServerNoticesConfig, self).__init__()
self.server_notices_mxid = None
self.server_notices_mxid_display_name = None
self.server_notices_room_name = None

def read_config(self, config):
c = config.get("server_notices")
if c is None:
return

mxid_localpart = c['system_mxid_localpart']
self.server_notices_mxid = UserID(
mxid_localpart, self.server_name,
).to_string()
self.server_notices_mxid_display_name = c.get(
'system_mxid_display_name', 'Server Notices',
)
# todo: i18n
self.server_notices_room_name = c.get('room_name', "Server Notices")

def default_config(self, **kwargs):
return DEFAULT_CONFIG
8 changes: 8 additions & 0 deletions synapse/handlers/federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def __init__(self, hs):
self.pusher_pool = hs.get_pusherpool()
self.spam_checker = hs.get_spam_checker()
self.event_creation_handler = hs.get_event_creation_handler()
self._server_notices_mxid = hs.config.server_notices_mxid

# When joining a room we need to queue any events for that room up
self.room_queues = {}
Expand Down Expand Up @@ -1180,6 +1181,13 @@ def on_invite_request(self, origin, pdu):
if not self.is_mine_id(event.state_key):
raise SynapseError(400, "The invite event must be for this server")

# block any attempts to invite the server notices mxid
if event.state_key == self._server_notices_mxid:
raise SynapseError(
http_client.FORBIDDEN,
"Cannot invite this user",
)

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

Expand Down
14 changes: 14 additions & 0 deletions synapse/handlers/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
class RegistrationHandler(BaseHandler):

def __init__(self, hs):
"""
Args:
hs (synapse.server.HomeServer):
"""
super(RegistrationHandler, self).__init__(hs)

self.auth = hs.get_auth()
Expand All @@ -49,6 +54,7 @@ def __init__(self, hs):
self._generate_user_id_linearizer = Linearizer(
name="_generate_user_id_linearizer",
)
self._server_notices_mxid = hs.config.server_notices_mxid

@defer.inlineCallbacks
def check_username(self, localpart, guest_access_token=None,
Expand Down Expand Up @@ -338,6 +344,14 @@ def bind_emails(self, user_id, threepidCreds):
yield identity_handler.bind_threepid(c, user_id)

def check_user_id_not_appservice_exclusive(self, user_id, allowed_appservice=None):
# don't allow people to register the server notices mxid
if self._server_notices_mxid is not None:
if user_id == self._server_notices_mxid:
raise SynapseError(
400, "This user ID is reserved.",
errcode=Codes.EXCLUSIVE
)

# valid user IDs must not clash with any user ID namespaces claimed by
# application services.
services = self.store.get_app_services()
Expand Down
16 changes: 14 additions & 2 deletions synapse/handlers/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,23 @@ def __init__(self, hs):
self.event_creation_handler = hs.get_event_creation_handler()

@defer.inlineCallbacks
def create_room(self, requester, config, ratelimit=True):
def create_room(self, requester, config, ratelimit=True,
creator_join_profile=None):
""" Creates a new room.
Args:
requester (synapse.types.Requester):
The user who requested the room creation.
config (dict) : A dict of configuration options.
ratelimit (bool): set to False to disable the rate limiter
creator_join_profile (dict|None):
Set to override the displayname and avatar for the creating
user in this room. If unset, displayname and avatar will be
derived from the user's profile. If set, should contain the
values to go in the body of the 'join' event (typically
`avatar_url` and/or `displayname`.
Returns:
Deferred[dict]:
a dict containing the keys `room_id` and, if an alias was
Expand Down Expand Up @@ -180,7 +189,8 @@ def create_room(self, requester, config, ratelimit=True):
initial_state=initial_state,
creation_content=creation_content,
room_alias=room_alias,
power_level_content_override=config.get("power_level_content_override", {})
power_level_content_override=config.get("power_level_content_override", {}),
creator_join_profile=creator_join_profile,
)

if "name" in config:
Expand Down Expand Up @@ -260,6 +270,7 @@ def _send_events_for_new_room(
creation_content,
room_alias,
power_level_content_override,
creator_join_profile,
):
def create(etype, content, **kwargs):
e = {
Expand Down Expand Up @@ -303,6 +314,7 @@ def send(etype, content, **kwargs):
room_id,
"join",
ratelimit=False,
content=creator_join_profile,
)

# We treat the power levels override specially as this needs to be one
Expand Down
48 changes: 44 additions & 4 deletions synapse/handlers/room_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
import abc
import logging

from six.moves import http_client

from signedjson.key import decode_verify_key_bytes
from signedjson.sign import verify_signed_json
from twisted.internet import defer
from unpaddedbase64 import decode_base64

import synapse.server
import synapse.types
from synapse.api.constants import (
EventTypes, Membership,
Expand All @@ -46,6 +49,11 @@ class RoomMemberHandler(object):
__metaclass__ = abc.ABCMeta

def __init__(self, hs):
"""
Args:
hs (synapse.server.HomeServer):
"""
self.hs = hs
self.store = hs.get_datastore()
self.auth = hs.get_auth()
Expand All @@ -63,6 +71,7 @@ def __init__(self, hs):

self.clock = hs.get_clock()
self.spam_checker = hs.get_spam_checker()
self._server_notices_mxid = self.config.server_notices_mxid

@abc.abstractmethod
def _remote_join(self, requester, remote_room_hosts, room_id, user, content):
Expand Down Expand Up @@ -289,12 +298,36 @@ def _update_membership(
is_blocked = yield self.store.is_room_blocked(room_id)
if is_blocked:
raise SynapseError(403, "This room has been blocked on this server")
else:
# we don't allow people to reject invites to, or leave, the
# server notice room.
is_blocked = yield self._is_server_notice_room(room_id)
if is_blocked:
raise SynapseError(
http_client.FORBIDDEN,
"You cannot leave this room",
)

if effective_membership_state == Membership.INVITE:
# block any attempts to invite the server notices mxid
if target.to_string() == self._server_notices_mxid:
raise SynapseError(
http_client.FORBIDDEN,
"Cannot invite this user",
)

if effective_membership_state == "invite":
block_invite = False
is_requester_admin = yield self.auth.is_server_admin(
requester.user,
)

if (self._server_notices_mxid is not None and
requester.user.to_string() == self._server_notices_mxid):
# allow the server notices mxid to send invites
is_requester_admin = True

else:
is_requester_admin = yield self.auth.is_server_admin(
requester.user,
)

if not is_requester_admin:
if self.config.block_non_admin_invites:
logger.info(
Expand Down Expand Up @@ -844,6 +877,13 @@ def _is_host_in_room(self, current_state_ids):

defer.returnValue(False)

@defer.inlineCallbacks
def _is_server_notice_room(self, room_id):
if self._server_notices_mxid is None:
defer.returnValue(False)
user_ids = yield self.store.get_users_in_room(room_id)
defer.returnValue(self._server_notices_mxid in user_ids)


class RoomMemberMasterHandler(RoomMemberHandler):
def __init__(self, hs):
Expand Down
5 changes: 5 additions & 0 deletions synapse/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
MediaRepository,
MediaRepositoryResource,
)
from synapse.server_notices.server_notices_manager import ServerNoticesManager
from synapse.state import StateHandler, StateResolutionHandler
from synapse.storage import DataStore
from synapse.streams.events import EventSources
Expand Down Expand Up @@ -156,6 +157,7 @@ def build_DEPENDENCY(self)
'spam_checker',
'room_member_handler',
'federation_registry',
'server_notices_manager',
]

def __init__(self, hostname, **kwargs):
Expand Down Expand Up @@ -398,6 +400,9 @@ def build_room_member_handler(self):
def build_federation_registry(self):
return FederationHandlerRegistry()

def build_server_notices_manager(self):
return ServerNoticesManager(self)

def remove_pusher(self, app_id, push_key, user_id):
return self.get_pusherpool().remove_pusher(app_id, push_key, user_id)

Expand Down
12 changes: 12 additions & 0 deletions synapse/server.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import synapse.api.auth
import synapse.config.homeserver
import synapse.federation.transaction_queue
import synapse.federation.transport.client
import synapse.handlers
Expand All @@ -8,11 +9,16 @@ import synapse.handlers.device
import synapse.handlers.e2e_keys
import synapse.handlers.set_password
import synapse.rest.media.v1.media_repository
import synapse.server_notices.server_notices_manager
import synapse.state
import synapse.storage


class HomeServer(object):
@property
def config(self) -> synapse.config.homeserver.HomeServerConfig:
pass

def get_auth(self) -> synapse.api.auth.Auth:
pass

Expand Down Expand Up @@ -43,6 +49,9 @@ class HomeServer(object):
def get_room_creation_handler(self) -> synapse.handlers.room.RoomCreationHandler:
pass

def get_event_creation_handler(self) -> synapse.handlers.message.EventCreationHandler:
pass

def get_set_password_handler(self) -> synapse.handlers.set_password.SetPasswordHandler:
pass

Expand All @@ -57,3 +66,6 @@ class HomeServer(object):

def get_media_repository(self) -> synapse.rest.media.v1.media_repository.MediaRepository:
pass

def get_server_notices_manager(self) -> synapse.server_notices.server_notices_manager.ServerNoticesManager:
pass
Empty file.
Loading

0 comments on commit 6d9dc67

Please sign in to comment.