diff --git a/actions/__init__.py b/actions/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/actions/pagekite b/actions/pagekite index 0a9024158..56b692a86 100755 --- a/actions/pagekite +++ b/actions/pagekite @@ -18,7 +18,7 @@ # """ -Configuration helper for Plint PageKite interface +Configuration helper for Plinth PageKite interface Unfortunately there is no python3 package for augeas yet """ diff --git a/actions/pagekite_util.py b/actions/pagekite_util.py index b3568d43a..b96786fe4 100644 --- a/actions/pagekite_util.py +++ b/actions/pagekite_util.py @@ -19,11 +19,14 @@ """ Utilities for configuring PageKite. - -The variables/functions defined here are used by both the action script -and the plinth pagekite module. """ -# ATTENTION: This file has to be both python2 and python3 compatible +# TODO: +# Once python-augeas is available for python3 import the following things +# from plinth.modules.pagekite.util (instead of having a copy in here): +# +# SERVICE_PARAMS, convert_service_to_string, convert_to_service +# +# until then, this file is python2 and python3 compatible for the unittests import os import shlex diff --git a/plinth/__main__.py b/plinth/__main__.py index 0c4a253a1..7ceacca80 100644 --- a/plinth/__main__.py +++ b/plinth/__main__.py @@ -222,9 +222,6 @@ def configure_django(): 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'plinth.modules.first_boot.middleware.FirstBootMiddleware', ), - MESSAGE_TAGS = { - messages_constants.ERROR: 'danger' - }, ROOT_URLCONF='plinth.urls', SECURE_PROXY_SSL_HEADER=secure_proxy_ssl_header, SESSION_ENGINE='django.contrib.sessions.backends.file', diff --git a/plinth/modules/pagekite/__init__.py b/plinth/modules/pagekite/__init__.py index b9ad5fb14..18fcccbcb 100644 --- a/plinth/modules/pagekite/__init__.py +++ b/plinth/modules/pagekite/__init__.py @@ -21,9 +21,8 @@ Plinth module to configure PageKite from gettext import gettext as _ from plinth import cfg -from . import pagekite -__all__ = ['pagekite', 'init'] +__all__ = ['init'] depends = ['plinth.modules.apps'] diff --git a/plinth/modules/pagekite/forms.py b/plinth/modules/pagekite/forms.py index 2df7f00c8..1228b9d5e 100644 --- a/plinth/modules/pagekite/forms.py +++ b/plinth/modules/pagekite/forms.py @@ -23,10 +23,9 @@ from django import forms from django.contrib import messages from django.core import validators -from actions.pagekite_util import convert_service_to_string from plinth.errors import ActionError -from .util import PREDEFINED_SERVICES, _run, get_kite_details, KITE_NAME, \ - KITE_SECRET, BACKEND_HOST +from .util import _run, convert_service_to_string, get_kite_details, \ + BACKEND_HOST, KITE_NAME, KITE_SECRET, PREDEFINED_SERVICES LOGGER = logging.getLogger(__name__) diff --git a/plinth/modules/pagekite/pagekite.py b/plinth/modules/pagekite/pagekite.py deleted file mode 100644 index a22c961d9..000000000 --- a/plinth/modules/pagekite/pagekite.py +++ /dev/null @@ -1,183 +0,0 @@ -# -# 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 configuring PageKite service -""" - - - - - - - - - - - - - return super(TrimmedCharField, self).clean(value) - - -class ConfigureForm(forms.Form): # pylint: disable-msg=W0232 - """Form to configure PageKite""" - enabled = forms.BooleanField(label=_('Enable PageKite'), - required=False) - - server = forms.CharField( - label=_('Server'), required=False, - help_text=_('Select your pagekite.net server. Set "pagekite.net" to ' - 'use the default pagekite.net server'), - widget=forms.TextInput()) - - kite_name = TrimmedCharField( - label=_('Kite name'), - help_text=_('Example: mybox1-myacc.pagekite.me'), - validators=[ - validators.RegexValidator(r'^[\w-]{1,63}(\.[\w-]{1,63})*$', - _('Invalid kite name'))]) - - kite_secret = TrimmedCharField( - label=_('Kite secret'), - help_text=_('A secret associated with the kite or the default secret \ -for your account if no secret is set on the kite')) - - http_enabled = forms.BooleanField( - label=_('Web Server (HTTP)'), required=False, - help_text=_('Site will be available at \ -http://mybox1-myacc.pagekite.me \ -')) - - ssh_enabled = forms.BooleanField( - label=_('Secure Shell (SSH)'), required=False, - help_text=_('See SSH client setup instructions')) - - -@login_required -@package.required(['pagekite']) -def configure(request): - """Serve the configuration form""" - status = get_status() - - form = None - - if request.method == 'POST': - form = ConfigureForm(request.POST, prefix='pagekite') - # pylint: disable-msg=E1101 - if form.is_valid(): - _apply_changes(request, status, form.cleaned_data) - status = get_status() - form = ConfigureForm(initial=status, prefix='pagekite') - else: - form = ConfigureForm(initial=status, prefix='pagekite') - - return TemplateResponse(request, 'pagekite_configure.html', - {'title': _('Configure PageKite'), - 'status': status, - 'form': form, - 'subsubmenu': subsubmenu}) - - -def get_status(): - """ - Return the current status of PageKite configuration by - executing various actions. - """ - status = {} - - # PageKite service enabled/disabled - output = _run(['get-status']) - status['enabled'] = (output.split()[0] == 'enabled') - - # PageKite kite details - output = _run(['get-kite']) - kite_details = output.split() - status['kite_name'] = kite_details[0] - status['kite_secret'] = kite_details[1] - - # PageKite server: 'pagekite.net' if flag 'defaults' is set, - # the value of 'frontend' otherwise - use_pagekitenet_server = _run(['get-pagekitenet-frontend-status']) - if "enabled" in use_pagekitenet_server: - value = 'pagekite.net' - elif "disabled" in use_pagekitenet_server: - value = _run(['get-frontend']) - status['server'] = value.replace('\n', '') - - # Service status - status['service'] = {} - for service in ('http', 'ssh'): - output = _run(['get-service-status', service]) - status[service + '_enabled'] = (output.split()[0] == 'enabled') - - return status - - -def _apply_changes(request, old_status, new_status): - """Apply the changes to PageKite configuration""" - LOGGER.info('New status is - %s', new_status) - - if old_status != new_status: - _run(['stop']) - - if old_status['enabled'] != new_status['enabled']: - if new_status['enabled']: - _run(['set-status', 'enable']) - messages.success(request, _('PageKite enabled')) - else: - _run(['set-status', 'disable']) - messages.success(request, _('PageKite disabled')) - - if old_status['kite_name'] != new_status['kite_name'] or \ - old_status['kite_secret'] != new_status['kite_secret']: - _run(['set-kite', '--kite-name', new_status['kite_name'], - '--kite-secret', new_status['kite_secret']]) - messages.success(request, _('Kite details set')) - - if old_status['server'] != new_status['server']: - server = new_status['server'] - if server in ('defaults', 'default', 'pagekite.net'): - _run(['enable-pagekitenet-frontend']) - else: - _run(['set-frontend', server]) - messages.success(request, _('Pagekite server set')) - - for service in ['http', 'ssh']: - if old_status[service + '_enabled'] != \ - new_status[service + '_enabled']: - if new_status[service + '_enabled']: - _run(['set-service-status', service, 'enable']) - messages.success(request, _('Service enabled: {service}') - .format(service=service)) - else: - _run(['set-service-status', service, 'disable']) - messages.success(request, _('Service disabled: {service}') - .format(service=service)) - - if old_status != new_status: - _run(['start']) - - -def _run(arguments, superuser=True): - """Run a given command and raise exception if there was an error""" - command = 'pagekite-configure' - - if superuser: - return actions.superuser_run(command, arguments) - else: - return actions.run(command, arguments) diff --git a/plinth/modules/pagekite/templates/pagekite_configure.html b/plinth/modules/pagekite/templates/pagekite_configure.html index 07c5c6066..8276a8994 100644 --- a/plinth/modules/pagekite/templates/pagekite_configure.html +++ b/plinth/modules/pagekite/templates/pagekite_configure.html @@ -27,7 +27,7 @@ {% include 'bootstrapform/field.html' with field=form.enabled %} -

