Skip to content

Instantly share code, notes, and snippets.

@cmuller
Last active May 7, 2024 14:19
Show Gist options
  • Save cmuller/518ae8c49c76fb40457ec3065c048b5f to your computer and use it in GitHub Desktop.
Save cmuller/518ae8c49c76fb40457ec3065c048b5f to your computer and use it in GitHub Desktop.
Matrix Synapse : how to prevent users from creating rooms or communities

Matrix Synapse : how to prevent users from creating rooms or communities

This can be useful for admins running local synapse servers.

Forbid Group/Community creation

in order to prevent users from creating communities, there is an option: put in your /matrix/synapse/config/homeserver.yaml file:

enable_group_creation: false

Forbid Room creation

in order to prevent users from creating rooms, it is a bit more complex: you have to use Synapse Third party event rules mechanism. They allow Synapse admins to specify an additional set of rules on when to allow or deny an event.

There is few documentation. I found only:

Below is what I use and is (almost) working:

  • the logs are a bit hard to configure and are working (the code will run in a deffered twisted context),
  • the room creation filter is working, but
  • the prohibition of encryption (this is a legal obligation for us) works with some clients and not with others

Extract from /matrix/synapse/config/homeserver.yaml:

third_party_event_rules:
    config:
        creators:
        - '@admin:domain.com'
        - '@other:domain.com'
    module: domain_event_rules.DomainRulesModule

Extract from /etc/systemd/system/matrix-synapse.service (it is a Docker-based server installed by ansible):

ExecStart=/usr/bin/docker run --rm --name matrix-synapse \
[...]
      --mount type=bind,\
      src=/matrix/synapse/ext/domain_event_rules.py,\
      dst=/usr/local/lib/python3.7/site-packages/domain_event_rules.py,\
      ro \
[...]

Extract from /matrix/synapse/ext/domain_event_rules.py:

import logging
import sys
from twisted.internet import defer
from twisted.logger import Logger, eventAsText, FileLogObserver
from synapse.api.errors import SynapseError

class DomainRulesModule(object):
    log = Logger()
    log.observer.addObserver(FileLogObserver(sys.stdout, lambda e: eventAsText(e) + "\n"))

    def __init__(self, config, http_client):
        self.log.debug('======== Domain Rules Module init ========')
        self.creators = config['creators']
        self.log.debug('=== debug creators ===')
        self.log.debug('* type: ' + type(self.creators).__name__)
        self.log.debug('* values: ' + ', '.join("%s" % i for i in self.creators))
        self.log.debug('======== end init of Domain Rules Module ========')

    def check_event_allowed(self, event, context):
        self.log.debug('======== Domain Rules Module check_event_allowed ========')
        if event.type == "m.room.encryption":
            self.log.debug('=== m.room.encryption event ===')
            valueslist = list(context.values())
            ev = valueslist[0]
            self.log.debug("   * content: {data!r}.", data=ev['content'])
            self.log.debug('*** FORBIDDEN ENCRYPTION REQUEST ***')
            return False
        else:
            return True

    def on_create_room(self, requester, config, is_requester_admin): 
        self.log.debug('======== Domain Rules Module on_create_room ========')
        self.log.debug('=== full requester ===')
        self.log.debug('* type: ' + type(requester).__name__)
        self.log.debug('* name: ' + requester.user.to_string())
        self.log.debug('=== debug config ===')
        self.log.debug('* type: ' + type(config).__name__)
        self.log.debug('* keys and values:')
        self.log.debug("* Got event keys: {data!r}.", data=config.keys())
        self.log.debug("* Got event values: {data!r}.", data=config.values())
        if requester.user.to_string() in self.creators:
            self.log.debug('=== is rooms created by authorized person ===')
            return True
        if 'is_direct' in config.keys():
            self.log.debug('=== is direct conversation ===')
            return True
        if is_requester_admin:
            self.log.debug('=== is room created by admin ===')
            return True
        else:
            raise SynapseError(403, "You are not permitted to create rooms")
            return False

    @staticmethod
    def parse_config(config):
        if config == None:
            raise Exception('Missing config for Domain Rules Module')
        if 'creators' not in config:
            raise Exception('Missing creators parameter for Domain Rules Module')
        return config
  • it is possible to add structures to the config in homeserver.yaml, parse them and use them (here the creators list)
  • with the Logger it is possible to display quite a lot on the arguments received and see what kind of tests can be implemented (here tests on is_requester_admin or is_direct)

What is not working:

The definition of check_event_allowed actually blocks any attempt to make a room as encrypted. But, in some cases it also prevents to create direct conversations..

  • in (web UI) riot (v1.5.15) => it works because the client is (was) not asking to setup a recovery key for encrypted information.
  • in Riot Desktop => when people setup encryption instead of skipping it, then they can no longer create direct conversations.

A creation not only calls on_create_room but then there is a series of events and one is blocked by the rules above:

  • on_create_room => OK ("is direct conversation")
  • check_event_allowed(m.room.create) => OK ({'room_version': '5', 'creator': '@g...'})
  • check_event_allowed(m.room.member) => OK ({'membership': 'join',...)
  • check_event_allowed(m.room.power_levels) => OK ({'users': {'@...)
  • check_event_allowed(m.room.join_rules) => OK ({'join_rule': 'invite'})
  • check_event_allowed(m.room.history_visibility) => OK ({'history_visibility': 'shared'})
  • check_event_allowed(m.room.guest_access) => OK ({'guest_access': 'can_join'})
  • check_event_allowed(m.room.encryption) => KO ({'algorithm': 'm.megolm.v1.aes-sha2'})

Only with riot-desktop or similar clients proposing to setup encryption does this type of event arrives. If anybody knows how I could differentiate between an encryption request of just a direct conversation creation with no encryption, I'd be very interested.

@candyman2903
Copy link

Unfortunately, it no longer works in Matrix v1.67. I always get this error message:

TypeError: DomainRulesModule.__init__() got an unexpected keyword argument 'module_api'

@heurtematte
Copy link

I adapted code for last matrix version v1.73.0. Only 'on_create_room' function is implemented. here

@cmuller
Copy link
Author

cmuller commented Jan 4, 2023

Hello @candyman2903 @heurtematte ,

Thanks for reporting that this needs work to adapt to recent versions of Synapse server (matrix is only the name of the specs) but I think when we upgrade we will probably not be using this technique anymore.

This is because we are also using the matrix-corporal tool in order to manage which user should be in which room etc. (I wrote a little python script to specify this using yaml and LDAP groups to simplify the rights specifications). And from what I have seen, the latest versions of matrix-corporal allow to specify for each user that they are allowed to create rooms or not: see the forbidRoomCreation flag in https://github.com/devture/matrix-corporal/blob/master/docs/policy.md#flags .
So if this is working ok we wouldn't need to add a third party hook which is always painful to maintain (I also had to modify the ansible playbooks to deploy the third party code correctly).
it is my understanding that if users are allowed to create rooms, then matrix-corporal can also enforce that they must be encrypted (or that they must not be encrypted) so it seems to be quite powerful now.. I will try to test and report here.

Cheers,
Christophe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment