From ebd3af340c6a78d30e950ab7a1bfb48944842cbf Mon Sep 17 00:00:00 2001 From: Matthias Dellweg <2500@gmx.de> Date: Sat, 21 Sep 2019 19:52:11 +0200 Subject: [PATCH] quassel: Add let's encrypt component for certficiates Signed-off-by: Matthias Dellweg <2500@gmx.de> [sunil@medhas.org Implement set domain as superuser action to make it succeed] [sunil@medhas.org Minor cosmetic changes] Signed-off-by: Sunil Mohan Adapa Reviewed-by: Sunil Mohan Adapa --- actions/quassel | 55 +++++++++++++++++++++++++ plinth/modules/quassel/__init__.py | 66 ++++++++++++++++++++++++------ plinth/modules/quassel/forms.py | 42 +++++++++++++++++++ plinth/modules/quassel/urls.py | 2 +- plinth/modules/quassel/views.py | 48 ++++++++++++++++++++++ 5 files changed, 200 insertions(+), 13 deletions(-) create mode 100755 actions/quassel create mode 100644 plinth/modules/quassel/forms.py create mode 100644 plinth/modules/quassel/views.py diff --git a/actions/quassel b/actions/quassel new file mode 100755 index 000000000..5beacb7d2 --- /dev/null +++ b/actions/quassel @@ -0,0 +1,55 @@ +#!/usr/bin/python3 +# +# This file is part of FreedomBox. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +""" +Configuration helper for Quassel. +""" + +import argparse +import pathlib + + +def parse_arguments(): + """Return parsed command line arguments as dictionary.""" + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(dest='subcommand', help='Sub command') + + subparser = subparsers.add_parser('set-domain', + help='Setup Cockpit configuration') + subparser.add_argument('domain_name', help='Domain name to be allowed') + + subparsers.required = True + return parser.parse_args() + + +def subcommand_set_domain(arguments): + """Write a file containing domain name.""" + domain_file = pathlib.Path('/var/lib/quassel/domain-freedombox') + domain_file.write_text(arguments.domain_name) + + +def main(): + """Parse arguments and perform all duties.""" + arguments = parse_arguments() + + subcommand = arguments.subcommand.replace('-', '_') + subcommand_method = globals()['subcommand_' + subcommand] + subcommand_method(arguments) + + +if __name__ == '__main__': + main() diff --git a/plinth/modules/quassel/__init__.py b/plinth/modules/quassel/__init__.py index dadde0996..dcdbdcf4a 100644 --- a/plinth/modules/quassel/__init__.py +++ b/plinth/modules/quassel/__init__.py @@ -18,16 +18,19 @@ FreedomBox app for Quassel. """ +import pathlib + from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ -from plinth import action_utils +from plinth import action_utils, actions from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.daemon import Daemon +from plinth.modules import names from plinth.modules.firewall.components import Firewall +from plinth.modules.letsencrypt.components import LetsEncrypt from plinth.utils import format_lazy -from plinth.views import AppView from .manifest import backup, clients # noqa, pylint: disable=unused-import @@ -37,6 +40,8 @@ managed_services = ['quasselcore'] managed_packages = ['quassel-core'] +managed_paths = [pathlib.Path('/var/lib/quassel/')] + name = _('Quassel') short_description = _('IRC Client') @@ -92,6 +97,15 @@ class QuasselApp(app_module.App): is_external=True) self.add(firewall) + letsencrypt = LetsEncrypt( + 'letsencrypt-quassel', domains=get_domains, + daemons=managed_services, should_copy_certificates=True, + private_key_path='/var/lib/quassel/quasselCert.pem', + certificate_path='/var/lib/quassel/quasselCert.pem', + user_owner='quasselcore', group_owner='quassel', + managing_app='quassel') + self.add(letsencrypt) + daemon = Daemon('daemon-quassel', managed_services[0]) self.add(daemon) @@ -106,20 +120,11 @@ def init(): app.set_enabled(True) -class QuasselAppView(AppView): - app_id = 'quassel' - diagnostics_module_name = 'quassel' - name = name - description = description - clients = clients - manual_page = manual_page - port_forwarding_info = port_forwarding_info - - def setup(helper, old_version=None): """Install and configure the module.""" helper.install(managed_packages) helper.call('post', app.enable) + app.get_component('letsencrypt-quassel').setup_certificates() def diagnose(): @@ -130,3 +135,40 @@ def diagnose(): results.append(action_utils.diagnose_port_listening(4242, 'tcp6')) return results + + +def get_available_domains(): + """Return an iterator with all domains able to have a certificate.""" + return (domain.name for domain in names.components.DomainName.list() + if domain.domain_type.can_have_certificate) + + +def set_domain(domain): + """Set the TLS domain by writing a file to data directory.""" + if domain: + actions.superuser_run('quassel', ['set-domain', domain]) + + +def get_domain(): + """Read TLS domain from config file select first available if none.""" + domain = None + try: + with open('/var/lib/quassel/domain-freedombox') as file_handle: + domain = file_handle.read().strip() + except FileNotFoundError: + pass + + if not domain: + domain = next(get_available_domains(), None) + set_domain(domain) + + return domain + + +def get_domains(): + """Return a list with the configured domain for quassel.""" + domain = get_domain() + if domain: + return [domain] + + return [] diff --git a/plinth/modules/quassel/forms.py b/plinth/modules/quassel/forms.py new file mode 100644 index 000000000..5455b7fc1 --- /dev/null +++ b/plinth/modules/quassel/forms.py @@ -0,0 +1,42 @@ +# +# This file is part of FreedomBox. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +""" +Forms for Quassel app. +""" + +from django import forms +from django.utils.translation import ugettext_lazy as _ + +from plinth.forms import AppForm +from plinth.modules import quassel + + +def get_domain_choices(): + """Double domain entries for inclusion in the choice field.""" + return ((domain, domain) for domain in quassel.get_available_domains()) + + +class QuasselForm(AppForm): + """Form to select a TLS domain for Quassel.""" + + domain = forms.ChoiceField( + choices=get_domain_choices, + label=_('TLS domain'), + help_text=_( + 'Select a domain to use TLS with. If the list is empty, please ' + 'configure at least one domain with certificates.'), + ) diff --git a/plinth/modules/quassel/urls.py b/plinth/modules/quassel/urls.py index a2dc6f9c0..fb64091d5 100644 --- a/plinth/modules/quassel/urls.py +++ b/plinth/modules/quassel/urls.py @@ -20,7 +20,7 @@ URLs for the quassel module. from django.conf.urls import url -from plinth.modules.quassel import QuasselAppView +from .views import QuasselAppView urlpatterns = [ url(r'^apps/quassel/$', QuasselAppView.as_view(), name='index'), diff --git a/plinth/modules/quassel/views.py b/plinth/modules/quassel/views.py new file mode 100644 index 000000000..62e018079 --- /dev/null +++ b/plinth/modules/quassel/views.py @@ -0,0 +1,48 @@ +# +# This file is part of FreedomBox. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +from plinth.modules import quassel +from plinth.views import AppView + +from .forms import QuasselForm + + +class QuasselAppView(AppView): + app_id = 'quassel' + diagnostics_module_name = 'quassel' + name = quassel.name + description = quassel.description + clients = quassel.clients + manual_page = quassel.manual_page + port_forwarding_info = quassel.port_forwarding_info + form_class = QuasselForm + + def get_initial(self): + """Return the values to fill in the form.""" + initial = super().get_initial() + initial['domain'] = quassel.get_domain() + return initial + + def form_valid(self, form): + """Change the access control of Radicale service.""" + data = form.cleaned_data + if quassel.get_domain() != data['domain']: + quassel.set_domain(data['domain']) + quassel.app.get_component( + 'letsencrypt-quassel').setup_certificates() + + return super().form_valid(form)