From 9d85084d8debe2c7a5ac21e8feb2d29eaca3e29b Mon Sep 17 00:00:00 2001 From: James Valleroy Date: Sat, 9 Jan 2016 16:29:40 -0500 Subject: [PATCH] radicale: New module for a CalDAV/CardDAV server --- actions/radicale | 106 ++++++++++++++++++ .../conf-available/radicale-plinth.conf | 18 +++ data/etc/plinth/modules-enabled/radicale | 1 + plinth/modules/radicale/__init__.py | 62 ++++++++++ plinth/modules/radicale/forms.py | 30 +++++ .../modules/radicale/templates/radicale.html | 65 +++++++++++ plinth/modules/radicale/tests/__init__.py | 0 plinth/modules/radicale/urls.py | 29 +++++ plinth/modules/radicale/views.py | 79 +++++++++++++ 9 files changed, 390 insertions(+) create mode 100755 actions/radicale create mode 100644 data/etc/apache2/conf-available/radicale-plinth.conf create mode 100644 data/etc/plinth/modules-enabled/radicale create mode 100644 plinth/modules/radicale/__init__.py create mode 100644 plinth/modules/radicale/forms.py create mode 100644 plinth/modules/radicale/templates/radicale.html create mode 100644 plinth/modules/radicale/tests/__init__.py create mode 100644 plinth/modules/radicale/urls.py create mode 100644 plinth/modules/radicale/views.py diff --git a/actions/radicale b/actions/radicale new file mode 100755 index 000000000..9f33dd72b --- /dev/null +++ b/actions/radicale @@ -0,0 +1,106 @@ +#!/usr/bin/python3 +# +# This file is part of Plinth. +# +# 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 Radicale. +""" + +import argparse +import augeas +import subprocess + +from plinth import action_utils + +CONFIG_FILE = '/etc/radicale/config' +DEFAULT_FILE = '/etc/default/radicale' + + +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('setup', help='Setup Radicale configuration') + subparsers.add_parser('enable', help='Enable Radicale service') + subparsers.add_parser('disable', help='Disable Radicale service') + + return parser.parse_args() + + +def subcommand_setup(_): + """Setup Radicale configuration.""" + aug = load_augeas() + + aug.set('/files' + DEFAULT_FILE + '/ENABLE_RADICALE', 'yes') + + aug.set('/files' + CONFIG_FILE + '/server/hosts', + '0.0.0.0:5232, [::]:5232') + aug.set('/files' + CONFIG_FILE + '/server/base_prefix', '/radicale/') + aug.set('/files' + CONFIG_FILE + '/well-known/caldav', + '/radicale/%(user)s/caldav/') + aug.set('/files' + CONFIG_FILE + '/well-known/carddav', + '/radicale/%(user)s/carddav/') + aug.set('/files' + CONFIG_FILE + '/auth/type', 'remote_user') + aug.set('/files' + CONFIG_FILE + '/rights/type', 'owner_only') + + aug.save() + + action_utils.service_restart('radicale') + action_utils.webserver_enable('radicale-plinth') + + +def subcommand_enable(_): + """Start service.""" + action_utils.service_enable('radicale') + action_utils.webserver_enable('radicale-plinth') + + +def subcommand_disable(_): + """Stop service.""" + action_utils.webserver_disable('radicale-plinth') + action_utils.service_disable('radicale') + + +def load_augeas(): + """Initialize Augeas.""" + aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD + + augeas.Augeas.NO_MODL_AUTOLOAD) + + # shell-script config file lens + aug.set('/augeas/load/Shellvars/lens', 'Shellvars.lns') + aug.set('/augeas/load/Shellvars/incl[last() + 1]', DEFAULT_FILE) + + # INI file lens + aug.set('/augeas/load/Puppet/lens', 'Puppet.lns') + aug.set('/augeas/load/Puppet/incl[last() + 1]', CONFIG_FILE) + + aug.load() + return aug + + +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/data/etc/apache2/conf-available/radicale-plinth.conf b/data/etc/apache2/conf-available/radicale-plinth.conf new file mode 100644 index 000000000..efa76c8de --- /dev/null +++ b/data/etc/apache2/conf-available/radicale-plinth.conf @@ -0,0 +1,18 @@ +## +## On all sites, provide Radicale on a path: /radicale +## Allow all valid users. +## +Redirect 301 /.well-known/carddav /radicale/.well-known/carddav +Redirect 301 /.well-known/caldav /radicale/.well-known/caldav + + + ProxyPass http://localhost:5232 + + AuthType basic + AuthName "FreedomBox Login" + AuthBasicProvider ldap + AuthLDAPUrl "ldap:///ou=users,dc=thisbox?uid" + AuthLDAPGroupAttribute memberUid + AuthLDAPGroupAttributeIsDN off + Require valid-user + diff --git a/data/etc/plinth/modules-enabled/radicale b/data/etc/plinth/modules-enabled/radicale new file mode 100644 index 000000000..785705bd1 --- /dev/null +++ b/data/etc/plinth/modules-enabled/radicale @@ -0,0 +1 @@ +plinth.modules.radicale diff --git a/plinth/modules/radicale/__init__.py b/plinth/modules/radicale/__init__.py new file mode 100644 index 000000000..0b221ef9d --- /dev/null +++ b/plinth/modules/radicale/__init__.py @@ -0,0 +1,62 @@ +# +# This file is part of Plinth. +# +# 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 . +# + +""" +Plinth module for radicale. +""" + +from django.utils.translation import ugettext_lazy as _ + +from plinth import action_utils +from plinth import cfg +from plinth import service as service_module + +depends = ['plinth.modules.apps'] + +service = None + + +def init(): + """Initialize the radicale module.""" + menu = cfg.main_menu.get('apps:index') + menu.add_urlname(_('Calendar and Addressbook (Radicale)'), + 'glyphicon-calendar', 'radicale:index', 375) + + global service + service = service_module.Service( + 'radicale-plinth', _('Radicale CalDAV and CardDAV Server'), + is_external=True, enabled=is_enabled()) + + +def is_enabled(): + """Return whether the service is enabled.""" + return action_utils.service_is_enabled('radicale') + + +def is_running(): + """Return whether the service is running.""" + return action_utils.service_is_running('radicale') + + +def diagnose(): + """Run diagnostics and return the results.""" + results = [] + + results.extend(action_utils.diagnose_url_on_all( + 'https://{host}/radicale', extra_options=['--no-check-certificate'])) + + return results diff --git a/plinth/modules/radicale/forms.py b/plinth/modules/radicale/forms.py new file mode 100644 index 000000000..7ecbeae83 --- /dev/null +++ b/plinth/modules/radicale/forms.py @@ -0,0 +1,30 @@ +# +# This file is part of Plinth. +# +# 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 radicale module. +""" + +from django import forms +from django.utils.translation import ugettext_lazy as _ + + +class RadicaleForm(forms.Form): + """Radicale configuration form.""" + enabled = forms.BooleanField( + label=_('Enable Radicale service'), + required=False) diff --git a/plinth/modules/radicale/templates/radicale.html b/plinth/modules/radicale/templates/radicale.html new file mode 100644 index 000000000..07f12fc74 --- /dev/null +++ b/plinth/modules/radicale/templates/radicale.html @@ -0,0 +1,65 @@ +{% extends "base.html" %} +{% comment %} +# +# This file is part of Plinth. +# +# 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 . +# +{% endcomment %} + +{% load bootstrap %} +{% load i18n %} + +{% block content %} + +

