Add option to enable encryption by default for new rooms (#7639)
Fixes https://github.com/matrix-org/synapse/issues/2431 Adds config option `encryption_enabled_by_default_for_room_type`, which determines whether encryption should be enabled with the default encryption algorithm in private or public rooms upon creation. Whether the room is private or public is decided based upon the room creation preset that is used. Part of this PR is also pulling out all of the individual instances of `m.megolm.v1.aes-sha2` into a constant variable to eliminate typos ala https://github.com/matrix-org/synapse/pull/7637 Based on #7637
This commit is contained in:
parent
191dc98f80
commit
fcd6961441
|
@ -0,0 +1 @@
|
|||
Add an option to enable encryption by default for new rooms.
|
|
@ -1973,6 +1973,26 @@ spam_checker:
|
|||
# example_stop_events_from: ['@bad:example.com']
|
||||
|
||||
|
||||
## Rooms ##
|
||||
|
||||
# Controls whether locally-created rooms should be end-to-end encrypted by
|
||||
# default.
|
||||
#
|
||||
# Possible options are "all", "invite", and "off". They are defined as:
|
||||
#
|
||||
# * "all": any locally-created room
|
||||
# * "invite": any room created with the "private_chat" or "trusted_private_chat"
|
||||
# room creation presets
|
||||
# * "off": this option will take no effect
|
||||
#
|
||||
# The default value is "off".
|
||||
#
|
||||
# Note that this option will only affect rooms created after it is set. It
|
||||
# will also not affect rooms created by other servers.
|
||||
#
|
||||
#encryption_enabled_by_default_for_room_type: invite
|
||||
|
||||
|
||||
# Uncomment to allow non-server-admin users to create groups on this server
|
||||
#
|
||||
#enable_group_creation: true
|
||||
|
|
|
@ -150,3 +150,8 @@ class EventContentFields(object):
|
|||
# Timestamp to delete the event after
|
||||
# cf https://github.com/matrix-org/matrix-doc/pull/2228
|
||||
SELF_DESTRUCT_AFTER = "org.matrix.self_destruct_after"
|
||||
|
||||
|
||||
class RoomEncryptionAlgorithms(object):
|
||||
MEGOLM_V1_AES_SHA2 = "m.megolm.v1.aes-sha2"
|
||||
DEFAULT = MEGOLM_V1_AES_SHA2
|
||||
|
|
|
@ -36,6 +36,7 @@ from .ratelimiting import RatelimitConfig
|
|||
from .redis import RedisConfig
|
||||
from .registration import RegistrationConfig
|
||||
from .repository import ContentRepositoryConfig
|
||||
from .room import RoomConfig
|
||||
from .room_directory import RoomDirectoryConfig
|
||||
from .saml2_config import SAML2Config
|
||||
from .server import ServerConfig
|
||||
|
@ -79,6 +80,7 @@ class HomeServerConfig(RootConfig):
|
|||
PasswordAuthProviderConfig,
|
||||
PushConfig,
|
||||
SpamCheckerConfig,
|
||||
RoomConfig,
|
||||
GroupsConfig,
|
||||
UserDirectoryConfig,
|
||||
ConsentConfig,
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
#
|
||||
# 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 synapse.api.constants import RoomCreationPreset
|
||||
|
||||
from ._base import Config, ConfigError
|
||||
|
||||
logger = logging.Logger(__name__)
|
||||
|
||||
|
||||
class RoomDefaultEncryptionTypes(object):
|
||||
"""Possible values for the encryption_enabled_by_default_for_room_type config option"""
|
||||
|
||||
ALL = "all"
|
||||
INVITE = "invite"
|
||||
OFF = "off"
|
||||
|
||||
|
||||
class RoomConfig(Config):
|
||||
section = "room"
|
||||
|
||||
def read_config(self, config, **kwargs):
|
||||
# Whether new, locally-created rooms should have encryption enabled
|
||||
encryption_for_room_type = config.get(
|
||||
"encryption_enabled_by_default_for_room_type",
|
||||
RoomDefaultEncryptionTypes.OFF,
|
||||
)
|
||||
if encryption_for_room_type == RoomDefaultEncryptionTypes.ALL:
|
||||
self.encryption_enabled_by_default_for_room_presets = [
|
||||
RoomCreationPreset.PRIVATE_CHAT,
|
||||
RoomCreationPreset.TRUSTED_PRIVATE_CHAT,
|
||||
RoomCreationPreset.PUBLIC_CHAT,
|
||||
]
|
||||
elif encryption_for_room_type == RoomDefaultEncryptionTypes.INVITE:
|
||||
self.encryption_enabled_by_default_for_room_presets = [
|
||||
RoomCreationPreset.PRIVATE_CHAT,
|
||||
RoomCreationPreset.TRUSTED_PRIVATE_CHAT,
|
||||
]
|
||||
elif encryption_for_room_type == RoomDefaultEncryptionTypes.OFF:
|
||||
self.encryption_enabled_by_default_for_room_presets = []
|
||||
else:
|
||||
raise ConfigError(
|
||||
"Invalid value for encryption_enabled_by_default_for_room_type"
|
||||
)
|
||||
|
||||
def generate_config_section(self, **kwargs):
|
||||
return """\
|
||||
## Rooms ##
|
||||
|
||||
# Controls whether locally-created rooms should be end-to-end encrypted by
|
||||
# default.
|
||||
#
|
||||
# Possible options are "all", "invite", and "off". They are defined as:
|
||||
#
|
||||
# * "all": any locally-created room
|
||||
# * "invite": any room created with the "private_chat" or "trusted_private_chat"
|
||||
# room creation presets
|
||||
# * "off": this option will take no effect
|
||||
#
|
||||
# The default value is "off".
|
||||
#
|
||||
# Note that this option will only affect rooms created after it is set. It
|
||||
# will also not affect rooms created by other servers.
|
||||
#
|
||||
#encryption_enabled_by_default_for_room_type: invite
|
||||
"""
|
|
@ -33,7 +33,12 @@ from unpaddedbase64 import decode_base64
|
|||
from twisted.internet import defer
|
||||
|
||||
from synapse import event_auth
|
||||
from synapse.api.constants import EventTypes, Membership, RejectedReason
|
||||
from synapse.api.constants import (
|
||||
EventTypes,
|
||||
Membership,
|
||||
RejectedReason,
|
||||
RoomEncryptionAlgorithms,
|
||||
)
|
||||
from synapse.api.errors import (
|
||||
AuthError,
|
||||
CodeMessageException,
|
||||
|
@ -742,7 +747,10 @@ class FederationHandler(BaseHandler):
|
|||
if device:
|
||||
keys = device.get("keys", {}).get("keys", {})
|
||||
|
||||
if event.content.get("algorithm") == "m.megolm.v1.aes-sha2":
|
||||
if (
|
||||
event.content.get("algorithm")
|
||||
== RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2
|
||||
):
|
||||
# For this algorithm we expect a curve25519 key.
|
||||
key_name = "curve25519:%s" % (device_id,)
|
||||
current_keys = [keys.get(key_name)]
|
||||
|
|
|
@ -26,7 +26,12 @@ from typing import Tuple
|
|||
|
||||
from six import iteritems, string_types
|
||||
|
||||
from synapse.api.constants import EventTypes, JoinRules, RoomCreationPreset
|
||||
from synapse.api.constants import (
|
||||
EventTypes,
|
||||
JoinRules,
|
||||
RoomCreationPreset,
|
||||
RoomEncryptionAlgorithms,
|
||||
)
|
||||
from synapse.api.errors import AuthError, Codes, NotFoundError, StoreError, SynapseError
|
||||
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion
|
||||
from synapse.events.utils import copy_power_levels_contents
|
||||
|
@ -56,8 +61,16 @@ FIVE_MINUTES_IN_MS = 5 * 60 * 1000
|
|||
|
||||
|
||||
class RoomCreationHandler(BaseHandler):
|
||||
def __init__(self, hs):
|
||||
super(RoomCreationHandler, self).__init__(hs)
|
||||
|
||||
PRESETS_DICT = {
|
||||
self.spam_checker = hs.get_spam_checker()
|
||||
self.event_creation_handler = hs.get_event_creation_handler()
|
||||
self.room_member_handler = hs.get_room_member_handler()
|
||||
self.config = hs.config
|
||||
|
||||
# Room state based off defined presets
|
||||
self._presets_dict = {
|
||||
RoomCreationPreset.PRIVATE_CHAT: {
|
||||
"join_rules": JoinRules.INVITE,
|
||||
"history_visibility": "shared",
|
||||
|
@ -81,13 +94,13 @@ class RoomCreationHandler(BaseHandler):
|
|||
},
|
||||
}
|
||||
|
||||
def __init__(self, hs):
|
||||
super(RoomCreationHandler, self).__init__(hs)
|
||||
|
||||
self.spam_checker = hs.get_spam_checker()
|
||||
self.event_creation_handler = hs.get_event_creation_handler()
|
||||
self.room_member_handler = hs.get_room_member_handler()
|
||||
self.config = hs.config
|
||||
# Modify presets to selectively enable encryption by default per homeserver config
|
||||
for preset_name, preset_config in self._presets_dict.items():
|
||||
encrypted = (
|
||||
preset_name
|
||||
in self.config.encryption_enabled_by_default_for_room_presets
|
||||
)
|
||||
preset_config["encrypted"] = encrypted
|
||||
|
||||
self._replication = hs.get_replication_data_handler()
|
||||
|
||||
|
@ -798,7 +811,7 @@ class RoomCreationHandler(BaseHandler):
|
|||
)
|
||||
return last_stream_id
|
||||
|
||||
config = RoomCreationHandler.PRESETS_DICT[preset_config]
|
||||
config = self._presets_dict[preset_config]
|
||||
|
||||
creator_id = creator.user.to_string()
|
||||
|
||||
|
@ -888,6 +901,13 @@ class RoomCreationHandler(BaseHandler):
|
|||
etype=etype, state_key=state_key, content=content
|
||||
)
|
||||
|
||||
if config["encrypted"]:
|
||||
last_sent_stream_id = await send(
|
||||
etype=EventTypes.RoomEncryption,
|
||||
state_key="",
|
||||
content={"algorithm": RoomEncryptionAlgorithms.DEFAULT},
|
||||
)
|
||||
|
||||
return last_sent_stream_id
|
||||
|
||||
async def _generate_room_id(
|
||||
|
|
|
@ -21,6 +21,7 @@ from signedjson.types import BaseKey, SigningKey
|
|||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.api.constants import RoomEncryptionAlgorithms
|
||||
from synapse.rest import admin
|
||||
from synapse.rest.client.v1 import login
|
||||
from synapse.types import JsonDict, ReadReceipt
|
||||
|
@ -536,7 +537,10 @@ def build_device_dict(user_id: str, device_id: str, sk: SigningKey):
|
|||
return {
|
||||
"user_id": user_id,
|
||||
"device_id": device_id,
|
||||
"algorithms": ["m.olm.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
"algorithms": [
|
||||
"m.olm.curve25519-aes-sha2",
|
||||
RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
|
||||
],
|
||||
"keys": {
|
||||
"curve25519:" + device_id: "curve25519+key",
|
||||
key_id(sk): encode_pubkey(sk),
|
||||
|
|
|
@ -25,6 +25,7 @@ from twisted.internet import defer
|
|||
import synapse.handlers.e2e_keys
|
||||
import synapse.storage
|
||||
from synapse.api import errors
|
||||
from synapse.api.constants import RoomEncryptionAlgorithms
|
||||
|
||||
from tests import unittest, utils
|
||||
|
||||
|
@ -222,7 +223,10 @@ class E2eKeysHandlerTestCase(unittest.TestCase):
|
|||
device_key_1 = {
|
||||
"user_id": local_user,
|
||||
"device_id": "abc",
|
||||
"algorithms": ["m.olm.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
"algorithms": [
|
||||
"m.olm.curve25519-aes-sha2",
|
||||
RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:abc": "base64+ed25519+key",
|
||||
"curve25519:abc": "base64+curve25519+key",
|
||||
|
@ -232,7 +236,10 @@ class E2eKeysHandlerTestCase(unittest.TestCase):
|
|||
device_key_2 = {
|
||||
"user_id": local_user,
|
||||
"device_id": "def",
|
||||
"algorithms": ["m.olm.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
"algorithms": [
|
||||
"m.olm.curve25519-aes-sha2",
|
||||
RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:def": "base64+ed25519+key",
|
||||
"curve25519:def": "base64+curve25519+key",
|
||||
|
@ -315,7 +322,10 @@ class E2eKeysHandlerTestCase(unittest.TestCase):
|
|||
device_key = {
|
||||
"user_id": local_user,
|
||||
"device_id": device_id,
|
||||
"algorithms": ["m.olm.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
"algorithms": [
|
||||
"m.olm.curve25519-aes-sha2",
|
||||
RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
|
||||
],
|
||||
"keys": {"curve25519:xyz": "curve25519+key", "ed25519:xyz": device_pubkey},
|
||||
"signatures": {local_user: {"ed25519:xyz": "something"}},
|
||||
}
|
||||
|
@ -392,7 +402,7 @@ class E2eKeysHandlerTestCase(unittest.TestCase):
|
|||
"device_id": device_id,
|
||||
"algorithms": [
|
||||
"m.olm.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2",
|
||||
RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
|
||||
],
|
||||
"keys": {
|
||||
"curve25519:xyz": "curve25519+key",
|
||||
|
|
|
@ -17,12 +17,13 @@ from mock import Mock
|
|||
from twisted.internet import defer
|
||||
|
||||
import synapse.rest.admin
|
||||
from synapse.api.constants import UserTypes
|
||||
from synapse.api.constants import EventTypes, RoomEncryptionAlgorithms, UserTypes
|
||||
from synapse.rest.client.v1 import login, room
|
||||
from synapse.rest.client.v2_alpha import user_directory
|
||||
from synapse.storage.roommember import ProfileInfo
|
||||
|
||||
from tests import unittest
|
||||
from tests.unittest import override_config
|
||||
|
||||
|
||||
class UserDirectoryTestCase(unittest.HomeserverTestCase):
|
||||
|
@ -147,6 +148,94 @@ class UserDirectoryTestCase(unittest.HomeserverTestCase):
|
|||
s = self.get_success(self.handler.search_users(u1, "user3", 10))
|
||||
self.assertEqual(len(s["results"]), 0)
|
||||
|
||||
@override_config({"encryption_enabled_by_default_for_room_type": "all"})
|
||||
def test_encrypted_by_default_config_option_all(self):
|
||||
"""Tests that invite-only and non-invite-only rooms have encryption enabled by
|
||||
default when the config option encryption_enabled_by_default_for_room_type is "all".
|
||||
"""
|
||||
# Create a user
|
||||
user = self.register_user("user", "pass")
|
||||
user_token = self.login(user, "pass")
|
||||
|
||||
# Create an invite-only room as that user
|
||||
room_id = self.helper.create_room_as(user, is_public=False, tok=user_token)
|
||||
|
||||
# Check that the room has an encryption state event
|
||||
event_content = self.helper.get_state(
|
||||
room_id=room_id, event_type=EventTypes.RoomEncryption, tok=user_token,
|
||||
)
|
||||
self.assertEqual(event_content, {"algorithm": RoomEncryptionAlgorithms.DEFAULT})
|
||||
|
||||
# Create a non invite-only room as that user
|
||||
room_id = self.helper.create_room_as(user, is_public=True, tok=user_token)
|
||||
|
||||
# Check that the room has an encryption state event
|
||||
event_content = self.helper.get_state(
|
||||
room_id=room_id, event_type=EventTypes.RoomEncryption, tok=user_token,
|
||||
)
|
||||
self.assertEqual(event_content, {"algorithm": RoomEncryptionAlgorithms.DEFAULT})
|
||||
|
||||
@override_config({"encryption_enabled_by_default_for_room_type": "invite"})
|
||||
def test_encrypted_by_default_config_option_invite(self):
|
||||
"""Tests that only new, invite-only rooms have encryption enabled by default when
|
||||
the config option encryption_enabled_by_default_for_room_type is "invite".
|
||||
"""
|
||||
# Create a user
|
||||
user = self.register_user("user", "pass")
|
||||
user_token = self.login(user, "pass")
|
||||
|
||||
# Create an invite-only room as that user
|
||||
room_id = self.helper.create_room_as(user, is_public=False, tok=user_token)
|
||||
|
||||
# Check that the room has an encryption state event
|
||||
event_content = self.helper.get_state(
|
||||
room_id=room_id, event_type=EventTypes.RoomEncryption, tok=user_token,
|
||||
)
|
||||
self.assertEqual(event_content, {"algorithm": RoomEncryptionAlgorithms.DEFAULT})
|
||||
|
||||
# Create a non invite-only room as that user
|
||||
room_id = self.helper.create_room_as(user, is_public=True, tok=user_token)
|
||||
|
||||
# Check that the room does not have an encryption state event
|
||||
self.helper.get_state(
|
||||
room_id=room_id,
|
||||
event_type=EventTypes.RoomEncryption,
|
||||
tok=user_token,
|
||||
expect_code=404,
|
||||
)
|
||||
|
||||
@override_config({"encryption_enabled_by_default_for_room_type": "off"})
|
||||
def test_encrypted_by_default_config_option_off(self):
|
||||
"""Tests that neither new invite-only nor non-invite-only rooms have encryption
|
||||
enabled by default when the config option
|
||||
encryption_enabled_by_default_for_room_type is "off".
|
||||
"""
|
||||
# Create a user
|
||||
user = self.register_user("user", "pass")
|
||||
user_token = self.login(user, "pass")
|
||||
|
||||
# Create an invite-only room as that user
|
||||
room_id = self.helper.create_room_as(user, is_public=False, tok=user_token)
|
||||
|
||||
# Check that the room does not have an encryption state event
|
||||
self.helper.get_state(
|
||||
room_id=room_id,
|
||||
event_type=EventTypes.RoomEncryption,
|
||||
tok=user_token,
|
||||
expect_code=404,
|
||||
)
|
||||
|
||||
# Create a non invite-only room as that user
|
||||
room_id = self.helper.create_room_as(user, is_public=True, tok=user_token)
|
||||
|
||||
# Check that the room does not have an encryption state event
|
||||
self.helper.get_state(
|
||||
room_id=room_id,
|
||||
event_type=EventTypes.RoomEncryption,
|
||||
tok=user_token,
|
||||
expect_code=404,
|
||||
)
|
||||
|
||||
def test_spam_checker(self):
|
||||
"""
|
||||
A user which fails to the spam checks will not appear in search results.
|
||||
|
|
|
@ -30,7 +30,7 @@ class MessageAcceptTests(unittest.HomeserverTestCase):
|
|||
room_creator = self.homeserver.get_room_creation_handler()
|
||||
room_deferred = ensureDeferred(
|
||||
room_creator.create_room(
|
||||
our_user, room_creator.PRESETS_DICT["public_chat"], ratelimit=False
|
||||
our_user, room_creator._presets_dict["public_chat"], ratelimit=False
|
||||
)
|
||||
)
|
||||
self.reactor.advance(0.1)
|
||||
|
|
Loading…
Reference in New Issue