瀏覽代碼

Infrastructure for a server notices room

Server Notices use a special room which the user can't dismiss. They are
created on demand when some other bit of the code calls send_notice.

(This doesn't actually do much yet becuse we don't call send_notice anywhere)
tags/v0.30.0-rc1
Richard van der Hoff 6 年之前
父節點
當前提交
fed62e21ad
共有 9 個檔案被更改,包括 280 行新增7 行删除
  1. +4
    -1
      synapse/config/homeserver.py
  2. +85
    -0
      synapse/config/server_notices_config.py
  3. +14
    -0
      synapse/handlers/register.py
  4. +14
    -2
      synapse/handlers/room.py
  5. +36
    -4
      synapse/handlers/room_member.py
  6. +5
    -0
      synapse/server.py
  7. +12
    -0
      synapse/server.pyi
  8. +0
    -0
     
  9. +110
    -0
      synapse/server_notices/server_notices_manager.py

+ 4
- 1
synapse/config/homeserver.py 查看文件

@@ -38,6 +38,7 @@ from .spam_checker import SpamCheckerConfig
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,
@@ -47,7 +48,9 @@ class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
JWTConfig, PasswordConfig, EmailConfig,
WorkerConfig, PasswordAuthProviderConfig, PushConfig,
SpamCheckerConfig, GroupsConfig, UserDirectoryConfig,
ConsentConfig):
ConsentConfig,
ServerNoticesConfig,
):
pass




+ 85
- 0
synapse/config/server_notices_config.py 查看文件

@@ -0,0 +1,85 @@
# -*- 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):
def __init__(self):
super(ServerNoticesConfig, self).__init__()

"""The MXID to use for server notices.

None if server notices are not enabled.

type: str|None
"""
self.server_notices_mxid = None

"""The display name to use for the server notices user.

None if server notices are not enabled.

type: str|None
"""
self.server_notices_mxid_display_name = None

"""The name to use for the server notices room.

None if server notices are not enabled.

(TODO: i18n)

type: str|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',
)

self.server_notices_room_name = c.get('room_name', "Server Notices")

def default_config(self, **kwargs):
return DEFAULT_CONFIG

+ 14
- 0
synapse/handlers/register.py 查看文件

@@ -34,6 +34,11 @@ logger = logging.getLogger(__name__)
class RegistrationHandler(BaseHandler):

def __init__(self, hs):
"""

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

self.auth = hs.get_auth()
@@ -49,6 +54,7 @@ class RegistrationHandler(BaseHandler):
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,
@@ -338,6 +344,14 @@ class RegistrationHandler(BaseHandler):
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()


+ 14
- 2
synapse/handlers/room.py 查看文件

@@ -68,7 +68,8 @@ class RoomCreationHandler(BaseHandler):
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:
@@ -76,6 +77,14 @@ class RoomCreationHandler(BaseHandler):
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
@@ -180,7 +189,8 @@ class RoomCreationHandler(BaseHandler):
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:
@@ -260,6 +270,7 @@ class RoomCreationHandler(BaseHandler):
creation_content,
room_alias,
power_level_content_override,
creator_join_profile,
):
def create(etype, content, **kwargs):
e = {
@@ -303,6 +314,7 @@ class RoomCreationHandler(BaseHandler):
room_id,
"join",
ratelimit=False,
content=creator_join_profile,
)

# We treat the power levels override specially as this needs to be one


+ 36
- 4
synapse/handlers/room_member.py 查看文件

@@ -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,
@@ -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()
@@ -63,6 +71,7 @@ class RoomMemberHandler(object):

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):
@@ -289,12 +298,28 @@ class RoomMemberHandler(object):
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:
if self._is_server_notice_room(room_id):
# we don't allow people to reject invites to, or leave, the
# server notice room.
raise SynapseError(
http_client.FORBIDDEN,
"You cannot leave this room",
)

if effective_membership_state == "invite":
if effective_membership_state == Membership.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(
@@ -844,6 +869,13 @@ class RoomMemberHandler(object):

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):


+ 5
- 0
synapse/server.py 查看文件

@@ -72,6 +72,7 @@ from synapse.rest.media.v1.media_repository import (
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
@@ -156,6 +157,7 @@ class HomeServer(object):
'spam_checker',
'room_member_handler',
'federation_registry',
'server_notices_manager',
]

def __init__(self, hostname, **kwargs):
@@ -398,6 +400,9 @@ class HomeServer(object):
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)



+ 12
- 0
synapse/server.pyi 查看文件

@@ -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
@@ -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

@@ -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

@@ -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

+ 0
- 0
查看文件


+ 110
- 0
synapse/server_notices/server_notices_manager.py 查看文件

@@ -0,0 +1,110 @@
# -*- 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.
import logging

from twisted.internet import defer

from synapse.api.constants import EventTypes, Membership, RoomCreationPreset
from synapse.types import create_requester
from synapse.util.caches.descriptors import cachedInlineCallbacks

logger = logging.getLogger(__name__)


class ServerNoticesManager(object):
def __init__(self, hs):
"""

Args:
hs (synapse.server.HomeServer):
"""

self._store = hs.get_datastore()
self._config = hs.config
self._room_creation_handler = hs.get_room_creation_handler()
self._event_creation_handler = hs.get_event_creation_handler()

def is_enabled(self):
return self._config.server_notices_mxid is not None

@defer.inlineCallbacks
def send_notice(self, user_id, event_content):
room_id = yield self.get_notice_room_for_user(user_id)

system_mxid = self._config.server_notices_mxid
requester = create_requester(system_mxid)

logger.info("Sending server notice to %s", user_id)

yield self._event_creation_handler.create_and_send_nonmember_event(
requester, {
"type": EventTypes.Message,
"room_id": room_id,
"sender": system_mxid,
"content": event_content,
},
ratelimit=False,
)

@cachedInlineCallbacks()
def get_notice_room_for_user(self, user_id):
"""Get the room for notices for a given user

If we have not yet created a notice room for this user, create it

Args:
user_id (str): complete user id for the user we want a room for

Returns:
str: room id of notice room.
"""
if not self.is_enabled():
raise Exception("Server notices not enabled")

rooms = yield self._store.get_rooms_for_user_where_membership_is(
user_id, [Membership.INVITE, Membership.JOIN],
)
system_mxid = self._config.server_notices_mxid
for room in rooms:
user_ids = yield self._store.get_users_in_room(room.room_id)
if system_mxid in user_ids:
# we found a room which our user shares with the system notice
# user
logger.info("Using room %s", room.room_id)
defer.returnValue(room.room_id)

# apparently no existing notice room: create a new one
logger.info("Creating server notices room for %s", user_id)

requester = create_requester(system_mxid)
info = yield self._room_creation_handler.create_room(
requester,
config={
"preset": RoomCreationPreset.PRIVATE_CHAT,
"name": self._config.server_notices_room_name,
"power_level_content_override": {
"users_default": -10,
},
"invite": (user_id,)
},
ratelimit=False,
creator_join_profile={
"displayname": self._config.server_notices_mxid_display_name,
},
)
room_id = info['room_id']

logger.info("Created server notices room %s for %s", room_id, user_id)
defer.returnValue(room_id)

Loading…
取消
儲存