Merge pull request #3232 from matrix-org/rav/server_notices_room
Infrastructure for a server notices roompull/3239/head
commit
6d9dc67139
|
@ -38,6 +38,7 @@ from .spam_checker import SpamCheckerConfig
|
||||||
from .groups import GroupsConfig
|
from .groups import GroupsConfig
|
||||||
from .user_directory import UserDirectoryConfig
|
from .user_directory import UserDirectoryConfig
|
||||||
from .consent_config import ConsentConfig
|
from .consent_config import ConsentConfig
|
||||||
|
from .server_notices_config import ServerNoticesConfig
|
||||||
|
|
||||||
|
|
||||||
class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
|
class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
|
||||||
|
@ -47,7 +48,9 @@ class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
|
||||||
JWTConfig, PasswordConfig, EmailConfig,
|
JWTConfig, PasswordConfig, EmailConfig,
|
||||||
WorkerConfig, PasswordAuthProviderConfig, PushConfig,
|
WorkerConfig, PasswordAuthProviderConfig, PushConfig,
|
||||||
SpamCheckerConfig, GroupsConfig, UserDirectoryConfig,
|
SpamCheckerConfig, GroupsConfig, UserDirectoryConfig,
|
||||||
ConsentConfig):
|
ConsentConfig,
|
||||||
|
ServerNoticesConfig,
|
||||||
|
):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -81,6 +81,7 @@ class FederationHandler(BaseHandler):
|
||||||
self.pusher_pool = hs.get_pusherpool()
|
self.pusher_pool = hs.get_pusherpool()
|
||||||
self.spam_checker = hs.get_spam_checker()
|
self.spam_checker = hs.get_spam_checker()
|
||||||
self.event_creation_handler = hs.get_event_creation_handler()
|
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
|
# When joining a room we need to queue any events for that room up
|
||||||
self.room_queues = {}
|
self.room_queues = {}
|
||||||
|
@ -1180,6 +1181,13 @@ class FederationHandler(BaseHandler):
|
||||||
if not self.is_mine_id(event.state_key):
|
if not self.is_mine_id(event.state_key):
|
||||||
raise SynapseError(400, "The invite event must be for this server")
|
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.outlier = True
|
||||||
event.internal_metadata.invite_from_remote = True
|
event.internal_metadata.invite_from_remote = True
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,11 @@ logger = logging.getLogger(__name__)
|
||||||
class RegistrationHandler(BaseHandler):
|
class RegistrationHandler(BaseHandler):
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hs (synapse.server.HomeServer):
|
||||||
|
"""
|
||||||
super(RegistrationHandler, self).__init__(hs)
|
super(RegistrationHandler, self).__init__(hs)
|
||||||
|
|
||||||
self.auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
|
@ -49,6 +54,7 @@ class RegistrationHandler(BaseHandler):
|
||||||
self._generate_user_id_linearizer = Linearizer(
|
self._generate_user_id_linearizer = Linearizer(
|
||||||
name="_generate_user_id_linearizer",
|
name="_generate_user_id_linearizer",
|
||||||
)
|
)
|
||||||
|
self._server_notices_mxid = hs.config.server_notices_mxid
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def check_username(self, localpart, guest_access_token=None,
|
def check_username(self, localpart, guest_access_token=None,
|
||||||
|
@ -338,6 +344,14 @@ class RegistrationHandler(BaseHandler):
|
||||||
yield identity_handler.bind_threepid(c, user_id)
|
yield identity_handler.bind_threepid(c, user_id)
|
||||||
|
|
||||||
def check_user_id_not_appservice_exclusive(self, user_id, allowed_appservice=None):
|
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
|
# valid user IDs must not clash with any user ID namespaces claimed by
|
||||||
# application services.
|
# application services.
|
||||||
services = self.store.get_app_services()
|
services = self.store.get_app_services()
|
||||||
|
|
|
@ -68,7 +68,8 @@ class RoomCreationHandler(BaseHandler):
|
||||||
self.event_creation_handler = hs.get_event_creation_handler()
|
self.event_creation_handler = hs.get_event_creation_handler()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@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.
|
""" Creates a new room.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -76,6 +77,14 @@ class RoomCreationHandler(BaseHandler):
|
||||||
The user who requested the room creation.
|
The user who requested the room creation.
|
||||||
config (dict) : A dict of configuration options.
|
config (dict) : A dict of configuration options.
|
||||||
ratelimit (bool): set to False to disable the rate limiter
|
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:
|
Returns:
|
||||||
Deferred[dict]:
|
Deferred[dict]:
|
||||||
a dict containing the keys `room_id` and, if an alias was
|
a dict containing the keys `room_id` and, if an alias was
|
||||||
|
@ -180,7 +189,8 @@ class RoomCreationHandler(BaseHandler):
|
||||||
initial_state=initial_state,
|
initial_state=initial_state,
|
||||||
creation_content=creation_content,
|
creation_content=creation_content,
|
||||||
room_alias=room_alias,
|
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:
|
if "name" in config:
|
||||||
|
@ -260,6 +270,7 @@ class RoomCreationHandler(BaseHandler):
|
||||||
creation_content,
|
creation_content,
|
||||||
room_alias,
|
room_alias,
|
||||||
power_level_content_override,
|
power_level_content_override,
|
||||||
|
creator_join_profile,
|
||||||
):
|
):
|
||||||
def create(etype, content, **kwargs):
|
def create(etype, content, **kwargs):
|
||||||
e = {
|
e = {
|
||||||
|
@ -303,6 +314,7 @@ class RoomCreationHandler(BaseHandler):
|
||||||
room_id,
|
room_id,
|
||||||
"join",
|
"join",
|
||||||
ratelimit=False,
|
ratelimit=False,
|
||||||
|
content=creator_join_profile,
|
||||||
)
|
)
|
||||||
|
|
||||||
# We treat the power levels override specially as this needs to be one
|
# We treat the power levels override specially as this needs to be one
|
||||||
|
|
|
@ -17,11 +17,14 @@
|
||||||
import abc
|
import abc
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from six.moves import http_client
|
||||||
|
|
||||||
from signedjson.key import decode_verify_key_bytes
|
from signedjson.key import decode_verify_key_bytes
|
||||||
from signedjson.sign import verify_signed_json
|
from signedjson.sign import verify_signed_json
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from unpaddedbase64 import decode_base64
|
from unpaddedbase64 import decode_base64
|
||||||
|
|
||||||
|
import synapse.server
|
||||||
import synapse.types
|
import synapse.types
|
||||||
from synapse.api.constants import (
|
from synapse.api.constants import (
|
||||||
EventTypes, Membership,
|
EventTypes, Membership,
|
||||||
|
@ -46,6 +49,11 @@ class RoomMemberHandler(object):
|
||||||
__metaclass__ = abc.ABCMeta
|
__metaclass__ = abc.ABCMeta
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hs (synapse.server.HomeServer):
|
||||||
|
"""
|
||||||
self.hs = hs
|
self.hs = hs
|
||||||
self.store = hs.get_datastore()
|
self.store = hs.get_datastore()
|
||||||
self.auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
|
@ -63,6 +71,7 @@ class RoomMemberHandler(object):
|
||||||
|
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
self.spam_checker = hs.get_spam_checker()
|
self.spam_checker = hs.get_spam_checker()
|
||||||
|
self._server_notices_mxid = self.config.server_notices_mxid
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def _remote_join(self, requester, remote_room_hosts, room_id, user, content):
|
def _remote_join(self, requester, remote_room_hosts, room_id, user, content):
|
||||||
|
@ -289,12 +298,36 @@ class RoomMemberHandler(object):
|
||||||
is_blocked = yield self.store.is_room_blocked(room_id)
|
is_blocked = yield self.store.is_room_blocked(room_id)
|
||||||
if is_blocked:
|
if is_blocked:
|
||||||
raise SynapseError(403, "This room has been blocked on this server")
|
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
|
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 not is_requester_admin:
|
||||||
if self.config.block_non_admin_invites:
|
if self.config.block_non_admin_invites:
|
||||||
logger.info(
|
logger.info(
|
||||||
|
@ -844,6 +877,13 @@ class RoomMemberHandler(object):
|
||||||
|
|
||||||
defer.returnValue(False)
|
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):
|
class RoomMemberMasterHandler(RoomMemberHandler):
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
|
|
|
@ -72,6 +72,7 @@ from synapse.rest.media.v1.media_repository import (
|
||||||
MediaRepository,
|
MediaRepository,
|
||||||
MediaRepositoryResource,
|
MediaRepositoryResource,
|
||||||
)
|
)
|
||||||
|
from synapse.server_notices.server_notices_manager import ServerNoticesManager
|
||||||
from synapse.state import StateHandler, StateResolutionHandler
|
from synapse.state import StateHandler, StateResolutionHandler
|
||||||
from synapse.storage import DataStore
|
from synapse.storage import DataStore
|
||||||
from synapse.streams.events import EventSources
|
from synapse.streams.events import EventSources
|
||||||
|
@ -156,6 +157,7 @@ class HomeServer(object):
|
||||||
'spam_checker',
|
'spam_checker',
|
||||||
'room_member_handler',
|
'room_member_handler',
|
||||||
'federation_registry',
|
'federation_registry',
|
||||||
|
'server_notices_manager',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, hostname, **kwargs):
|
def __init__(self, hostname, **kwargs):
|
||||||
|
@ -398,6 +400,9 @@ class HomeServer(object):
|
||||||
def build_federation_registry(self):
|
def build_federation_registry(self):
|
||||||
return FederationHandlerRegistry()
|
return FederationHandlerRegistry()
|
||||||
|
|
||||||
|
def build_server_notices_manager(self):
|
||||||
|
return ServerNoticesManager(self)
|
||||||
|
|
||||||
def remove_pusher(self, app_id, push_key, user_id):
|
def remove_pusher(self, app_id, push_key, user_id):
|
||||||
return self.get_pusherpool().remove_pusher(app_id, push_key, user_id)
|
return self.get_pusherpool().remove_pusher(app_id, push_key, user_id)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import synapse.api.auth
|
import synapse.api.auth
|
||||||
|
import synapse.config.homeserver
|
||||||
import synapse.federation.transaction_queue
|
import synapse.federation.transaction_queue
|
||||||
import synapse.federation.transport.client
|
import synapse.federation.transport.client
|
||||||
import synapse.handlers
|
import synapse.handlers
|
||||||
|
@ -8,11 +9,16 @@ import synapse.handlers.device
|
||||||
import synapse.handlers.e2e_keys
|
import synapse.handlers.e2e_keys
|
||||||
import synapse.handlers.set_password
|
import synapse.handlers.set_password
|
||||||
import synapse.rest.media.v1.media_repository
|
import synapse.rest.media.v1.media_repository
|
||||||
|
import synapse.server_notices.server_notices_manager
|
||||||
import synapse.state
|
import synapse.state
|
||||||
import synapse.storage
|
import synapse.storage
|
||||||
|
|
||||||
|
|
||||||
class HomeServer(object):
|
class HomeServer(object):
|
||||||
|
@property
|
||||||
|
def config(self) -> synapse.config.homeserver.HomeServerConfig:
|
||||||
|
pass
|
||||||
|
|
||||||
def get_auth(self) -> synapse.api.auth.Auth:
|
def get_auth(self) -> synapse.api.auth.Auth:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -43,6 +49,9 @@ class HomeServer(object):
|
||||||
def get_room_creation_handler(self) -> synapse.handlers.room.RoomCreationHandler:
|
def get_room_creation_handler(self) -> synapse.handlers.room.RoomCreationHandler:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def get_event_creation_handler(self) -> synapse.handlers.message.EventCreationHandler:
|
||||||
|
pass
|
||||||
|
|
||||||
def get_set_password_handler(self) -> synapse.handlers.set_password.SetPasswordHandler:
|
def get_set_password_handler(self) -> synapse.handlers.set_password.SetPasswordHandler:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -57,3 +66,6 @@ class HomeServer(object):
|
||||||
|
|
||||||
def get_media_repository(self) -> synapse.rest.media.v1.media_repository.MediaRepository:
|
def get_media_repository(self) -> synapse.rest.media.v1.media_repository.MediaRepository:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def get_server_notices_manager(self) -> synapse.server_notices.server_notices_manager.ServerNoticesManager:
|
||||||
|
pass
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
# -*- 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):
|
||||||
|
"""Checks if server notices are enabled on this server.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool
|
||||||
|
"""
|
||||||
|
return self._config.server_notices_mxid is not None
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def send_notice(self, user_id, event_content):
|
||||||
|
"""Send a notice to the given user
|
||||||
|
|
||||||
|
Creates the server notices room, if none exists.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id (str): mxid of user to send event to.
|
||||||
|
event_content (dict): content of event to send
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferrred[None]
|
||||||
|
"""
|
||||||
|
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:
|
||||||
|
# it's worth noting that there is an asymmetry here in that we
|
||||||
|
# expect the user to be invited or joined, but the system user must
|
||||||
|
# be joined. This is kinda deliberate, in that if somebody somehow
|
||||||
|
# manages to invite the system user to a room, that doesn't make it
|
||||||
|
# the server notices room.
|
||||||
|
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…
Reference in New Issue