ejabberd: Add multi-select form for domains

Choices includes all of the available domain names in the system, as
well as any domains that are in ejabberd configuration.

Tests:

- Disable a domain. It is removed from ejabberd config.

- Enable a domain. It is added to ejabberd config.

- Enable all name services. Run ejabberd functional tests and observe
  that they pass.

Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
James Valleroy 2022-06-08 10:57:13 -04:00 committed by Sunil Mohan Adapa
parent 7d4c3dbb67
commit 1a39212313
No known key found for this signature in database
GPG Key ID: 43EA1CFF0AA7C5F2
5 changed files with 147 additions and 23 deletions

View File

@ -70,11 +70,19 @@ def parse_arguments():
hostname_change.add_argument('--old-hostname', help='Previous hostname')
hostname_change.add_argument('--new-hostname', help='New hostname')
# Add a domain name to ejabberd
# Manage domain names
subparsers.add_parser('get-domains',
help='Get all configured domains in JSON format')
add_domain = subparsers.add_parser('add-domain',
help='Add a domain name to ejabberd')
add_domain.add_argument('--domainname', help='New domain name')
set_domains = subparsers.add_parser('set-domains',
help='Set list of ejabberd domains')
set_domains.add_argument('--domains', nargs='+',
help='One or more domain names')
# Configure STUN/TURN server for use with ejabberd
turn = subparsers.add_parser(
'configure-turn', help='Configure a TURN server for use with ejabberd')
@ -236,8 +244,23 @@ def subcommand_change_hostname(arguments):
EJABBERD_BACKUP_NEW)
def subcommand_get_domains(_):
"""Get all configured domains."""
if not shutil.which('ejabberdctl'):
print('ejabberdctl not found. Is ejabberd installed?')
return
with open(EJABBERD_CONFIG, 'r') as file_handle:
conf = yaml.load(file_handle)
print(json.dumps(conf['hosts']))
def subcommand_add_domain(arguments):
"""Update ejabberd with new domainname"""
"""Update ejabberd with new domainname.
Restarting ejabberd is handled by letsencrypt-ejabberd component.
"""
if not shutil.which('ejabberdctl'):
print('ejabberdctl not found. Is ejabberd installed?')
return
@ -255,6 +278,26 @@ def subcommand_add_domain(arguments):
with open(EJABBERD_CONFIG, 'w') as file_handle:
yaml.dump(conf, file_handle)
# Restarting ejabberd is handled by letsencrypt-ejabberd component.
def subcommand_set_domains(arguments):
"""Set list of ejabberd domains.
Restarting ejabberd is handled by letsencrypt-ejabberd component.
"""
if not shutil.which('ejabberdctl'):
print('ejabberdctl not found. Is ejabberd installed?')
return
with open(EJABBERD_CONFIG, 'r') as file_handle:
conf = yaml.load(file_handle)
conf['hosts'] = arguments.domains
with open(EJABBERD_CONFIG, 'w') as file_handle:
yaml.dump(conf, file_handle)
def subcommand_mam(argument):
"""Enable, disable, or get status of Message Archive Management (MAM)."""

View File

@ -158,22 +158,6 @@ def setup(helper, old_version=None):
update_turn_configuration(configuration, force=True)
def get_domains():
"""Return the list of domains that ejabberd is interested in.
XXX: Retrieve the list from ejabberd configuration.
"""
if app.needs_setup():
return []
domain_name = config.get_domainname()
if domain_name:
return [domain_name]
return []
def on_pre_hostname_change(sender, old_hostname, new_hostname, **kwargs):
"""
Backup ejabberd database before hostname is changed.
@ -202,19 +186,39 @@ def on_post_hostname_change(sender, old_hostname, new_hostname, **kwargs):
], run_in_background=True)
def get_domains():
"""Return the list of domains configured for ejabberd.
"""
if app.needs_setup():
return []
output = actions.superuser_run('ejabberd', ['get-domains'])
return json.loads(output)
def on_domain_added(sender, domain_type, name='', description='',
services=None, **kwargs):
"""Update ejabberd config after domain name change."""
if not name or app.needs_setup():
return
conf = actions.superuser_run('ejabberd', ['get-configuration'])
conf = json.loads(conf)
if name not in conf['domains']:
domains = get_domains()
if name not in domains:
actions.superuser_run('ejabberd', ['add-domain', '--domainname', name])
app.get_component('letsencrypt-ejabberd').setup_certificates()
def set_domains(domains):
"""Configure ejabberd to have this list of domains."""
if not domains or app.needs_setup():
return
commands = ['set-domains', '--domains']
commands.extend(domains)
actions.superuser_run('ejabberd', commands)
app.get_component('letsencrypt-ejabberd').setup_certificates()
def update_turn_configuration(config: TurnConfiguration, managed=True,
force=False):
"""Update ejabberd's STUN/TURN server configuration."""

View File