PageKite Account

{{ form.server|bootstrap_horizontal }} @@ -47,9 +47,9 @@ $('#id_pagekite-enabled').change(function() { if ($('#id_pagekite-enabled').prop('checked')) { - $('.pagekite-post-enabled-form').show('slow'); + $('#pagekite-post-enabled-form').show('slow'); } else { - $('.pagekite-post-enabled-form').hide('slow'); + $('#pagekite-post-enabled-form').hide('slow'); } }); diff --git a/plinth/modules/pagekite/templates/pagekite_default_services.html b/plinth/modules/pagekite/templates/pagekite_default_services.html index f9654efe8..53fbd99e4 100644 --- a/plinth/modules/pagekite/templates/pagekite_default_services.html +++ b/plinth/modules/pagekite/templates/pagekite_default_services.html @@ -26,10 +26,6 @@ display: inline-block; margin: 0px 10px; } - div.custom-services span.service { - display: inline-block; - padding-top: 6px; - } input.btn { margin: 10px 15px; } diff --git a/plinth/modules/pagekite/templates/pagekite_introduction.html b/plinth/modules/pagekite/templates/pagekite_introduction.html index 23c7fe99c..2e3781b5c 100644 --- a/plinth/modules/pagekite/templates/pagekite_introduction.html +++ b/plinth/modules/pagekite/templates/pagekite_introduction.html @@ -22,7 +22,7 @@

