From ff9d061e98df79c77b25e2f0a7c563132ea1d36b Mon Sep 17 00:00:00 2001 From: Joseph Nuthalapati Date: Sun, 28 Jan 2018 18:26:12 +0530 Subject: [PATCH] Add file-sharing application Coquelicot to FreedomBox - Add settings in Service View - Fixes for maximum file setting - Don't allow negative values for max. file size in UI - Minor text changes to django messages - Minor correction to maximum file size calculation - Rename apache conf file to coquelicot-freedombox.conf - Remove all hacks to adjust file size. - Fix permissions issues for settings file - Show status block in UI - try-restart on settings change instead of restart Signed-off-by: Joseph Nuthalapati Reviewed-by: James Valleroy --- actions/coquelicot | 137 ++++++++++++++++++ .../conf-available/coquelicot-freedombox.conf | 5 + data/etc/plinth/modules-enabled/coquelicot | 1 + plinth/action_utils.py | 35 ++--- plinth/modules/coquelicot/__init__.py | 133 +++++++++++++++++ plinth/modules/coquelicot/forms.py | 37 +++++ plinth/modules/coquelicot/manifest.py | 28 ++++ plinth/modules/coquelicot/urls.py | 27 ++++ plinth/modules/coquelicot/views.py | 72 +++++++++ static/themes/default/icons/coquelicot.png | Bin 0 -> 27771 bytes 10 files changed, 455 insertions(+), 20 deletions(-) create mode 100755 actions/coquelicot create mode 100644 data/etc/apache2/conf-available/coquelicot-freedombox.conf create mode 100644 data/etc/plinth/modules-enabled/coquelicot create mode 100644 plinth/modules/coquelicot/__init__.py create mode 100644 plinth/modules/coquelicot/forms.py create mode 100644 plinth/modules/coquelicot/manifest.py create mode 100644 plinth/modules/coquelicot/urls.py create mode 100644 plinth/modules/coquelicot/views.py create mode 100644 static/themes/default/icons/coquelicot.png diff --git a/actions/coquelicot b/actions/coquelicot new file mode 100755 index 000000000..340fbae50 --- /dev/null +++ b/actions/coquelicot @@ -0,0 +1,137 @@ +#!/usr/bin/python3 +# -*- mode: python -*- +# +# 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 coquelicot. +""" + +import argparse +import hashlib +import os +import sys + +import yaml +from plinth import action_utils + +SETTINGS_FILE = '/etc/coquelicot/settings.yml' + + +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='Post-installation operations for coquelicot') + subparsers.add_parser('enable', help='Enable coquelicot') + subparsers.add_parser('disable', help='Disable coquelicot') + + subparsers.add_parser( + 'set-upload-password', + help='Set a new global, pre-shared password for uploading files') + + max_file_size = subparsers.add_parser( + 'set-max-file-size', + help='Change the maximum size of the files that can be uploaded to ' + 'Coquelicot') + max_file_size.add_argument('size', type=int, help='upload file size in MB') + + subparsers.add_parser( + 'get-max-file-size', + help='Print the maximum size of the files that can be uploaded to ' + 'Coquelicot') + + subparsers.required = True + return parser.parse_args() + + +def subcommand_setup(_): + """Perform post-installation operations for coquelicot.""" + settings = read_settings() + settings['path'] = "/coquelicot" + settings['max_file_size'] = mebibytes(1024) + write_settings(settings) + action_utils.service_restart('coquelicot') + + +def subcommand_enable(_): + """Enable web configuration and reload.""" + action_utils.service_enable('coquelicot') + action_utils.webserver_enable('coquelicot-freedombox') + + +def subcommand_disable(_): + """Disable web configuration and reload.""" + action_utils.webserver_disable('coquelicot-freedombox') + action_utils.service_disable('coquelicot') + + +def subcommand_set_upload_password(arguments): + """Set a new upload password for Coquelicot.""" + upload_password = ''.join(sys.stdin) + settings = read_settings() + hashed_pw = hashlib.sha1(upload_password.encode()).hexdigest() + settings['authentication_method']['upload_password'] = hashed_pw + write_settings(settings) + action_utils.service_try_restart('coquelicot') + + +def subcommand_set_max_file_size(arguments): + """Set a new maximum file size for Coquelicot.""" + size_in_bytes = mebibytes(arguments.size) + settings = read_settings() + settings['max_file_size'] = size_in_bytes + write_settings(settings) + action_utils.service_try_restart('coquelicot') + + +def subcommand_get_max_file_size(_): + """Print the maximum file size to stdout.""" + if os.path.exists(SETTINGS_FILE): + settings = read_settings() + print(int(settings['max_file_size'] / (1024 * 1024))) + else: + print(-1) + + +def read_settings(): + with open(SETTINGS_FILE, 'rb') as settings_file: + return yaml.load(settings_file) + + +def write_settings(settings): + with open(SETTINGS_FILE, 'w') as settings_file: + yaml.dump(settings, settings_file) + + +def main(): + """Parse arguments and perform all duties.""" + arguments = parse_arguments() + + subcommand = arguments.subcommand.replace('-', '_') + subcommand_method = globals()['subcommand_' + subcommand] + subcommand_method(arguments) + + +def mebibytes(size): + """Return the given size of mebibytes in bytes.""" + return size * 1024 * 1024 + + +if __name__ == '__main__': + main() diff --git a/data/etc/apache2/conf-available/coquelicot-freedombox.conf b/data/etc/apache2/conf-available/coquelicot-freedombox.conf new file mode 100644 index 000000000..0fe851e83 --- /dev/null +++ b/data/etc/apache2/conf-available/coquelicot-freedombox.conf @@ -0,0 +1,5 @@ + + ProxyPass http://127.0.0.1:51161/coquelicot + SetEnv proxy-sendchunks 1 + RequestHeader set X-Forwarded-SSL "on" + diff --git a/data/etc/plinth/modules-enabled/coquelicot b/data/etc/plinth/modules-enabled/coquelicot new file mode 100644 index 000000000..2b2d71614 --- /dev/null +++ b/data/etc/plinth/modules-enabled/coquelicot @@ -0,0 +1 @@ +plinth.modules.coquelicot diff --git a/plinth/action_utils.py b/plinth/action_utils.py index e5acc96b5..6a4dc71ee 100644 --- a/plinth/action_utils.py +++ b/plinth/action_utils.py @@ -99,41 +99,36 @@ def service_unmask(service_name): def service_start(service_name): """Start a service with systemd or sysvinit.""" - if is_systemd_running(): - subprocess.run(['systemctl', 'start', service_name], - stdout=subprocess.DEVNULL) - else: - subprocess.run(['service', service_name, 'start'], - stdout=subprocess.DEVNULL) + service_action(service_name, 'start') def service_stop(service_name): """Stop a service with systemd or sysvinit.""" - if is_systemd_running(): - subprocess.run(['systemctl', 'stop', service_name], - stdout=subprocess.DEVNULL) - else: - subprocess.run(['service', service_name, 'stop'], - stdout=subprocess.DEVNULL) + service_action(service_name, 'stop') def service_restart(service_name): """Restart a service with systemd or sysvinit.""" - if is_systemd_running(): - subprocess.run(['systemctl', 'restart', service_name], - stdout=subprocess.DEVNULL) - else: - subprocess.run(['service', service_name, 'restart'], - stdout=subprocess.DEVNULL) + service_action(service_name, 'restart') + + +def service_try_restart(service_name): + """Try to restart a service with systemd or sysvinit.""" + service_action(service_name, 'try-restart') def service_reload(service_name): """Reload a service with systemd or sysvinit.""" + service_action(service_name, 'reload') + + +def service_action(service_name, action): + """Preform the given action on the service_name.""" if is_systemd_running(): - subprocess.run(['systemctl', 'reload', service_name], + subprocess.run(['systemctl', action, service_name], stdout=subprocess.DEVNULL) else: - subprocess.run(['service', service_name, 'reload'], + subprocess.run(['service', service_name, action], stdout=subprocess.DEVNULL) diff --git a/plinth/modules/coquelicot/__init__.py b/plinth/modules/coquelicot/__init__.py new file mode 100644 index 000000000..cc8c70618 --- /dev/null +++ b/plinth/modules/coquelicot/__init__.py @@ -0,0 +1,133 @@ +# +# 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 to configure coquelicot. +""" + +from django.utils.translation import ugettext_lazy as _ + +from plinth import service as service_module +from plinth import action_utils, actions, frontpage +from plinth.menu import main_menu + +from .manifest import clients + +clients = clients + +version = 1 + +managed_services = ['coquelicot'] + +managed_packages = ['coquelicot'] + +name = _('Coquelicot') + +short_description = _('File Sharing') + +description = [ + _('Coquelicot is a “one-click” file sharing web application with a focus ' + 'on protecting users’ privacy. It is best used for quickly sharing a ' + 'single file. '), + _('This Coquelicot instance is exposed to the public but requires an ' + 'upload password to prevent unauthorized access. You can set a new ' + 'upload password in the form that will appear below after installation. ' + 'The default upload password is "test".') +] + +service = None + + +def init(): + """Intialize the module.""" + menu = main_menu.get('apps') + menu.add_urlname(name, 'glyphicon-open-file', 'coquelicot:index', + short_description) + + global service + setup_helper = globals()['setup_helper'] + if setup_helper.get_state() != 'needs-setup': + service = service_module.Service(managed_services[0], name, ports=[ + 'http', 'https' + ], is_external=True, is_enabled=is_enabled, enable=enable, + disable=disable, + is_running=is_running) + + if is_enabled(): + add_shortcut() + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(managed_packages) + helper.call('post', actions.superuser_run, 'coquelicot', ['setup']) + helper.call('post', actions.superuser_run, 'coquelicot', ['enable']) + global service + if service is None: + service = service_module.Service(managed_services[0], name, ports=[ + 'http', 'https' + ], is_external=True, is_enabled=is_enabled, enable=enable, + disable=disable, + is_running=is_running) + helper.call('post', service.notify_enabled, None, True) + helper.call('post', add_shortcut) + + +def add_shortcut(): + """Helper method to add a shortcut to the frontpage.""" + frontpage.add_shortcut('coquelicot', name, + short_description=short_description, + url='/coquelicot', login_required=True) + + +def is_running(): + """Return whether the service is running.""" + return action_utils.service_is_running('coquelicot') + + +def is_enabled(): + """Return whether the module is enabled.""" + return (action_utils.service_is_enabled('coquelicot') + and action_utils.webserver_is_enabled('coquelicot-freedombox')) + + +def enable(): + """Enable the module.""" + actions.superuser_run('coquelicot', ['enable']) + add_shortcut() + + +def disable(): + """Disable the module.""" + actions.superuser_run('coquelicot', ['disable']) + frontpage.remove_shortcut('coquelicot') + + +def get_current_max_file_size(): + """Get the current value of maximum file size.""" + size = actions.superuser_run('coquelicot', ['get-max-file-size']) + return int(size.strip()) + + +def diagnose(): + """Run diagnostics and return the results.""" + results = [] + + results.extend( + action_utils.diagnose_url_on_all('https://{host}/coquelicot', + check_certificate=False)) + + return results diff --git a/plinth/modules/coquelicot/forms.py b/plinth/modules/coquelicot/forms.py new file mode 100644 index 000000000..1d8535f5f --- /dev/null +++ b/plinth/modules/coquelicot/forms.py @@ -0,0 +1,37 @@ +# +# 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 form for configuring Coquelicot. +""" + +from django import forms +from django.utils.translation import ugettext_lazy as _ + +from plinth.forms import ServiceForm + + +class CoquelicotForm(ServiceForm): # pylint: disable=W0232 + """Coquelicot configuration form.""" + upload_password = forms.CharField( + label=_('Upload Password'), + help_text=_('Set a new upload password for Coquelicot. ' + 'Leave this field blank to keep the current password.'), + required=False, widget=forms.PasswordInput) + max_file_size = forms.IntegerField( + label=_("Maximum File Size (in MiB)"), help_text=_( + 'Set the maximum size of the files that can be uploaded to ' + 'Coquelicot.'), required=False, min_value=0) diff --git a/plinth/modules/coquelicot/manifest.py b/plinth/modules/coquelicot/manifest.py new file mode 100644 index 000000000..88a17ba54 --- /dev/null +++ b/plinth/modules/coquelicot/manifest.py @@ -0,0 +1,28 @@ +# +# 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 . +# + +from django.utils.translation import ugettext_lazy as _ + +from plinth.clients import validate + +clients = validate([{ + 'name': _('coquelicot'), + 'platforms': [{ + 'type': 'web', + 'url': '/coquelicot' + }] +}]) diff --git a/plinth/modules/coquelicot/urls.py b/plinth/modules/coquelicot/urls.py new file mode 100644 index 000000000..d1934ba38 --- /dev/null +++ b/plinth/modules/coquelicot/urls.py @@ -0,0 +1,27 @@ +# +# 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 coquelicot module. +""" + +from django.conf.urls import url + +from .views import CoquelicotServiceView + +urlpatterns = [ + url(r'^apps/coquelicot/$', CoquelicotServiceView.as_view(), name='index'), +] diff --git a/plinth/modules/coquelicot/views.py b/plinth/modules/coquelicot/views.py new file mode 100644 index 000000000..b1f95276f --- /dev/null +++ b/plinth/modules/coquelicot/views.py @@ -0,0 +1,72 @@ +# +# 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 views for Coquelicot. +""" + +from django.contrib import messages +from django.utils.translation import ugettext as _ + +from plinth import actions, views +from plinth.errors import ActionError +from plinth.modules.coquelicot import (clients, description, + get_current_max_file_size) + +from .forms import CoquelicotForm + + +class CoquelicotServiceView(views.ServiceView): + """Serve configuration page.""" + clients = clients + description = description + diagnostics_module_name = 'coquelicot' + service_id = 'coquelicot' + form_class = CoquelicotForm + show_status_block = True + + def get_initial(self): + """Return the status of the service to fill in the form.""" + initial = super().get_initial() + initial['max_file_size'] = get_current_max_file_size() + return initial + + def form_valid(self, form): + """Apply the changes submitted in the form.""" + form_data = form.cleaned_data + + if form_data['upload_password']: + try: + actions.superuser_run( + 'coquelicot', ['set-upload-password'], + input=form_data['upload_password'].encode()) + messages.success(self.request, _('Upload password updated')) + except ActionError as e: + messages.error(self.request, + _('Failed to update upload password')) + + max_file_size = form_data['max_file_size'] + if max_file_size and max_file_size != get_current_max_file_size(): + try: + actions.superuser_run( + 'coquelicot', ['set-max-file-size', + str(max_file_size)]) + messages.success(self.request, _('Maximum file size updated')) + except ActionError as e: + messages.error(self.request, + _('Failed to update maximum file size')) + + return super().form_valid(form) diff --git a/static/themes/default/icons/coquelicot.png b/static/themes/default/icons/coquelicot.png new file mode 100644 index 0000000000000000000000000000000000000000..9bb173f8329a5efa0e5c7cf477329ab3682fcd3b GIT binary patch literal 27771 zcmV(004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 S&CD