matrixsynapse: Use privileged decorator for actions

Tests:

- Functional tests work
- Initial setup works
- Setup after install works
  - Domain is properly set
  - Configure domains is properly shown in the app page
- Updating TURN configuration works
  - Configuration file is updated
- Enabling/disabling public registration works
  - Configuration file is updated
  - App page show current status
- FAIL: Daemon fails to start when public registration is enabled

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2022-09-03 08:14:26 -07:00 committed by James Valleroy
parent c1cf5699c2
commit bcdf374868
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
4 changed files with 90 additions and 153 deletions

View File

@ -1,7 +1,5 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
FreedomBox app to configure matrix-synapse server.
"""
"""FreedomBox app to configure matrix-synapse server."""
import logging
import os
@ -11,7 +9,6 @@ from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from ruamel.yaml.util import load_yaml_guess_indent
from plinth import actions
from plinth import app as app_module
from plinth import frontpage, menu
from plinth.daemon import Daemon
@ -23,7 +20,7 @@ from plinth.modules.letsencrypt.components import LetsEncrypt
from plinth.package import Packages, install
from plinth.utils import format_lazy, is_non_empty_file
from . import manifest
from . import manifest, privileged
_description = [
_('<a href="https://matrix.org/docs/guides/faq.html">Matrix</a> is an new '
@ -41,16 +38,6 @@ _description = [
logger = logging.getLogger(__name__)
CONF_DIR = "/etc/matrix-synapse/conf.d/"
ORIG_CONF_PATH = '/etc/matrix-synapse/homeserver.yaml'
SERVER_NAME_PATH = CONF_DIR + 'server_name.yaml'
STATIC_CONF_PATH = CONF_DIR + 'freedombox-static.yaml'
LISTENERS_CONF_PATH = CONF_DIR + 'freedombox-listeners.yaml'
REGISTRATION_CONF_PATH = CONF_DIR + 'freedombox-registration.yaml'
TURN_CONF_PATH = CONF_DIR + 'freedombox-turn.yaml'
OVERRIDDEN_TURN_CONF_PATH = CONF_DIR + 'turn.yaml'
class MatrixSynapseApp(app_module.App):
"""FreedomBox app for Matrix Synapse."""
@ -122,7 +109,7 @@ class MatrixSynapseApp(app_module.App):
if old_version and old_version < 6:
upgrade()
else:
actions.superuser_run('matrixsynapse', ['post-install'])
privileged.post_install()
if not old_version:
self.enable()
@ -144,14 +131,13 @@ class MatrixSynapseTurnConsumer(TurnConsumer):
def upgrade():
"""Upgrade matrix-synapse configuration to avoid conffile prompt."""
public_registration_status = get_public_registration_status()
actions.superuser_run('matrixsynapse', ['move-old-conf'])
public_registration_status = privileged.public_registration('status')
privileged.move_old_conf()
install(['matrix-synapse'], force_configuration='new', reinstall=True,
force_missing_configuration=True)
actions.superuser_run('matrixsynapse', ['post-install'])
privileged.post_install()
if public_registration_status:
actions.superuser_run('matrixsynapse',
['public-registration', 'enable'])
privileged.public_registration('enable')
def setup_domain(domain_name):
@ -159,13 +145,12 @@ def setup_domain(domain_name):
app = app_module.App.get('matrixsynapse')
app.get_component('letsencrypt-matrixsynapse').setup_certificates(
[domain_name])
actions.superuser_run('matrixsynapse',
['setup', '--domain-name', domain_name])
privileged.setup(domain_name)
def is_setup():
"""Return whether the Matrix Synapse server is setup."""
return os.path.exists(SERVER_NAME_PATH)
return os.path.exists(privileged.SERVER_NAME_PATH)
def get_domains():
@ -182,7 +167,7 @@ def get_configured_domain_name():
if not is_setup():
return None
with open(SERVER_NAME_PATH, encoding='utf-8') as config_file:
with open(privileged.SERVER_NAME_PATH, encoding='utf-8') as config_file:
config, _, _ = load_yaml_guess_indent(config_file)
return config['server_name']
@ -190,8 +175,8 @@ def get_configured_domain_name():
def get_turn_configuration() -> (List[str], str, bool):
"""Return TurnConfiguration if setup else empty."""
for file_path, managed in ((OVERRIDDEN_TURN_CONF_PATH, False),
(TURN_CONF_PATH, True)):
for file_path, managed in ((privileged.OVERRIDDEN_TURN_CONF_PATH, False),
(privileged.TURN_CONF_PATH, True)):
if is_non_empty_file(file_path):
with open(file_path, encoding='utf-8') as config_file:
config, _, _ = load_yaml_guess_indent(config_file)
@ -202,13 +187,6 @@ def get_turn_configuration() -> (List[str], str, bool):
return (TurnConfiguration(), True)
def get_public_registration_status() -> bool:
"""Return whether public registration is enabled."""
output = actions.superuser_run('matrixsynapse',
['public-registration', 'status'])
return output.strip() == 'enabled'
def get_certificate_status():
"""Return the status of certificate for the configured domain."""
app = app_module.App.get('matrixsynapse')
@ -226,7 +204,4 @@ def update_turn_configuration(config: TurnConfiguration, managed=True,
if not force and app.needs_setup():
return
params = ['configure-turn']
params += ['--managed'] if managed else []
actions.superuser_run('matrixsynapse', params,
input=config.to_json().encode())
privileged.configure_turn(managed, config.to_json())

View File

@ -1,24 +1,25 @@
#!/usr/bin/python3
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Configuration helper for Matrix-Synapse server.
"""
"""Configure Matrix-Synapse server."""
import argparse
import json
import os
import pathlib
import sys
from typing import Optional
import yaml
from plinth import action_utils
from plinth.modules.matrixsynapse import (LISTENERS_CONF_PATH, ORIG_CONF_PATH,
REGISTRATION_CONF_PATH,
STATIC_CONF_PATH)
from plinth.actions import privileged
TURN_CONF_PATH = '/etc/matrix-synapse/conf.d/freedombox-turn.yaml'
OVERRIDDEN_TURN_CONF_PATH = '/etc/matrix-synapse/conf.d/turn.yaml'
CONF_DIR = "/etc/matrix-synapse/conf.d/"
ORIG_CONF_PATH = '/etc/matrix-synapse/homeserver.yaml'
SERVER_NAME_PATH = CONF_DIR + 'server_name.yaml'
STATIC_CONF_PATH = CONF_DIR + 'freedombox-static.yaml'
LISTENERS_CONF_PATH = CONF_DIR + 'freedombox-listeners.yaml'
REGISTRATION_CONF_PATH = CONF_DIR + 'freedombox-registration.yaml'
TURN_CONF_PATH = CONF_DIR + 'freedombox-turn.yaml'
OVERRIDDEN_TURN_CONF_PATH = CONF_DIR + 'turn.yaml'
STATIC_CONFIG = {
'max_upload_size':
@ -40,38 +41,8 @@ STATIC_CONFIG = {
}
def parse_arguments():
"""Return parsed command line arguments as dictionary"""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
subparsers.add_parser('post-install', help='Perform post install steps')
help_pubreg = 'Enable/Disable/Status public user registration.'
pubreg = subparsers.add_parser('public-registration', help=help_pubreg)
pubreg.add_argument('command', choices=('enable', 'disable', 'status'),
help=help_pubreg)
setup = subparsers.add_parser('setup', help='Set domain name for Matrix')
setup.add_argument(
'--domain-name',
help='The domain name that will be used by Matrix Synapse')
subparsers.add_parser(
'move-old-conf',
help='Move old configuration file to backup before reinstall')
turn = subparsers.add_parser(
'configure-turn',
help='Configure a TURN server for use with Matrix Synapse')
turn.add_argument(
'--managed', required=False, default=False, action='store_true',
help='Whether configuration is provided by user or auto-managed by '
'FreedomBox')
subparsers.required = True
return parser.parse_args()
def subcommand_post_install(_):
@privileged
def post_install():
"""Perform post installation configuration."""
with open(STATIC_CONF_PATH, 'w', encoding='utf-8') as static_conf_file:
yaml.dump(STATIC_CONFIG, static_conf_file)
@ -91,15 +62,19 @@ def subcommand_post_install(_):
yaml.dump({'listeners': listeners}, listeners_conf_file)
def subcommand_setup(arguments):
@privileged
def setup(domain_name: str):
"""Configure the domain name for matrix-synapse package."""
domain_name = arguments.domain_name
action_utils.dpkg_reconfigure('matrix-synapse',
{'server-name': domain_name})
def subcommand_public_registration(argument):
@privileged
def public_registration(command: str) -> Optional[bool]:
"""Enable/Disable/Status public user registration."""
if command not in ('enable', 'disable', 'status'):
raise ValueError('Invalid command')
try:
with open(REGISTRATION_CONF_PATH, encoding='utf-8') as reg_conf_file:
config = yaml.load(reg_conf_file)
@ -112,25 +87,22 @@ def subcommand_public_registration(argument):
orig_config.get('enable_registration', False)
}
if argument.command == 'status':
if config['enable_registration']:
print('enabled')
return
else:
print('disabled')
return
elif argument.command == 'enable':
if command == 'status':
return bool(config['enable_registration'])
elif command == 'enable':
config['enable_registration'] = True
elif argument.command == 'disable':
elif command == 'disable':
config['enable_registration'] = False
with open(REGISTRATION_CONF_PATH, 'w', encoding='utf-8') as reg_conf_file:
yaml.dump(config, reg_conf_file)
action_utils.service_try_restart('matrix-synapse')
return None
def subcommand_move_old_conf(_arguments):
@privileged
def move_old_conf():
"""Move old configuration to backup so it can be restored by reinstall."""
conf_file = pathlib.Path(ORIG_CONF_PATH)
if conf_file.exists():
@ -138,8 +110,8 @@ def subcommand_move_old_conf(_arguments):
conf_file.replace(backup_file)
def _set_turn_config(conf_file):
turn_server_config = json.loads(''.join(sys.stdin))
def _set_turn_config(conf_file, conf):
turn_server_config = json.loads(conf)
if not turn_server_config['uris']:
# No valid configuration, remove the configuration file
@ -161,22 +133,12 @@ def _set_turn_config(conf_file):
yaml.dump(config, turn_config)
def subcommand_configure_turn(arguments):
@privileged
def configure_turn(managed: bool, conf: str):
"""Set parameters for the STUN/TURN server to use with Matrix Synapse."""
if arguments.managed:
_set_turn_config(TURN_CONF_PATH)
if managed:
_set_turn_config(TURN_CONF_PATH, conf)
else:
_set_turn_config(OVERRIDDEN_TURN_CONF_PATH)
_set_turn_config(OVERRIDDEN_TURN_CONF_PATH, conf)
action_utils.service_try_restart('matrix-synapse')
def main():
arguments = parse_arguments()
sub_command = arguments.subcommand.replace('-', '_')
sub_command_method = globals()['subcommand_' + sub_command]
sub_command_method(arguments)
if __name__ == '__main__':
main()