PageKite is a system for exposing {{ cfg.box_name }} services when you don't have a direct connection to the Internet. You only need this -service if your {{ cfg.box_name }} services are unreachable from the +if your {{ cfg.box_name }} services are unreachable from the rest of the Internet. This includes the following situations:

PageKite works around NAT, firewalls and IP-address limitations by -using a combination of tunnels and reverse proxies. Currently, -exposing web server and SSH server are supported. You can use any -server that offers a pagekite service, for example +using a combination of tunnels and reverse proxies. You can use any +pagekite service provider, for example pagekite.net. -In future, it might be possible to use your buddy's +In future it might be possible to use your buddy's {{ cfg.box_name }} for this.

diff --git a/plinth/modules/pagekite/util.py b/plinth/modules/pagekite/util.py index 9fac5b859..d6c52b7dd 100644 --- a/plinth/modules/pagekite/util.py +++ b/plinth/modules/pagekite/util.py @@ -17,17 +17,20 @@ from gettext import gettext as _ import logging +import shlex -from actions.pagekite_util import convert_to_service from plinth import actions LOGGER = logging.getLogger(__name__) # defaults for the credentials; @kitename acts as a placeholder and is # understood (and replaced with the actual kitename) by pagekite. +BACKEND_HOST = 'localhost' KITE_NAME = '@kitename' KITE_SECRET = '@kitesecret' -BACKEND_HOST = 'localhost' + +SERVICE_PARAMS = ['protocol', 'kitename', 'backend_host', 'backend_port', + 'secret'] # Predefined services are used to build the PredefinedServiceForm # @@ -68,6 +71,57 @@ PREDEFINED_SERVICES = { } +def convert_to_service(service_string): + """ Convert a service string into a service parameter dictionary + >>> convert_to_service('https/443:@kitename:localhost:443:@kitesecret') + {'kitename': '@kitename', 'backend_host': 'localhost', \ +'secret': '@kitesecret', 'protocol': 'https/443', 'backend_port': '443'} + """ + # The actions.py uses shlex.quote() to escape/quote malicious user input. + # That affects '*.@kitename', so the params string gets quoted. + # If the string is escaped and contains '*.@kitename', look whether shlex + # would still quote/escape the string when we remove '*.@kitename'. + + if service_string.startswith("'") and service_string.endswith("'"): + unquoted_string = service_string[1:-1] + error_msg = "The parameters contain suspicious characters: %s " + if '*.@kitename' in service_string: + unquoted_test_string = unquoted_string.replace('*.@kitename', '') + if unquoted_test_string == shlex.quote(unquoted_test_string): + # no other malicious characters found, use the unquoted string + service_string = unquoted_string + else: + raise RuntimeError(error_msg % service_string) + else: + raise RuntimeError(error_msg % service_string) + + try: + params = dict(zip(SERVICE_PARAMS, service_string.split(':'))) + except Exception: + msg = """params are expected to be a ':'-separated string containing + values for: %s , for example:\n"--params + http/8000:@kitename:localhost:8000:@kitesecret" + """ + raise ValueError(msg % ", ".join(SERVICE_PARAMS)) + return params + + +def convert_service_to_string(service): + """ Convert service dict into a ":"-separated parameter string + + >>> convert_service_to_string({'kitename': '@kitename', \ +'backend_host': 'localhost', 'secret': '@kitesecret', \ +'protocol': 'https/443', 'backend_port': '443'}) + 'https/443:@kitename:localhost:443:@kitesecret' + """ + try: + service_string = ":".join([str(service[param]) for param in + SERVICE_PARAMS]) + except KeyError: + raise ValueError("Could not parse params: %s " % service) + return service_string + + def get_kite_details(): output = _run(['get-kite']) kite_details = output.split() diff --git a/plinth/modules/pagekite/views.py b/plinth/modules/pagekite/views.py index adfb0bcb5..8637b953f 100644 --- a/plinth/modules/pagekite/views.py +++ b/plinth/modules/pagekite/views.py @@ -30,6 +30,7 @@ from .forms import ConfigurationForm, DefaultServiceForm, \ AddCustomServiceForm, DeleteCustomServiceForm +required_packages = ('pagekite', 'augeas-tools', 'python-augeas') subsubmenu = [{'url': reverse_lazy('pagekite:index'), 'text': _('About PageKite')}, {'url': reverse_lazy('pagekite:configure'), @@ -50,7 +51,7 @@ def index(request): class ContextMixin(object): """Mixin to add 'subsubmenu' and 'title' to the context. - Also requires 'pagekite' to be installed. + Also adds the requirement of all necessary packages to be installed """ def get_context_data(self, **kwargs): """Use self.title and the module-level subsubmenu""" @@ -59,8 +60,7 @@ class ContextMixin(object): context['subsubmenu'] = subsubmenu return context - @method_decorator(package.required('pagekite', 'augeas-tools', - 'python-augeas')) + @method_decorator(package.required(required_packages)) def dispatch(self, *args, **kwargs): return super(ContextMixin, self).dispatch(*args, **kwargs) diff --git a/plinth/tests/test_pagekite_actions.py b/plinth/tests/test_pagekite_actions.py index 2c8d66b9e..c5904383e 100644 --- a/plinth/tests/test_pagekite_actions.py +++ b/plinth/tests/test_pagekite_actions.py @@ -18,12 +18,13 @@ import os import unittest -from actions.pagekite_util import get_augeas_servicefile_path, CONF_PATH, \ - convert_to_service, convert_service_to_string +from actions.pagekite_util import get_augeas_servicefile_path, CONF_PATH +from plinth.modules.pagekite.util import convert_to_service, \ + convert_service_to_string class TestPagekiteActions(unittest.TestCase): - # test-cases to convert parameter-strings into param dicts and back + """Test-cases for the pagekite action utils""" _tests = [ { 'line': 'https/8080:*.@kitename:localhost:8080:@kitesecret',