{% trans "Calendar and Addressbook (Radicale)" %}

+ +

+ {% blocktrans trimmed %} + Radicale is a CalDAV and CardDAV server. It allows synchronization and + sharing of scheduling and contact data. To use Radicale, a + + supported client application is needed. Radicale can be accessed by + any user with a {{ box_name }} login. + {% endblocktrans %} +

+ +

{% trans "Status" %}

+ +

+ {% if status.is_running %} + + {% trans "Radicale service is running" %} + {% else %} + + {% trans "Radicale service is not running" %} + {% endif %} +

+ + {% include "diagnostics_button.html" with module="radicale" %} + + +

{% trans "Configuration" %}

+ +
+ {% csrf_token %} + + {{ form|bootstrap }} + + +
+ +{% endblock %} diff --git a/plinth/modules/radicale/tests/__init__.py b/plinth/modules/radicale/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plinth/modules/radicale/urls.py b/plinth/modules/radicale/urls.py new file mode 100644 index 000000000..ae7c20084 --- /dev/null +++ b/plinth/modules/radicale/urls.py @@ -0,0 +1,29 @@ +# +# This file is part of Plinth. +# +# 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 . +# + +""" +URLs for the radicale module. +""" + +from django.conf.urls import url + +from . import views + + +urlpatterns = [ + url(r'^apps/radicale/$', views.index, name='index'), +] diff --git a/plinth/modules/radicale/views.py b/plinth/modules/radicale/views.py new file mode 100644 index 000000000..3bfb8fdd7 --- /dev/null +++ b/plinth/modules/radicale/views.py @@ -0,0 +1,79 @@ +# +# This file is part of Plinth. +# +# 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 . +# + +""" +Views for radicale module. +""" + +from django.contrib import messages +from django.template.response import TemplateResponse +from django.utils.translation import ugettext as _ + +from .forms import RadicaleForm +from plinth import actions +from plinth import package +from plinth.modules import radicale + + +def on_install(): + """Notify that the service is now enabled.""" + actions.superuser_run('radicale', ['setup']) + radicale.service.notify_enabled(None, True) + + +@package.required(['radicale'], on_install=on_install) +def index(request): + """Serve configuration page.""" + status = get_status() + + form = None + + if request.method == 'POST': + form = RadicaleForm(request.POST, prefix='radicale') + if form.is_valid(): + _apply_changes(request, status, form.cleaned_data) + status = get_status() + form = RadicaleForm(initial=status, prefix='radicale') + else: + form = RadicaleForm(initial=status, prefix='radicale') + + return TemplateResponse(request, 'radicale.html', + {'title': _('Calendar and Addressbook (Radicale)'), + 'status': status, + 'form': form}) + + +def get_status(): + """Get the current service status.""" + return {'enabled': radicale.is_enabled(), + 'is_running': radicale.is_running()} + + +def _apply_changes(request, old_status, new_status): + """Apply the changes.""" + modified = False + + if old_status['enabled'] != new_status['enabled']: + sub_command = 'enable' if new_status['enabled'] else 'disable' + actions.superuser_run('radicale', [sub_command]) + radicale.service.notify_enabled(None, new_status['enabled']) + modified = True + + if modified: + messages.success(request, _('Configuration updated')) + else: + messages.info(request, _('Setting unchanged'))