View File

@ -9,8 +9,10 @@ import pytest
from plinth.modules import matrixsynapse
from plinth.modules.coturn.components import TurnConfiguration
from plinth.modules.matrixsynapse import privileged
actions_name = 'matrixsynapse'
pytestmark = pytest.mark.usefixtures('mock_privileged')
privileged_modules_to_mock = ['plinth.modules.matrixsynapse.privileged']
@pytest.fixture(name='managed_turn_conf_file')
@ -28,29 +30,27 @@ def fixture_overridden_turn_conf_file(tmp_path):
@pytest.fixture(autouse=True)
def fixture_set_paths(actions_module, capsys, managed_turn_conf_file,
overridden_turn_conf_file):
def fixture_set_paths(managed_turn_conf_file, overridden_turn_conf_file):
"""Run actions with custom root path."""
actions_module.TURN_CONF_PATH = managed_turn_conf_file
actions_module.OVERRIDDEN_TURN_CONF_PATH = overridden_turn_conf_file
with patch('plinth.action_utils.service_try_restart'):
privileged.TURN_CONF_PATH = managed_turn_conf_file
privileged.OVERRIDDEN_TURN_CONF_PATH = overridden_turn_conf_file
with patch('plinth.privileged.service.try_restart'):
yield
@pytest.fixture(name='test_configuration', autouse=True)
def fixture_test_configuration(call_action, managed_turn_conf_file,
def fixture_test_configuration(managed_turn_conf_file,
overridden_turn_conf_file):
"""Use a separate Matrix Synapse configuration for tests.
Overrides TURN configuration files and patches actions.superuser_run
with the fixture call_action
Overrides TURN configuration files.
"""
with (patch('plinth.modules.matrixsynapse.TURN_CONF_PATH',
matrixsynapse = 'plinth.modules.matrixsynapse'
with (patch(f'{matrixsynapse}.privileged.TURN_CONF_PATH',
managed_turn_conf_file),
patch('plinth.modules.matrixsynapse.OVERRIDDEN_TURN_CONF_PATH',
patch(f'{matrixsynapse}.privileged.OVERRIDDEN_TURN_CONF_PATH',
overridden_turn_conf_file),
patch('plinth.modules.matrixsynapse.is_setup', return_value=True),
patch('plinth.actions.superuser_run', call_action),
patch(f'{matrixsynapse}.is_setup', return_value=True),
patch('plinth.app.App.get') as app_get):
app = Mock()
app_get.return_value = app
@ -71,14 +71,12 @@ updated_coturn_configuration = TurnConfiguration(
'aiP02OsbkC7BUeKvKzhAsTZ8MEwMd3yTwpr2uvbOxgWe51AGyOlj6WGuCyqj7iaO')
def _set_managed_configuration(monkeypatch, config=coturn_configuration):
monkeypatch.setattr('sys.stdin', config.to_json())
def _set_managed_configuration(config=coturn_configuration):
matrixsynapse.update_turn_configuration(config)
def _set_overridden_configuration(monkeypatch,
def _set_overridden_configuration(
config=overridden_configuration):
monkeypatch.setattr('sys.stdin', config.to_json())
matrixsynapse.update_turn_configuration(config, managed=False)
@ -90,30 +88,30 @@ def _assert_conf(expected_configuration, expected_managed):
assert managed == expected_managed
def test_managed_turn_server_configuration(monkeypatch):
def test_managed_turn_server_configuration():
"""Test setting and getting managed TURN server configuration."""
_set_managed_configuration(monkeypatch)
_set_managed_configuration()
_assert_conf(coturn_configuration, True)
def test_overridden_turn_server_configuration(monkeypatch):
def test_overridden_turn_server_configuration():
"""Test setting and getting overridden TURN sever configuration."""
_set_overridden_configuration(monkeypatch)
_set_overridden_configuration()
_assert_conf(overridden_configuration, False)
def test_revert_to_managed_turn_server_configuration(monkeypatch):
def test_revert_to_managed_turn_server_configuration():
"""Test setting and getting overridden TURN sever configuration."""
# Had to do all 3 operations because all fixtures were function-scoped
_set_managed_configuration(monkeypatch)
_set_overridden_configuration(monkeypatch)
_set_overridden_configuration(monkeypatch, TurnConfiguration())
_set_managed_configuration()
_set_overridden_configuration()
_set_overridden_configuration(TurnConfiguration())
_assert_conf(coturn_configuration, True)
def test_coturn_configuration_update_after_admin_override(monkeypatch):
def test_coturn_configuration_update_after_admin_override():
"""Test that overridden conf prevails even if managed conf is updated."""
_set_managed_configuration(monkeypatch)
_set_overridden_configuration(monkeypatch)
_set_managed_configuration(monkeypatch, updated_coturn_configuration)
_set_managed_configuration()
_set_overridden_configuration()
_set_managed_configuration(updated_coturn_configuration)
_assert_conf(overridden_configuration, False)

View File

@ -1,7 +1,5 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Views for the Matrix Synapse module.
"""
"""Views for the Matrix Synapse module."""
from django.contrib import messages
from django.shortcuts import redirect
@ -9,19 +7,19 @@ from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic import FormView
from plinth import actions
from plinth import app as app_module
from plinth.forms import DomainSelectionForm
from plinth.modules import matrixsynapse, names
from plinth.modules.coturn.components import TurnConfiguration
from plinth.views import AppView
from . import get_public_registration_status, get_turn_configuration
from . import get_turn_configuration, privileged
from .forms import MatrixSynapseForm
class SetupView(FormView):
"""Show matrix-synapse setup page."""
template_name = 'matrix-synapse-pre-setup.html'
form_class = DomainSelectionForm
success_url = reverse_lazy('matrixsynapse:index')
@ -46,6 +44,7 @@ class SetupView(FormView):
class MatrixSynapseAppView(AppView):
"""Show matrix-synapse service page."""
app_id = 'matrixsynapse'
template_name = 'matrix-synapse.html'
form_class = MatrixSynapseForm
@ -69,21 +68,24 @@ class MatrixSynapseAppView(AppView):
initial = super().get_initial()
config, managed = get_turn_configuration()
initial.update({
'enable_public_registration': get_public_registration_status(),
'enable_managed_turn': managed,
'turn_uris': '\n'.join(config.uris),
'shared_secret': config.shared_secret
'enable_public_registration':
privileged.public_registration('status'),
'enable_managed_turn':
managed,
'turn_uris':
'\n'.join(config.uris),
'shared_secret':
config.shared_secret
})
return initial
@staticmethod
def _handle_public_registrations(new_config):
if new_config['enable_public_registration']:
actions.superuser_run('matrixsynapse',
['public-registration', 'enable'])
privileged.public_registration('enable')
else:
actions.superuser_run('matrixsynapse',
['public-registration', 'disable'])
privileged.public_registration('disable')
@staticmethod
def _handle_turn_configuration(old_config, new_config):