@ -8,12 +8,21 @@ from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from plinth import cfg
from plinth.modules import ejabberd
from plinth.modules.coturn.forms import turn_uris_validator
from plinth.utils import format_lazy
class EjabberdForm(forms.Form):
"""Ejabberd configuration form."""
domain_names = forms.MultipleChoiceField(
label=_('Domain names'), widget=forms.CheckboxSelectMultiple,
help_text=_(
'Domains to be used by ejabberd. "localhost" is always included, '
'so it is not shown here. Note that user accounts are unique for '
'each domain, and migrating users to a new domain name is not yet '
'implemented.'), choices=[])
MAM_enabled = forms.BooleanField(
label=_('Enable Message Archive Management'), required=False,
help_text=format_lazy(
@ -43,6 +52,18 @@ class EjabberdForm(forms.Form):
help_text=_('Shared secret used to compute passwords for the '
'TURN server.'))
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Start with any existing domains from ejabberd configuration.
domains = set(ejabberd.get_domains())
# Add other domains that can be configured.
from plinth.modules.names.components import DomainName
domains |= DomainName.list_names()
self.fields['domain_names'].choices = zip(domains, domains)
def clean_turn_uris(self):
"""Normalize newlines in URIs."""
data = self.cleaned_data['turn_uris']

View File

@ -8,8 +8,7 @@ from plinth.tests import functional
pytestmark = [pytest.mark.apps, pytest.mark.ejabberd]
# TODO Check service
# TODO Check domain name display
# TODO Check domain name displayed in description
class TestEjabberdApp(functional.BaseAppTests):
@ -17,6 +16,29 @@ class TestEjabberdApp(functional.BaseAppTests):
has_service = True
has_web = False
@pytest.fixture(scope='module', autouse=True)
def fixture_background(self, session_browser):
"""Login, install, and enable the app."""
functional.login(session_browser)
functional.install(session_browser, self.app_name)
functional.app_enable(session_browser, self.app_name)
yield
functional.login(session_browser)
functional.app_disable(session_browser, self.app_name)
def test_add_remove_domain(self, session_browser):
"""Test adding and removing a domain."""
functional.app_enable(session_browser, 'ejabberd')
_enable_domain(session_browser, 'freedombox.local')
_disable_domain(session_browser, 'freedombox.local')
assert not _is_domain_enabled(session_browser, 'freedombox.local')
assert functional.service_is_running(session_browser, 'ejabberd')
_enable_domain(session_browser, 'freedombox.local')
assert _is_domain_enabled(session_browser, 'freedombox.local')
assert functional.service_is_running(session_browser, 'ejabberd')
def test_message_archive_management(self, session_browser):
"""Test enabling message archive management."""
functional.app_enable(session_browser, 'ejabberd')
@ -39,6 +61,29 @@ class TestEjabberdApp(functional.BaseAppTests):
_jsxc_assert_has_contact(session_browser)
def _enable_domain(browser, domain):
"""Add domain name to Ejabberd configuration."""
functional.nav_to_module(browser, 'ejabberd')
checkbox = browser.find_by_value(domain).first
checkbox.check()
functional.submit(browser, form_class='form-configuration')
def _disable_domain(browser, domain):
"""Remove domain name from Ejabberd configuration."""
functional.nav_to_module(browser, 'ejabberd')
checkbox = browser.find_by_value(domain).first
checkbox.uncheck()
functional.submit(browser, form_class='form-configuration')
def _is_domain_enabled(browser, domain):
"""Return whether the domain name is enabled."""
functional.nav_to_module(browser, 'ejabberd')
checkbox = browser.find_by_value(domain).first
return checkbox.checked
def _enable_message_archive_management(browser):
"""Enable Message Archive Management in Ejabberd."""
functional.nav_to_module(browser, 'ejabberd')

View File

@ -24,6 +24,7 @@ class EjabberdAppView(AppView):
"""Initial data to fill in the form."""
config, managed = ejabberd.get_turn_configuration()
return super().get_initial() | {
'domain_names': ejabberd.get_domains(),
'MAM_enabled': self.is_MAM_enabled(),
'enable_managed_turn': managed,
'turn_uris': '\n'.join(config.uris),
@ -37,6 +38,11 @@ class EjabberdAppView(AppView):
context['domainname'] = domains[0] if domains else None
return context
@staticmethod
def _handle_domain_names_configuration(new_config):
"""Update list of domain names in configuration."""
ejabberd.set_domains(new_config['domain_names'])
@staticmethod
def _handle_turn_configuration(old_config, new_config):
if not new_config['enable_managed_turn']:
@ -72,6 +78,11 @@ class EjabberdAppView(AppView):
return old_config[prop] != new_config[prop]
is_changed = False
if changed('domain_names'):
self._handle_domain_names_configuration(new_config)
is_changed = True
if changed('MAM_enabled'):
self._handle_MAM_configuration(old_config, new_config)
is_changed = True