From cf96797040be510006ada43b23be72af556638ff Mon Sep 17 00:00:00 2001
From: fonfon
Date: Tue, 6 Jan 2015 16:32:10 +0100
Subject: [PATCH 01/21] pagekite action: Use augeas pagekite lens
And allow setting any server as pagekite frontend.
---
actions/__init__.py | 0
actions/pagekite | 296 +++++++++++++++
actions/pagekite-configure | 290 ---------------
actions/pagekite-configure-configobj | 352 ++++++++++++++++++
actions/pagekite_common.py | 52 +++
data/usr/share/augeas/lenses/pagekite.aug | 79 ++++
.../augeas/lenses/tests/test_pagekite.aug | 111 ++++++
plinth/modules/pagekite/pagekite.py | 23 +-
.../templates/pagekite_configure.html | 10 +-
setup.py | 4 +
10 files changed, 919 insertions(+), 298 deletions(-)
create mode 100644 actions/__init__.py
create mode 100755 actions/pagekite
delete mode 100755 actions/pagekite-configure
create mode 100755 actions/pagekite-configure-configobj
create mode 100644 actions/pagekite_common.py
create mode 100644 data/usr/share/augeas/lenses/pagekite.aug
create mode 100644 data/usr/share/augeas/lenses/tests/test_pagekite.aug
diff --git a/actions/__init__.py b/actions/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/actions/pagekite b/actions/pagekite
new file mode 100755
index 000000000..561a31427
--- /dev/null
+++ b/actions/pagekite
@@ -0,0 +1,296 @@
+#!/usr/bin/python2
+# -*- 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 Plint PageKite interface
+
+Unfortunately there is no python3 package for augeas yet
+"""
+
+import argparse
+import augeas
+import os
+import subprocess
+
+from pagekite_common import SERVICE_PARAMS, construct_params
+
+aug = augeas.Augeas()
+
+CONF_PATH = '/files/etc/pagekite.d'
+PATHS = {
+ 'service_on': os.path.join(CONF_PATH, '*', 'service_on', '*'),
+ 'kitename': os.path.join(CONF_PATH, '10_account.rc', 'kitename'),
+ 'kitesecret': os.path.join(CONF_PATH, '10_account.rc', 'kitesecret'),
+ 'abort_not_configured': os.path.join(CONF_PATH, '10_account.rc',
+ 'abort_not_configured'),
+ 'defaults': os.path.join(CONF_PATH, '20_frontends.rc', 'defaults'),
+ 'frontend': os.path.join(CONF_PATH, '20_frontends.rc', 'frontend'),
+}
+
+
+def parse_arguments():
+ """Return parsed command line arguments as dictionary"""
+ parser = argparse.ArgumentParser()
+ subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
+
+ # Start/Stop PageKite
+ subparsers.add_parser('start', help='Start PageKite service')
+ subparsers.add_parser('stop', help='Stop PageKite service')
+
+ # Get/set status
+ subparsers.add_parser('is-enabled', help='Get whether PakeKite is enabled')
+ subparsers.add_parser('enable', help='Enable PageKite service')
+ subparsers.add_parser('disable', help='Disable PageKite service')
+
+ # get/set using the default pagekite.net frontend
+ subparsers.add_parser('get-pagekitenet-frontend-status',
+ help='Get whether pagekite.net frontend is enabled')
+ subparsers.add_parser('enable-pagekitenet-frontend',
+ help='Enable using default pagekite.net frontend')
+ subparsers.add_parser('disable-pagekitenet-frontend',
+ help='Disable default pagekite.net frontend')
+
+ # Frontend
+ subparsers.add_parser('get-frontend', help='Get pagekite frontend')
+ set_frontend = subparsers.add_parser('set-frontend',
+ help='Set pagekite frontend')
+ set_frontend.add_argument('url', help='frontend url')
+
+ # Kite details (name + secret)
+ subparsers.add_parser('get-kite',
+ help='Get configured kite name and secret')
+ set_kite = subparsers.add_parser('set-kite',
+ help='Configure kite name and its secret')
+ set_kite.add_argument('--kite-name',
+ help='Name of the kite (eg: mybox.pagekite.me)')
+ set_kite.add_argument('--kite-secret', help='Secret for the kite')
+
+ # Services
+ subparsers.add_parser('get-services', help='Get list of enabled services')
+ add_service = subparsers.add_parser('add-service',
+ help='Add a pagekite service')
+ add_service.add_argument('--params', help='\':\'-separated parameters')
+ remove_service = subparsers.add_parser('remove-service',
+ help='Remove a pagekite service')
+ remove_service.add_argument('--params', help='\':\'-separated parameters')
+
+ return parser.parse_args()
+
+
+def subcommand_start(_):
+ """Start PageKite service"""
+ status = subprocess.call(['service', 'pagekite', 'start'])
+ if status:
+ raise Exception('Unable to start PageKite server')
+
+
+def subcommand_stop(_):
+ """Stop PageKite service"""
+ status = subprocess.call(['service', 'pagekite', 'stop'])
+ if status:
+ raise Exception('Unable to stop PageKite server')
+
+
+def subcommand_is_enabled(_):
+ """Print whether pagekite is enabled (yes or no)"""
+ is_enabled = is_pagekite_enabled()
+ print 'yes' if is_enabled else 'no'
+
+
+def is_pagekite_enabled():
+ return not bool(aug.match(PATHS['abort_not_configured']))
+
+
+def subcommand_enable(_):
+ pagekite_enable()
+ print 'enabled'
+
+
+def subcommand_disable(_):
+ pagekite_disable()
+ print 'disabled'
+
+
+def subcommand_get_frontend(_):
+ """Get pagekite frontend url"""
+ url = aug.get(PATHS['frontend'])
+ print url if url else ""
+
+
+def subcommand_set_frontend(arguments):
+ """Set pagekite frontend url and disable default pagekite.net frontend"""
+ aug.remove(PATHS['defaults'])
+ aug.set(PATHS['frontend'], arguments.url)
+ aug.save()
+
+
+def subcommand_get_pagekitenet_frontend_status(_):
+ match = aug.match(PATHS['defaults'])
+ print "enabled" if match else "disabled"
+
+
+def subcommand_enable_pagekitenet_frontend(_):
+ """Enable using default pageket.net frontend
+
+ This disables any other frontends.
+ """
+ aug.set(PATHS['defaults'], '')
+ aug.remove(PATHS['frontend'])
+ aug.save()
+ print "enabled"
+
+
+def subcommand_disable_pagekitenet_frontend(_):
+ aug.remove(PATHS['defaults'])
+ aug.save()
+ print "disabled"
+
+
+def pagekite_enable():
+ """Enable the pagekite daemon"""
+ aug.remove(PATHS['abort_not_configured'])
+ aug.save()
+
+
+def pagekite_disable():
+ """Disable the pagekite daemon"""
+ aug.set(PATHS['abort_not_configured'], '')
+ aug.save()
+
+
+def subcommand_get_services(arguments):
+ """ lists all available (enabled) services """
+ for match in aug.match(PATHS['service_on']):
+ print ":".join([aug.get(os.path.join(match, param)) for param in
+ SERVICE_PARAMS])
+
+
+def subcommand_remove_service(arguments):
+ """Searches and removes the service(s) that match all given parameters"""
+ params = construct_params(arguments.params)
+ paths = get_existing_service_paths(params)
+ # TODO: theoretically, everything to do here is:
+ # [aug.remove(path) for path in paths]
+ # but augeas won't let you save the changed files, and won't tell you why
+ for path in paths:
+ filepath = convert_augeas_path_to_filepath(path)
+ service_found = False
+ with open(filepath, 'r') as file:
+ lines = file.readlines()
+ for i, line in enumerate(lines):
+ if line.startswith('service_on') and \
+ all(param in line for param in params.values()):
+ lines[i] = ""
+ service_found = True
+ break
+ if service_found:
+ with open(filepath, 'w') as file:
+ file.writelines(lines)
+
+
+def get_existing_service_paths(params):
+ """Returns paths of existing services that match the given params"""
+ # construct an augeas query path with patterns like:
+ # */service_on/*[protocol='http']
+ path = PATHS['service_on']
+ for key, value in params.items():
+ path += "[%s='%s']" % (key, value)
+ return aug.match(path)
+
+
+def subcommand_add_service(arguments):
+ """Add one service"""
+ params = construct_params(arguments.params)
+ if get_existing_service_paths(params):
+ msg = "Service with the parameters %s already exists"
+ raise RuntimeError(msg % params)
+
+ root = get_new_service_path(params['protocol'])
+ # TODO: after adding a service, augeas fails writing the config;
+ # so do it manually here
+ path = convert_augeas_path_to_filepath(root)
+ with open(path, 'a') as servicefile:
+ line = "service_on = %s" % arguments.params
+ servicefile.write(line)
+
+
+def convert_augeas_path_to_filepath(augpath, prefix='/files',
+ suffix='service_on'):
+ """Convert an augeas service_on path to the actual file path"""
+ if augpath.startswith(prefix):
+ augpath = augpath.replace(prefix, "", 1)
+
+ index = augpath.rfind(suffix)
+ if index:
+ augpath = augpath[:index]
+ return augpath.rstrip('/')
+
+
+def get_new_service_path(protocol):
+ """Get the augeas path of a new service for a protocol
+
+ This takes care of existing services using a /service_on/*/ query"""
+ root = get_augeas_servicefile_path(protocol)
+ new_index = len(aug.match(root + '/*')) + 1
+ return os.path.join(root, str(new_index))
+
+
+def get_augeas_servicefile_path(protocol):
+ """Get the augeas path where a service for a protocol should be stored"""
+ if protocol == 'http':
+ relpath = '80_httpd.rc'
+ elif protocol == 'https':
+ relpath = '443_https.rc'
+ elif protocol == 'raw/22':
+ relpath = '22_ssh.rc'
+ elif protocol.startswith('raw'):
+ port = protocol.split('/')[1]
+ relpath = '%s.rc' % port
+ else:
+ raise ValueError('Unsupported protocol: %s' % protocol)
+
+ return os.path.join(CONF_PATH, relpath, 'service_on')
+
+
+def subcommand_get_kite(_):
+ """Print details of the currently configured kite"""
+ kitename = aug.get(PATHS['kitename'])
+ kitesecret = aug.get(PATHS['kitesecret'])
+ print kitename if kitename else ''
+ print kitesecret if kitesecret else ''
+
+
+def subcommand_set_kite(arguments):
+ """Set details of the kite"""
+ aug.set(PATHS['kitename'], arguments.kite_name)
+ aug.set(PATHS['kitesecret'], arguments.kite_secret)
+ aug.save()
+
+
+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/actions/pagekite-configure b/actions/pagekite-configure
deleted file mode 100755
index a5af96fb0..000000000
--- a/actions/pagekite-configure
+++ /dev/null
@@ -1,290 +0,0 @@
-#!/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 Plint PageKite inteface
-
-TODO: Use augeas for manipulating /etc/pagekite.d/* files
-"""
-
-# Disable warning about invalid module name # pylint: disable-msg=C0103
-
-import argparse
-import os
-import re
-import subprocess
-
-CONFIG_DIR = '/etc/pagekite.d'
-
-SERVICE_FILE_MAP = {
- 'http': {
- 'file': '80_httpd.rc',
- 'match': 'http:',
- 'line': 'service_on = http:@kitename : localhost:80 : @kitesecret'},
- 'ssh': {
- 'file': '80_sshd.rc',
- 'match': 'raw/22:',
- 'line': 'service_on = raw/22:@kitename : localhost:22 : @kitesecret'}}
-
-
-def parse_arguments():
- """Return parsed command line arguments as dictionary"""
- parser = argparse.ArgumentParser()
- subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
-
- # Start PageKite
- subparsers.add_parser('start', help='Start PageKite service')
-
- # Stop PageKite
- subparsers.add_parser('stop', help='Stop PageKite service')
-
- # Get status
- subparsers.add_parser('get-status', help='Get whether PakeKite is enabled')
-
- # Set status
- set_status = subparsers.add_parser('set-status',
- help='Enable/disable PageKite')
- set_status.add_argument('enable', choices=['enable', 'disable'])
-
- # Get kite details
- subparsers.add_parser('get-kite',
- help='Get configured kite name and secret')
-
- # Set kite details
- set_kite = subparsers.add_parser('set-kite',
- help='Configure kite name and its secret')
- set_kite.add_argument('--kite-name',
- help='Name of the kite (eg: mybox.pagekite.me)')
- set_kite.add_argument('--kite-secret', help='Secret for the kite')
-
- # Get service status
- get_service = subparsers.add_parser('get-service-status',
- help='Get whether service is enabled')
- get_service.add_argument('service', choices=['http', 'ssh'])
-
- # Set service status
- set_service = subparsers.add_parser('set-service-status',
- help='Enable/disable a service')
- set_service.add_argument('service', choices=['http', 'ssh'])
- set_service.add_argument('enable', choices=['enable', 'disable'])
-
- return parser.parse_args()
-
-
-def subcommand_start(_):
- """Start PageKite service"""
- status = subprocess.call(['service', 'pagekite', 'start'])
- if status:
- raise Exception('Unable to start PageKite server')
-
-
-def subcommand_stop(_):
- """Stop PageKite service"""
- status = subprocess.call(['service', 'pagekite', 'stop'])
- if status:
- raise Exception('Unable to stop PageKite server')
-
-
-def subcommand_get_status(_):
- """Print status of the pagekite service"""
- is_enabled = is_pagekite_enabled()
- print('enabled' if is_enabled else 'disabled')
-
-
-def is_pagekite_enabled():
- """Return if pagekite is enabled"""
- service_file_path = os.path.join(CONFIG_DIR, '10_account.rc')
- try:
- with open(service_file_path, 'r') as file_object:
- for line in file_object:
- regex = r'^[ \t]*abort_not_configured'
- if re.match(regex, line):
- return False
- except Exception:
- return True
-
- return True
-
-
-def subcommand_set_status(arguments):
- """Enable/disable the pagekite service"""
- enable = arguments.enable == 'enable'
- is_enabled = is_pagekite_enabled()
- if enable and is_enabled:
- print('already enabled')
- return
-
- if not enable and not is_enabled:
- print('already disabled')
- return
-
- if enable:
- pagekite_enable()
- print('enabled')
- else:
- pagekite_disable()
- print('disabled')
-
-
-def pagekite_enable():
- """Enable the pagekite daemon"""
- file_path = os.path.join(CONFIG_DIR, '10_account.rc')
- file_path_new = os.path.join(CONFIG_DIR, '10_account.rc.new')
- with open(file_path, 'r') as read_file_object, \
- open(file_path_new, 'w') as write_file_object:
- for line in read_file_object:
- if not re.match('^[ \t]*abort_not_configured', line):
- write_file_object.write(line)
-
- os.rename(file_path_new, file_path)
-
-
-def pagekite_disable():
- """Disable the pagekite daemon"""
- file_path = os.path.join(CONFIG_DIR, '10_account.rc')
- with open(file_path, 'a') as file_object:
- file_object.write('abort_not_configured\n')
-
-
-def subcommand_get_kite(_):
- """Print details of the currently configure kite"""
- kite_name = ''
- kite_secret = ''
-
- file_path = os.path.join(CONFIG_DIR, '10_account.rc')
- with open(file_path, 'r') as file_object:
- for line in file_object:
- match = re.match(r'[ \t]*kitename[ \t]*=[ \t]*(.*)', line)
- if match:
- kite_name = match.group(1)
- continue
-
- match = re.match(r'[ \t]*kitesecret[ \t]*=[ \t]*(.*)', line)
- if match:
- kite_secret = match.group(1)
- continue
-
- print(kite_name)
- print(kite_secret)
-
-
-def subcommand_set_kite(arguments):
- """Set details of the kite"""
- kite_name = arguments.kite_name
- kite_secret = arguments.kite_secret
-
- file_path = os.path.join(CONFIG_DIR, '10_account.rc')
- file_path_new = os.path.join(CONFIG_DIR, '10_account.rc.new')
- with open(file_path, 'r') as read_file_object, \
- os.fdopen(os.open(file_path_new, os.O_WRONLY | os.O_CREAT,
- 0o400), 'w') as write_file_object:
- for line in read_file_object:
- if re.match(r'[ \t]*kitename[ \t]*=.*', line):
- write_file_object.write(
- 'kitename = {kite_name}\n'.format(kite_name=kite_name))
- continue
-
- if re.match(r'[ \t]*kitesecret[ \t]*=.*', line):
- write_file_object.write('kitesecret = {kite_secret}\n'
- .format(kite_secret=kite_secret))
- continue
-
- write_file_object.write(line)
-
- os.rename(file_path_new, file_path)
-
-
-def subcommand_get_service_status(arguments):
- """Print status of the pagekite service"""
- is_enabled = is_service_enabled(arguments.service)
- print('enabled' if is_enabled else 'disabled')
-
-
-def is_service_enabled(service):
- """Return if a service is enabled"""
- service = SERVICE_FILE_MAP[service]
- service_file_path = os.path.join(CONFIG_DIR, service['file'])
- if not os.path.isfile(service_file_path):
- return False
-
- try:
- with open(service_file_path, 'r') as file_object:
- for line in file_object:
- regex = '[ \t]*service_on[ \t]*=[ \t]*{match}'
- regex = regex.format(match=service['match'])
- if re.match(regex, line):
- return True
- except Exception:
- return False
-
- return False
-
-
-def subcommand_set_service_status(arguments):
- """Enable/disable a pagekite service"""
- enable = arguments.enable == 'enable'
- is_enabled = is_service_enabled(arguments.service)
- if enable and is_enabled:
- print('already enabled')
- return
-
- if not enable and not is_enabled:
- print('already disabled')
- return
-
- if enable:
- service_enable(arguments.service)
- print('enabled')
- else:
- service_disable(arguments.service)
- print('disabled')
-
-
-def service_enable(service_name):
- """Enable a service"""
- service = SERVICE_FILE_MAP[service_name]
- service_file_path = os.path.join(CONFIG_DIR, service['file'])
- with open(service_file_path, 'w') as file_object:
- file_object.write('''
-# Expose the local {service_name} daemon
-# File auto-generated by Plinth
-
-{line}
-'''.format(service_name=service_name, line=service['line']))
-
-
-def service_disable(service_name):
- """Disable a service"""
- service = SERVICE_FILE_MAP[service_name]
- service_file_path = os.path.join(CONFIG_DIR, service['file'])
- service_file_path_new = os.path.join(CONFIG_DIR,
- service['file'] + '.plinthbak')
- os.rename(service_file_path, service_file_path_new)
-
-
-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/actions/pagekite-configure-configobj b/actions/pagekite-configure-configobj
new file mode 100755
index 000000000..e1a9cde60
--- /dev/null
+++ b/actions/pagekite-configure-configobj
@@ -0,0 +1,352 @@
+#!/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 Plint PageKite interface
+
+Unfortunately there is no python3 package for augeas yet
+"""
+
+# Disable warning about invalid module name # pylint: disable-msg=C0103
+
+import argparse
+#import augeas
+from configobj import ConfigObj
+import os
+import subprocess
+
+#aug = augeas.Augeas()
+aug = "XX"
+
+class ConfigFileCache(dict):
+ """Cache files to not open files more than once"""
+ def __missing__(self, path):
+ self[path] = ConfigObj(path)
+ return self[path]
+
+filecache = ConfigFileCache()
+
+
+CONF_PATH = '/etc/pagekite.d'
+PATHS = {
+ 'account': os.path.join(CONF_PATH, '10_account.rc'),
+ 'frontends': os.path.join(CONF_PATH, '20_frontends.rc'),
+ 'kitesecret': os.path.join(CONF_PATH, '10_account.rc', 'kitesecret'),
+ 'abort_not_configured': os.path.join(CONF_PATH, '10_account.rc',
+ 'abort_not_configured'),
+ 'defaults': os.path.join(CONF_PATH, '20_frontends.rc', 'defaults'),
+ 'frontend': os.path.join(CONF_PATH, '20_frontends.rc', 'frontend'),
+ 'http': os.path.join(CONF_PATH, '80_httpd.rc', 'service_on'),
+ 'https': os.path.join(CONF_PATH, '443_https.rc', 'service_on'),
+ 'ssh': os.path.join(CONF_PATH, '80_sshd.rc', 'service_on'),
+}
+
+
+_CONF_PATH = '/files/etc/pagekite.d'
+_PATHS = {
+ 'kitename': os.path.join(CONF_PATH, '10_account.rc', 'kitename'),
+ 'kitesecret': os.path.join(CONF_PATH, '10_account.rc', 'kitesecret'),
+ 'abort_not_configured': os.path.join(CONF_PATH, '10_account.rc',
+ 'abort_not_configured'),
+ 'defaults': os.path.join(CONF_PATH, '20_frontends.rc', 'defaults'),
+ 'frontend': os.path.join(CONF_PATH, '20_frontends.rc', 'frontend'),
+ 'http': os.path.join(CONF_PATH, '80_httpd.rc', 'service_on'),
+ 'https': os.path.join(CONF_PATH, '443_https.rc', 'service_on'),
+ 'ssh': os.path.join(CONF_PATH, '80_sshd.rc', 'service_on'),
+}
+# service entries are tuples with [source, destination, secret]
+# this information will be used when enabling a service
+SERVICES = {
+ 'http': ['http:*.@kitename', 'localhost:80', '@kitesecret'],
+ 'https': ['https:*.@kitename', 'localhost:443', '@kitesecret'],
+ 'ssh': ['raw/22:@kitename', 'localhost:22', '@kitesecret'],
+}
+
+
+def parse_arguments():
+ """Return parsed command line arguments as dictionary"""
+ parser = argparse.ArgumentParser()
+ subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
+
+ # Start PageKite
+ subparsers.add_parser('start', help='Start PageKite service')
+
+ # Stop PageKite
+ subparsers.add_parser('stop', help='Stop PageKite service')
+
+ # Get status
+ subparsers.add_parser('get-status', help='Get whether PakeKite is enabled')
+
+ # Set status
+ set_status = subparsers.add_parser('set-status',
+ help='Enable/disable PageKite')
+ set_status.add_argument('enable', choices=['enable', 'disable'])
+
+ # Get whether default pagekite.net frontend is enabled
+ subparsers.add_parser('get-pagekitenet-frontend-status',
+ help='Get whether pagekite.net frontend is enabled')
+
+ # Enable/Disable using default pagekite.net frontend
+ subparsers.add_parser('enable-pagekitenet-frontend',
+ help='Enable using default pagekite.net frontend')
+ subparsers.add_parser('disable-pagekitenet-frontend',
+ help='Disable default pagekite.net frontend')
+
+ # Get frontend
+ subparsers.add_parser('get-frontend', help='Get pagekite frontend')
+
+ # Set frontend
+ set_frontend = subparsers.add_parser('set-frontend',
+ help='Set pagekite frontend')
+ set_frontend.add_argument('url', help='frontend url')
+
+ # Get kite details
+ subparsers.add_parser('get-kite',
+ help='Get configured kite name and secret')
+
+ # Set kite details
+ set_kite = subparsers.add_parser('set-kite',
+ help='Configure kite name and its secret')
+ set_kite.add_argument('--kite-name',
+ help='Name of the kite (eg: mybox.pagekite.me)')
+ set_kite.add_argument('--kite-secret', help='Secret for the kite')
+
+ # Get service status
+ get_service = subparsers.add_parser('get-service-status',
+ help='Get whether service is enabled')
+ get_service.add_argument('service', choices=['http', 'ssh'])
+
+ # Set service status
+ set_service = subparsers.add_parser('set-service-status',
+ help='Enable/disable a service')
+ set_service.add_argument('service', choices=['http', 'ssh'])
+ set_service.add_argument('enable', choices=['enable', 'disable'])
+
+ return parser.parse_args()
+
+
+def subcommand_start(_):
+ """Start PageKite service"""
+ status = subprocess.call(['service', 'pagekite', 'start'])
+ if status:
+ raise Exception('Unable to start PageKite server')
+
+
+def subcommand_stop(_):
+ """Stop PageKite service"""
+ status = subprocess.call(['service', 'pagekite', 'stop'])
+ if status:
+ raise Exception('Unable to stop PageKite server')
+
+
+def subcommand_get_status(_):
+ """Print status of the pagekite service"""
+ is_enabled = is_pagekite_enabled()
+ print('enabled' if is_enabled else 'disabled')
+
+
+def is_pagekite_enabled():
+ conf = filecache[PATHS['account']]
+ return 'abort_not_configured' not in conf
+
+
+def subcommand_set_status(arguments):
+ """Enable/disable the pagekite service"""
+ enable = arguments.enable == 'enable'
+ is_enabled = is_pagekite_enabled()
+ if enable and is_enabled:
+ print('already enabled')
+ return
+
+ if not enable and not is_enabled:
+ print('already disabled')
+ return
+
+ if enable:
+ pagekite_enable()
+ print('enabled')
+ else:
+ pagekite_disable()
+ print('disabled')
+
+
+def subcommand_get_frontend(_):
+ """Get pagekite frontend url"""
+ import ipdb; ipdb.set_trace()
+ conf = filecache[PATHS['frontends']]
+ url = conf['frontend']
+ #url = aug.get(PATHS['frontend'])
+ print(url if url else "")
+
+
+def subcommand_set_frontend(arguments):
+ """Set pagekite frontend url and disable default pagekite.net frontend"""
+ aug.remove(PATHS['defaults'])
+ aug.set(PATHS['frontend'], arguments.url)
+ aug.save()
+
+
+def subcommand_get_pagekitenet_frontend_status(_):
+ match = aug.match(PATHS['defaults'])
+ print("enabled" if match else "disabled")
+
+
+def subcommand_enable_pagekitenet_frontend(_):
+ """Enable using default pageket.net frontend
+
+ This disables any other frontends.
+ """
+ aug.set(PATHS['defaults'], '')
+ aug.remove(PATHS['frontend'])
+ aug.save()
+ print("enabled")
+
+
+def subcommand_disable_pagekitenet_frontend(_):
+ aug.remove(PATHS['defaults'])
+ aug.save()
+ print("disabled")
+
+
+def pagekite_enable():
+ """Enable the pagekite daemon"""
+ aug.remove(PATHS['abort_not_configured'])
+ aug.save()
+
+
+def pagekite_disable():
+ """Disable the pagekite daemon"""
+ aug.set(PATHS['abort_not_configured'], '')
+ aug.save()
+
+
+def subcommand_get_service_status(arguments):
+ """Print status of the pagekite service"""
+ is_enabled = bool(get_enabled_service_paths(arguments.service))
+ print('enabled' if is_enabled else 'disabled')
+
+
+def subcommand_get_kite(_):
+ """Print details of the currently configured kite"""
+ kitename = aug.get(PATHS['kitename'])
+ kitesecret = aug.get(PATHS['kitesecret'])
+ print(kitename if kitename else '')
+ print(kitesecret if kitesecret else '')
+
+
+def subcommand_set_kite(arguments):
+ """Set details of the kite"""
+ aug.set(PATHS['kitename'], arguments.kite_name)
+ aug.set(PATHS['kitesecret'], arguments.kite_secret)
+ aug.save()
+
+
+def subcommand_set_service_status(arguments):
+ """Enable/disable a pagekite service"""
+ enable = arguments.enable == 'enable'
+ is_enabled = bool(get_enabled_service_paths(arguments.service))
+ if enable and is_enabled:
+ print('already enabled')
+ return
+
+ if not enable and not is_enabled:
+ print('already disabled')
+ return
+
+ if enable:
+ service_enable(arguments.service)
+ print('enabled')
+ else:
+ service_disable(arguments.service)
+ print('disabled')
+
+
+def service_enable(service):
+ """Enable a service"""
+ position = len(aug.match(PATHS[service])) + 1
+ root = os.path.join(PATHS[service], str(position))
+ set_service(root, *SERVICES[service])
+
+
+def set_service(root, source, destination, secret=None):
+ """Set service_on with the given augeas root (path)"""
+ aug.set(os.path.join(root, 'source'), source)
+ aug.set(os.path.join(root, 'destination'), destination)
+ if secret is not None:
+ aug.set(os.path.join(root, 'secret'), secret)
+ aug.save()
+
+
+def get_enabled_service_paths(service):
+ """Search all service_on lines of the given protocol"""
+ paths = []
+ for i, match in enumerate(aug.match(PATHS[service]), start=1):
+ service_path = os.path.join(match, str(i))
+ source = aug.get(os.path.join(service_path, 'source'))
+ if service == "ssh" and "raw/22" in source:
+ paths.append(service_path)
+ elif service == "http" and service in source:
+ paths.append(service_path)
+ return paths
+
+
+def service_disable(service):
+ """Disable a service"""
+ # TODO: saving config files after changing/deleting config entries with
+ # augeas fails with an IOError.
+ # Saving the file after removing this path does not work:
+ # /files/etc/pagekite.d/80_httpd.rc/service_on/1
+ # Saving the file after removing this path works:
+ # /files/etc/pagekite.d/80_httpd.rc/service_on
+ #
+ # This could be an augeas bug, see
+ # http://permalink.gmane.org/gmane.comp.sysutils.augeas.devel/5419
+ # Once this problem is solved, all you should need in this function is:
+ #
+ # paths = get_enabled_service_paths(service)
+ # [aug.remove(path) for path in paths]
+ # aug.save()
+
+ if service == "ssh":
+ path = '/etc/pagekite.d/80_sshd.rc'
+ activated_pattern = 'raw/22'
+ elif service == "http":
+ path = '/etc/pagekite.d/80_httpd.rc'
+ activated_pattern = 'http'
+ with open(path, 'r') as file:
+ lines = file.readlines()
+ for i, line in enumerate(lines):
+ if line.startswith('service_on') and activated_pattern in line:
+ lines[i] = "#%s" % line
+ break
+ with open(path, 'w') as file:
+ file.writelines(lines)
+
+
+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/actions/pagekite_common.py b/actions/pagekite_common.py
new file mode 100644
index 000000000..df552041e
--- /dev/null
+++ b/actions/pagekite_common.py
@@ -0,0 +1,52 @@
+#!/usr/bin/python2
+# -*- 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 .
+#
+
+"""
+The variables/functions defined here are used by both the action script
+and the plinth pagekite module.
+
+Currently that's functionality for converting pagekite service_on strings like
+ "http:@kitename:localhost:80:@kitestring"
+into parameter dictionaries and the other way round.
+"""
+
+SERVICE_PARAMS = ['protocol', 'kitename', 'backend_host', 'backend_port',
+ 'secret']
+
+
+def construct_params(string):
+ """ Convert a parameter string into a params dictionary"""
+ try:
+ params = dict(zip(SERVICE_PARAMS, string.split(':')))
+ except:
+ msg = """params are expected to be a ':'-separated string
+ containing values for: %s , for example:\n"--params
+ http:@kitename:localhost:8000:@kitesecret"
+ """
+ raise ValueError(msg % ", ".join(SERVICE_PARAMS))
+ return params
+
+
+def deconstruct_params(params):
+ """ Convert params into a ":"-separated parameter string """
+ try:
+ paramstring = ":".join([params[param] for param in SERVICE_PARAMS])
+ except KeyError:
+ raise ValueError("Could not parse params: %s " % params)
+ return paramstring
diff --git a/data/usr/share/augeas/lenses/pagekite.aug b/data/usr/share/augeas/lenses/pagekite.aug
new file mode 100644
index 000000000..83ced80bd
--- /dev/null
+++ b/data/usr/share/augeas/lenses/pagekite.aug
@@ -0,0 +1,79 @@
+(*
+Module: Pagekite
+ Parses /etc/pagekite.d/
+
+Author: Michael Pimmer
+
+About: License
+ This file is licenced under the LGPL v2+, like the rest of Augeas.
+*)
+
+module Pagekite =
+autoload xfm
+
+(* View: lns *)
+
+(* Variables *)
+let equals = del /[ \t]*=[ \t]*/ "="
+let neg2 = /[^# \n\t]+/
+let neg3 = /[^# \:\n\t]+/
+let eol = del /\n/ "\n"
+(* Match everything from here to eol, cropping whitespace at both ends *)
+let to_eol = /[^ \t\n](.*[^ \t\n])?/
+
+(* A key followed by comma-separated values
+ k: name of the key
+ key_sep: separator between key and values
+ value_sep: separator between values
+ sto: store for values
+*)
+let key_csv_line (k:string) (key_sep:lens) (value_sep:lens) (sto:lens) =
+ [ key k . key_sep . [ seq k . sto ] .
+ [ seq k . value_sep . sto ]* . Util.eol ]
+
+(* entries for pagekite.d/10_account.rc *)
+let domain = [ key "domain" . equals . store neg2 . Util.comment_or_eol ]
+let frontend = Build.key_value_line ("frontend" | "frontends")
+ equals (store Rx.neg1)
+let host = Build.key_value_line "host" equals (store Rx.ip)
+let ports = key_csv_line "ports" equals Sep.comma (store Rx.integer)
+let protos = key_csv_line "protos" equals Sep.comma (store Rx.word)
+
+(* entries for pagekite.d/20_frontends.rc *)
+let kitesecret = Build.key_value_line "kitesecret" equals (store Rx.space_in)
+let kv_frontend = Build.key_value_line ( "kitename" | "fe_certname" |
+ "ca_certs" | "tls_endpoint" )
+ equals (store Rx.neg1)
+
+(* entries for services like 80_httpd.rc *)
+let service_colon = del /[ \t]*:[ \t]*/ " : "
+let service_on = [ key "service_on" . [ seq "service_on" . equals .
+ [ label "protocol" . store neg3 ] . service_colon .
+ [ label "kitename" . (store neg3) ] . service_colon .
+ [ label "backend_host" . (store neg3) ] . service_colon .
+ [ label "backend_port" . (store neg3) ] . service_colon . (
+ [ label "secret" . (store Rx.no_spaces) . Util.eol ] | eol
+ ) ] ]
+
+let service_cfg = [ key "service_cfg" . equals . store to_eol . eol ]
+
+let flags = ( "defaults" | "isfrontend" | "abort_not_configured" | "insecure" )
+
+let entries = Build.flag_line flags
+ | domain
+ | frontend
+ | host
+ | ports
+ | protos
+ | kv_frontend
+ | kitesecret
+ | service_on
+ | service_cfg
+
+let lns = ( entries | Util.empty | Util.comment )*
+
+(* View: filter *)
+let filter = incl "/etc/pagekite.d/*.rc"
+ . Util.stdexcl
+
+let xfm = transform lns filter
diff --git a/data/usr/share/augeas/lenses/tests/test_pagekite.aug b/data/usr/share/augeas/lenses/tests/test_pagekite.aug
new file mode 100644
index 000000000..d32bc4f48
--- /dev/null
+++ b/data/usr/share/augeas/lenses/tests/test_pagekite.aug
@@ -0,0 +1,111 @@
+module Test_Pagekite =
+
+let conf1 = "# Use the pagekite.net service defaults.
+defaults
+"
+test Pagekite.lns get conf1 =
+ { "#comment" = "Use the pagekite.net service defaults." }
+ { "defaults" }
+
+
+let conf2 ="
+frontends = pagekite.freedombox.me
+ports=80,81
+"
+test Pagekite.lns get conf2 =
+ { }
+ { "frontends" = "pagekite.freedombox.me" }
+ { "ports"
+ { "1" = "80" }
+ { "2" = "81" } }
+
+
+let conf3 = "frontend=pagekite.freedombox.me
+host=192.168.0.3
+"
+test Pagekite.lns get conf3 =
+ { "frontend" = "pagekite.freedombox.me" }
+ { "host" = "192.168.0.3" }
+
+
+let conf4 = "isfrontend
+ports=80,443
+protos=http,https
+domain=http,https:*.your.domain:MakeUpAPasswordHere
+"
+test Pagekite.lns get conf4 =
+ { "isfrontend" }
+ { "ports"
+ { "1" = "80" }
+ { "2" = "443" } }
+ { "protos"
+ { "1" = "http" }
+ { "2" = "https" } }
+ { "domain" = "http,https:*.your.domain:MakeUpAPasswordHere" }
+
+let conf_account = "kitename = my.freedombox.me
+kitesecret = 0420
+# Delete this line!
+abort_not_configured
+"
+test Pagekite.lns get conf_account =
+ { "kitename" = "my.freedombox.me" }
+ { "kitesecret" = "0420" }
+ { "#comment" = "Delete this line!" }
+ { "abort_not_configured" }
+
+
+let conf_service = "
+service_on = raw/22:@kitename : localhost:22 : @kitesecret
+service_on=http:192.168.0.1:127.0.0.1:80:
+service_on=https:yourhostname,fqdn:127.0.0.1:443:
+"
+test Pagekite.lns get conf_service =
+ { }
+ { "service_on"
+ { "1"
+ { "protocol" = "raw/22" }
+ { "kitename" = "@kitename" }
+ { "backend_host" = "localhost" }
+ { "backend_port" = "22" }
+ { "secret" = "@kitesecret" }
+ }
+ }
+ { "service_on"
+ { "2"
+ { "protocol" = "http" }
+ { "kitename" = "192.168.0.1" }
+ { "backend_host" = "127.0.0.1" }
+ { "backend_port" = "80" }
+ }
+ }
+ { "service_on"
+ { "3"
+ { "protocol" = "https" }
+ { "kitename" = "yourhostname,fqdn" }
+ { "backend_host" = "127.0.0.1" }
+ { "backend_port" = "443" }
+ }
+ }
+
+
+let conf_encryption = "
+frontend=frontend.your.domain:443
+fe_certname=frontend.your/domain
+ca_certs=/etc/pagekite.d/site-cert.pem
+tls_endpoint=frontend.your.domain:/path/to/frontend.pem
+"
+test Pagekite.lns get conf_encryption =
+ { }
+ { "frontend" = "frontend.your.domain:443" }
+ { "fe_certname" = "frontend.your/domain" }
+ { "ca_certs" = "/etc/pagekite.d/site-cert.pem" }
+ { "tls_endpoint" = "frontend.your.domain:/path/to/frontend.pem" }
+
+
+let conf_service_cfg = "insecure
+service_cfg = KITENAME.pagekite.me/80 : insecure : True
+"
+test Pagekite.lns get conf_service_cfg =
+ { "insecure" }
+ { "service_cfg" = "KITENAME.pagekite.me/80 : insecure : True" }
diff --git a/plinth/modules/pagekite/pagekite.py b/plinth/modules/pagekite/pagekite.py
index 132d7fff7..af2b34bc0 100644
--- a/plinth/modules/pagekite/pagekite.py
+++ b/plinth/modules/pagekite/pagekite.py
@@ -73,9 +73,9 @@ class ConfigureForm(forms.Form): # pylint: disable-msg=W0232
server = forms.CharField(
label=_('Server'), required=False,
- help_text=_('Currently only pagekite.net server is supported'),
- widget=forms.TextInput(attrs={'placeholder': 'pagekite.net',
- 'disabled': 'disabled'}))
+ 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'),
@@ -143,6 +143,15 @@ def get_status():
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'):
@@ -173,6 +182,14 @@ def _apply_changes(request, old_status, new_status):
'--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']:
diff --git a/plinth/modules/pagekite/templates/pagekite_configure.html b/plinth/modules/pagekite/templates/pagekite_configure.html
index 0d737ba9b..0ef209ee7 100644
--- a/plinth/modules/pagekite/templates/pagekite_configure.html
+++ b/plinth/modules/pagekite/templates/pagekite_configure.html
@@ -30,13 +30,13 @@
PageKite Account
- {% include 'bootstrapform/field.html' with field=form.server %}
- {% include 'bootstrapform/field.html' with field=form.kite_name %}
- {% include 'bootstrapform/field.html' with field=form.kite_secret %}
+ {{ form.server|bootstrap_horizontal }}
+ {{ form.kite_name|bootstrap_horizontal }}
+ {{ form.kite_secret|bootstrap_horizontal }}
Services
- {% include 'bootstrapform/field.html' with field=form.http_enabled %}
- {% include 'bootstrapform/field.html' with field=form.ssh_enabled %}
+ {{ form.http_enabled|bootstrap_horizontal }}
+ {{ form.ssh_enabled|bootstrap_horizontal }}
diff --git a/setup.py b/setup.py
index 537b438f4..f71dcbc81 100755
--- a/setup.py
+++ b/setup.py
@@ -140,6 +140,10 @@ setuptools.setup(
glob.glob(os.path.join('actions', '*'))),
('/usr/share/man/man1', ['doc/plinth.1']),
('/etc/plinth', ['data/etc/plinth/plinth.config']),
+ ('/usr/share/augeas/lenses',
+ ['data/usr/share/augeas/lenses/pagekite.aug']),
+ ('/usr/share/augeas/lenses/tests',
+ ['data/usr/share/augeas/lenses/tests/test_pagekite.aug']),
('/etc/plinth/modules-enabled',
glob.glob(os.path.join('data/etc/plinth/modules-enabled',
'*')))],
From 1fc0064fd0e3217bfe929dbde66d2536b0b22cd2 Mon Sep 17 00:00:00 2001
From: fonfon
Date: Wed, 14 Jan 2015 19:14:27 +0000
Subject: [PATCH 02/21] refactoring pagekite: configuration form works
splitting the services to a separate page is not yet finished
---
actions/pagekite | 23 +--
actions/pagekite_common.py | 52 ------
actions/pagekite_util.py | 104 +++++++++++
plinth/modules/pagekite/__init__.py | 10 +-
plinth/modules/pagekite/forms.py | 169 ++++++++++++++++++
plinth/modules/pagekite/pagekite.py | 37 +---
.../templates/pagekite_configure.html | 13 +-
.../templates/pagekite_custom_services.html | 95 ++++++++++
.../templates/pagekite_default_services.html | 55 ++++++
.../templates/pagekite_introduction.html | 8 +-
plinth/modules/pagekite/urls.py | 13 +-
plinth/modules/pagekite/util.py | 141 +++++++++++++++
plinth/modules/pagekite/views.py | 123 +++++++++++++
plinth/templatetags/plinth_extras.py | 19 ++
plinth/tests/test_pagekite_actions.py | 79 ++++++++
15 files changed, 818 insertions(+), 123 deletions(-)
delete mode 100644 actions/pagekite_common.py
create mode 100644 actions/pagekite_util.py
create mode 100644 plinth/modules/pagekite/forms.py
create mode 100644 plinth/modules/pagekite/templates/pagekite_custom_services.html
create mode 100644 plinth/modules/pagekite/templates/pagekite_default_services.html
create mode 100644 plinth/modules/pagekite/util.py
create mode 100644 plinth/modules/pagekite/views.py
create mode 100644 plinth/tests/test_pagekite_actions.py
diff --git a/actions/pagekite b/actions/pagekite
index 561a31427..34bbaadaa 100755
--- a/actions/pagekite
+++ b/actions/pagekite
@@ -28,11 +28,11 @@ import augeas
import os
import subprocess
-from pagekite_common import SERVICE_PARAMS, construct_params
+from pagekite_util import SERVICE_PARAMS, construct_params, \
+ deconstruct_params, get_augeas_servicefile_path, CONF_PATH
aug = augeas.Augeas()
-CONF_PATH = '/files/etc/pagekite.d'
PATHS = {
'service_on': os.path.join(CONF_PATH, '*', 'service_on', '*'),
'kitename': os.path.join(CONF_PATH, '10_account.rc', 'kitename'),
@@ -226,7 +226,7 @@ def subcommand_add_service(arguments):
# so do it manually here
path = convert_augeas_path_to_filepath(root)
with open(path, 'a') as servicefile:
- line = "service_on = %s" % arguments.params
+ line = "service_on = %s" % deconstruct_params(params)
servicefile.write(line)
@@ -251,23 +251,6 @@ def get_new_service_path(protocol):
return os.path.join(root, str(new_index))
-def get_augeas_servicefile_path(protocol):
- """Get the augeas path where a service for a protocol should be stored"""
- if protocol == 'http':
- relpath = '80_httpd.rc'
- elif protocol == 'https':
- relpath = '443_https.rc'
- elif protocol == 'raw/22':
- relpath = '22_ssh.rc'
- elif protocol.startswith('raw'):
- port = protocol.split('/')[1]
- relpath = '%s.rc' % port
- else:
- raise ValueError('Unsupported protocol: %s' % protocol)
-
- return os.path.join(CONF_PATH, relpath, 'service_on')
-
-
def subcommand_get_kite(_):
"""Print details of the currently configured kite"""
kitename = aug.get(PATHS['kitename'])
diff --git a/actions/pagekite_common.py b/actions/pagekite_common.py
deleted file mode 100644
index df552041e..000000000
--- a/actions/pagekite_common.py
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/usr/bin/python2
-# -*- 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 .
-#
-
-"""
-The variables/functions defined here are used by both the action script
-and the plinth pagekite module.
-
-Currently that's functionality for converting pagekite service_on strings like
- "http:@kitename:localhost:80:@kitestring"
-into parameter dictionaries and the other way round.
-"""
-
-SERVICE_PARAMS = ['protocol', 'kitename', 'backend_host', 'backend_port',
- 'secret']
-
-
-def construct_params(string):
- """ Convert a parameter string into a params dictionary"""
- try:
- params = dict(zip(SERVICE_PARAMS, string.split(':')))
- except:
- msg = """params are expected to be a ':'-separated string
- containing values for: %s , for example:\n"--params
- http:@kitename:localhost:8000:@kitesecret"
- """
- raise ValueError(msg % ", ".join(SERVICE_PARAMS))
- return params
-
-
-def deconstruct_params(params):
- """ Convert params into a ":"-separated parameter string """
- try:
- paramstring = ":".join([params[param] for param in SERVICE_PARAMS])
- except KeyError:
- raise ValueError("Could not parse params: %s " % params)
- return paramstring
diff --git a/actions/pagekite_util.py b/actions/pagekite_util.py
new file mode 100644
index 000000000..9a258e7f9
--- /dev/null
+++ b/actions/pagekite_util.py
@@ -0,0 +1,104 @@
+#!/usr/bin/python2
+# -*- 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 .
+#
+
+"""
+The variables/functions defined here are used by both the action script
+and the plinth pagekite module.
+
+For example the functionality to convert pagekite service_on strings like
+ "http:@kitename:localhost:80:@kitestring"
+into parameter dictionaries and the other way round. And functions that we want
+to be covered by tests.
+"""
+# ATTENTION: This file has to be both python2 and python3 compatible
+
+import os
+import shlex
+CONF_PATH = '/files/etc/pagekite.d'
+
+SERVICE_PARAMS = ['protocol', 'kitename', 'backend_host', 'backend_port',
+ 'secret']
+
+
+def construct_params(string):
+ """ Convert a parameter string into a params dictionary"""
+ # 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'.
+
+ # TODO: use shlex only once augeas-python supports python3
+ if hasattr(shlex, 'quote'):
+ quotefunction = shlex.quote
+ else:
+ import pipes
+ quotefunction = pipes.quote
+
+ if string.startswith("'") and string.endswith("'"):
+ unquoted_string = string[1:-1]
+ error_msg = "The parameters contain suspicious characters: %s "
+ if '*.@kitename' in string:
+ unquoted_test_string = unquoted_string.replace('*.@kitename', '')
+ if unquoted_test_string == quotefunction(unquoted_test_string):
+ # no other malicious characters found, use the unquoted string
+ string = unquoted_string
+ else:
+ raise RuntimeError(error_msg % string)
+ else:
+ raise RuntimeError(error_msg % string)
+
+ try:
+ params = dict(zip(SERVICE_PARAMS, string.split(':')))
+ except:
+ 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 deconstruct_params(params):
+ """ Convert params into a ":"-separated parameter string """
+ try:
+ paramstring = ":".join([str(params[param]) for param in
+ SERVICE_PARAMS])
+ except KeyError:
+ raise ValueError("Could not parse params: %s " % params)
+ return paramstring
+
+
+def get_augeas_servicefile_path(protocol):
+ """Get the augeas path where a service for a protocol should be stored"""
+ if not protocol.startswith(("http", "https", "raw")):
+ raise ValueError('Unsupported protocol: %s' % protocol)
+
+ try:
+ _protocol, port = protocol.split('/')
+ except ValueError:
+ if protocol == 'http':
+ relpath = '80_http.rc'
+ elif protocol == 'https':
+ relpath = '443_https.rc'
+ else:
+ raise ValueError('Unsupported protocol: %s' % protocol)
+ else:
+ relpath = '%s_%s.rc' % (port, _protocol)
+
+ return os.path.join(CONF_PATH, relpath, 'service_on')
diff --git a/plinth/modules/pagekite/__init__.py b/plinth/modules/pagekite/__init__.py
index 0d836a04f..b9ad5fb14 100644
--- a/plinth/modules/pagekite/__init__.py
+++ b/plinth/modules/pagekite/__init__.py
@@ -19,9 +19,17 @@
Plinth module to configure PageKite
"""
+from gettext import gettext as _
+from plinth import cfg
from . import pagekite
-from .pagekite import init
__all__ = ['pagekite', 'init']
depends = ['plinth.modules.apps']
+
+
+def init():
+ """Intialize the PageKite module"""
+ menu = cfg.main_menu.get('apps:index')
+ menu.add_urlname(_('Public Visibility (PageKite)'),
+ 'glyphicon-flag', 'pagekite:index', 50)
diff --git a/plinth/modules/pagekite/forms.py b/plinth/modules/pagekite/forms.py
new file mode 100644
index 000000000..f19c59a92
--- /dev/null
+++ b/plinth/modules/pagekite/forms.py
@@ -0,0 +1,169 @@
+#
+# 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 gettext import gettext as _
+import logging
+
+from django import forms
+from django.contrib import messages
+from django.core import validators
+
+from actions.pagekite_util import deconstruct_params
+from .util import PREDEFINED_SERVICES, _run, get_kite_details, KITE_NAME, \
+ KITE_SECRET, BACKEND_HOST
+
+LOGGER = logging.getLogger(__name__)
+
+
+class TrimmedCharField(forms.CharField):
+ """Trim the contents of a CharField"""
+ def clean(self, value):
+ """Clean and validate the field value"""
+ if value:
+ value = value.strip()
+
+ return super(TrimmedCharField, self).clean(value)
+
+
+class ConfigurationForm(forms.Form):
+ """Configure PageKite credentials and frontend"""
+
+ 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'))
+
+ def save(self, request):
+ old = self.initial
+ new = self.cleaned_data
+ LOGGER.info('New status is - %s', new)
+
+ if old != new:
+ _run(['stop'])
+
+ if old['enabled'] != new['enabled']:
+ if new['enabled']:
+ _run(['enable'])
+ messages.success(request, _('PageKite enabled'))
+ else:
+ _run(['disable'])
+ messages.success(request, _('PageKite disabled'))
+
+ if old['kite_name'] != new['kite_name'] or \
+ old['kite_secret'] != new['kite_secret']:
+ _run(['set-kite', '--kite-name', new['kite_name'],
+ '--kite-secret', new['kite_secret']])
+ messages.success(request, _('Kite details set'))
+
+ if old['server'] != new['server']:
+ server = new['server']
+ if server in ('defaults', 'default', 'pagekite.net'):
+ _run(['enable-pagekitenet-frontend'])
+ else:
+ _run(['set-frontend', server])
+ messages.success(request, _('Pagekite server set'))
+
+ if old != new:
+ _run(['start'])
+
+
+class DefaultServiceForm(forms.Form):
+ """Constructs a form out of PREDEFINED_SERVICES"""
+
+ def __init__(self, *args, **kwargs):
+ """Add the fields from PREDEFINED_SERVICES"""
+ super(DefaultServiceForm, self).__init__(*args, **kwargs)
+ kite = get_kite_details()
+ for name, service in PREDEFINED_SERVICES.items():
+ if name in ('http', 'https'):
+ help_text = service['help_text'].format(kite['kite_name'])
+ else:
+ help_text = service['help_text']
+ self.fields[name] = forms.BooleanField(label=service['label'],
+ help_text=help_text,
+ required=False)
+
+ def save(self, request):
+ formdata = self.cleaned_data
+ for service in PREDEFINED_SERVICES.keys():
+ if self.initial[service] != formdata[service]:
+ params = PREDEFINED_SERVICES[service]['params']
+ param_line = deconstruct_params(params)
+ if formdata[service]:
+ _run(['add-service', '--params', param_line])
+ messages.success(request, _('Service enabled: {service}')
+ .format(service=service))
+ else:
+ _run(['remove-service', '--params', param_line])
+ messages.success(request, _('Service disabled: {service}')
+ .format(service=service))
+
+
+class CustomServiceForm(forms.Form):
+ """Form to add/delete a custom service"""
+ choices = [("http", "http"), ("https", "https"), ("raw", "raw")]
+ protocol = forms.ChoiceField(choices=choices, label="protocol")
+ frontend_port = forms.IntegerField(min_value=0, max_value=65535,
+ label="external (frontend) port")
+ backend_port = forms.IntegerField(min_value=0, max_value=65535,
+ label="internal (freedombox) port")
+ subdomains = forms.BooleanField(label="Enable Subdomains", required=False)
+
+ def prepare_user_input_for_storage(self, params):
+ """prepare the user input for being stored via the action"""
+ # set kitename and kitesecret if not already set
+ if 'kitename' not in params:
+ if 'subdomains' in params and params['subdomains']:
+ params['kitename'] = "*.%s" % KITE_NAME
+ else:
+ params['kitename'] = KITE_NAME
+ if 'secret' not in params:
+ params['secret'] = KITE_SECRET
+
+ # condense protocol and frontend_port to one entry (protocol)
+ if 'frontend_port' in params:
+ if str(params['frontend_port']) not in params['protocol']:
+ params['protocol'] = "%s/%s" % (params['protocol'],
+ params['frontend_port'])
+ if 'backend_host' not in params:
+ params['backend_host'] = BACKEND_HOST
+
+ return deconstruct_params(params)
+
+ def save(self, request):
+ params = self.prepare_user_input_for_storage(self.cleaned_data)
+ _run(['add-service', '--params', params])
+
+ def delete(self, request):
+ params = self.prepare_user_input_for_storage(self.cleaned_data)
+ _run(['remove-service', '--params', params])
diff --git a/plinth/modules/pagekite/pagekite.py b/plinth/modules/pagekite/pagekite.py
index af2b34bc0..a22c961d9 100644
--- a/plinth/modules/pagekite/pagekite.py
+++ b/plinth/modules/pagekite/pagekite.py
@@ -19,49 +19,16 @@
Plinth module for configuring PageKite service
"""
-from django import forms
-from django.contrib import messages
-from django.contrib.auth.decorators import login_required
-from django.core import validators
-from django.core.urlresolvers import reverse_lazy
-from django.template.response import TemplateResponse
-from gettext import gettext as _
-import logging
-
-from plinth import actions
-from plinth import cfg
-from plinth import package
-LOGGER = logging.getLogger(__name__)
-
-subsubmenu = [{'url': reverse_lazy('pagekite:index'),
- 'text': _('About PageKite')},
- {'url': reverse_lazy('pagekite:configure'),
- 'text': _('Configure PageKite')}]
-def init():
- """Intialize the PageKite module"""
- menu = cfg.main_menu.get('apps:index')
- menu.add_urlname(_('Public Visibility (PageKite)'),
- 'glyphicon-flag', 'pagekite:index', 50)
-@login_required
-def index(request):
- """Serve introduction page"""
- return TemplateResponse(request, 'pagekite_introduction.html',
- {'title': _('Public Visibility (PageKite)'),
- 'subsubmenu': subsubmenu})
-class TrimmedCharField(forms.CharField):
- """Trim the contents of a CharField"""
- def clean(self, value):
- """Clean and validate the field value"""
- if value:
- value = value.strip()
+
+
return super(TrimmedCharField, self).clean(value)
diff --git a/plinth/modules/pagekite/templates/pagekite_configure.html b/plinth/modules/pagekite/templates/pagekite_configure.html
index 0ef209ee7..07c5c6066 100644
--- a/plinth/modules/pagekite/templates/pagekite_configure.html
+++ b/plinth/modules/pagekite/templates/pagekite_configure.html
@@ -27,20 +27,15 @@
{% include 'bootstrapform/field.html' with field=form.enabled %}
-
-
-
-
+
{% endblock %}
@@ -52,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_custom_services.html b/plinth/modules/pagekite/templates/pagekite_custom_services.html
new file mode 100644
index 000000000..d865d9d40
--- /dev/null
+++ b/plinth/modules/pagekite/templates/pagekite_custom_services.html
@@ -0,0 +1,95 @@
+{% 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 plinth_extras %}
+
+{% block page_head %}
+
+{% endblock %}
+
+{% block content %}
+
+
Custom Services
+
+
+
+
+
Existing custom services
+ {% if not custom_services %}
+ You don't have any Custom Services enabled
+ {% endif %}
+
+ {% for service in custom_services %}
+ {% create_pagekite_service_link service kite_name as service_link %}
+
+
+{% endblock %}
+
+
diff --git a/plinth/modules/pagekite/templates/pagekite_default_services.html b/plinth/modules/pagekite/templates/pagekite_default_services.html
new file mode 100644
index 000000000..906cfb4b6
--- /dev/null
+++ b/plinth/modules/pagekite/templates/pagekite_default_services.html
@@ -0,0 +1,55 @@
+{% 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 %}
+
+{% block page_head %}
+
+{% endblock %}
+
+{% block content %}
+
+
Default Services
+
+
Exposing services makes them accessible and attackable from the evil
+ internet. Be cautious!
+
+
+
+{% endblock %}
+
diff --git a/plinth/modules/pagekite/templates/pagekite_introduction.html b/plinth/modules/pagekite/templates/pagekite_introduction.html
index 211ff20c7..23c7fe99c 100644
--- a/plinth/modules/pagekite/templates/pagekite_introduction.html
+++ b/plinth/modules/pagekite/templates/pagekite_introduction.html
@@ -42,10 +42,10 @@ 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. An intermediary
-server with direct Internet access is required. Currently, only
-pagekite.net server is supported and you will need an account
-there. In future, it might be possible to use your buddy's
+exposing web server and SSH server are supported. You can use any
+server that offers a pagekite service, for example
+pagekite.net.
+In future, it might be possible to use your buddy's
{{ cfg.box_name }} for this.
diff --git a/plinth/modules/pagekite/urls.py b/plinth/modules/pagekite/urls.py
index 7fa6c9918..ebc457c63 100644
--- a/plinth/modules/pagekite/urls.py
+++ b/plinth/modules/pagekite/urls.py
@@ -20,10 +20,19 @@ URLs for the PageKite module
"""
from django.conf.urls import patterns, url
+from .views import DefaultServiceView, CustomServiceView, ConfigurationView, \
+ DeleteServiceView
urlpatterns = patterns( # pylint: disable-msg=C0103
- 'plinth.modules.pagekite.pagekite',
+ 'plinth.modules.pagekite.views',
url(r'^apps/pagekite/$', 'index', name='index'),
- url(r'^apps/pagekite/configure/$', 'configure', name='configure'),
+ url(r'^apps/pagekite/configure/$', ConfigurationView.as_view(),
+ name='configure'),
+ url(r'^apps/pagekite/services/default$', DefaultServiceView.as_view(),
+ name='default-services'),
+ url(r'^apps/pagekite/services/custom$', CustomServiceView.as_view(),
+ name='custom-services'),
+ url(r'^apps/pagekite/services/custom/delete$', DeleteServiceView.as_view(),
+ name='delete-custom-service'),
)
diff --git a/plinth/modules/pagekite/util.py b/plinth/modules/pagekite/util.py
new file mode 100644
index 000000000..a40ba106f
--- /dev/null
+++ b/plinth/modules/pagekite/util.py
@@ -0,0 +1,141 @@
+#
+# 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 gettext import gettext as _
+import logging
+
+from actions.pagekite_util import construct_params
+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.
+KITE_NAME = '@kitename'
+KITE_SECRET = '@kitesecret'
+BACKEND_HOST = 'localhost'
+# predefined services show up in the PredefinedServiceForm as checkbox
+PREDEFINED_SERVICES = {
+ 'http': {
+ 'params': {'protocol': 'http',
+ 'kitename': KITE_NAME,
+ 'backend_port': '80',
+ 'backend_host': BACKEND_HOST,
+ 'secret': KITE_SECRET},
+ 'label': _("Web Server (HTTP)"),
+ 'help_text': _("Site will be available at "
+ "http://{0}"),
+ },
+ 'https': {
+ 'params': {'protocol': 'https',
+ 'kitename': KITE_NAME,
+ 'backend_port': '443',
+ 'backend_host': BACKEND_HOST,
+ 'secret': KITE_SECRET},
+ 'label': _("Web Server (HTTPS)"),
+ 'help_text': _("Site will be available at "
+ "https://{0}"),
+ },
+ 'ssh': {
+ 'params': {'protocol': 'raw/22',
+ 'kitename': KITE_NAME,
+ 'backend_port': '22',
+ 'backend_host': BACKEND_HOST,
+ 'secret': KITE_SECRET},
+ 'label': _("Secure Shell (SSH)"),
+ 'help_text': _("See SSH client setup "
+ "instructions")
+ },
+}
+
+
+def get_kite_details():
+ output = _run(['get-kite'])
+ kite_details = output.split()
+ return {'kite_name': kite_details[0],
+ 'kite_secret': kite_details[1]}
+
+
+def prepare_params_for_display(params):
+ """Add extra information to display a custom service:
+
+ - protocol is split into 'protocol' and 'frontend_port'
+ - we try to detect whether 'subdomains' are supported (as boolean)
+ """
+ protocol = params['protocol']
+ if '/' in protocol:
+ params['protocol'], params['frontend_port'] = protocol.split('/')
+ params['subdomains'] = params['kitename'].startswith('*.')
+ return params
+
+
+def get_pagekite_config():
+ """
+ Return the current PageKite configuration by executing various actions.
+ """
+ status = {}
+
+ # PageKite service enabled/disabled
+ output = _run(['is-enabled'])
+ status['enabled'] = (output.split()[0] == 'yes')
+
+ # PageKite kite details
+ status.update(get_kite_details())
+
+ # 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', '')
+
+ return status
+
+
+def get_pagekite_services():
+ """Get enabled services. Returns two values:
+
+ 1. predefined services: {'http': False, 'ssh': True, 'https': True}
+ 2. custom services: [{'protocol': 'http', 'secret' 'nono', ..}, [..]}
+ """
+ custom = []
+ predefined = {}
+ # set all predefined services to 'disabled' by default
+ [predefined.update({proto: False}) for proto in PREDEFINED_SERVICES.keys()]
+ # now, search for the enabled ones
+ for serviceline in _run(['get-services']).split():
+ params = construct_params(serviceline)
+ for name, predefined_service in PREDEFINED_SERVICES.items():
+ if params == predefined_service['params']:
+ predefined[name] = True
+ break
+ else:
+ custom.append(prepare_params_for_display(params))
+ return predefined, custom
+
+
+def _run(arguments, superuser=True):
+ """Run a given command and raise exception if there was an error"""
+ command = 'pagekite'
+
+ if superuser:
+ return actions.superuser_run(command, arguments)
+ else:
+ return actions.run(command, arguments)
diff --git a/plinth/modules/pagekite/views.py b/plinth/modules/pagekite/views.py
new file mode 100644
index 000000000..370944b03
--- /dev/null
+++ b/plinth/modules/pagekite/views.py
@@ -0,0 +1,123 @@
+#
+# 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 gettext import gettext as _
+from django.contrib.auth.decorators import login_required
+from django.core.urlresolvers import reverse, reverse_lazy
+from django.http.response import HttpResponseRedirect
+from django.template.response import TemplateResponse
+from django.views.generic import View, TemplateView
+from django.views.generic.edit import FormView
+
+from plinth import package
+from .util import get_pagekite_config, get_pagekite_services, get_kite_details
+from .forms import ConfigurationForm, DefaultServiceForm, CustomServiceForm
+
+subsubmenu = [{'url': reverse_lazy('pagekite:index'),
+ 'text': _('About PageKite')},
+ {'url': reverse_lazy('pagekite:configure'),
+ 'text': _('Configure PageKite')},
+ {'url': reverse_lazy('pagekite:default-services'),
+ 'text': _('Default Services')},
+ {'url': reverse_lazy('pagekite:custom-services'),
+ 'text': _('Custom Services')}]
+
+
+@login_required
+def index(request):
+ """Serve introduction page"""
+ return TemplateResponse(request, 'pagekite_introduction.html',
+ {'title': _('Public Visibility (PageKite)'),
+ 'subsubmenu': subsubmenu})
+
+
+class ContextMixin(object):
+ """Mixin to add 'subsubmenu' and 'title' to the context."""
+ def get_context_data(self, **kwargs):
+ """Use self.title and the module-level subsubmenu"""
+ context = super(ContextMixin, self).get_context_data(**kwargs)
+ context['title'] = getattr(self, 'title', '')
+ context['subsubmenu'] = subsubmenu
+ return context
+
+
+class DeleteServiceView(View):
+ def post(self, request):
+ form = CustomServiceForm(request.POST)
+ if form.is_valid():
+ form.delete(request)
+ return HttpResponseRedirect(reverse('pagekite:custom-services'))
+
+
+class CustomServiceView(ContextMixin, TemplateView):
+ template_name = 'pagekite_custom_services.html'
+
+ def get_context_data(self, *args, **kwargs):
+ context = super(CustomServiceView, self).get_context_data(*args,
+ **kwargs)
+ unused, custom_services = get_pagekite_services()
+ for service in custom_services:
+ service['form'] = CustomServiceForm(initial=service)
+ context['custom_services'] = custom_services
+ context.update(get_kite_details())
+ return context
+
+ def get(self, request, *args, **kwargs):
+ context = self.get_context_data(**kwargs)
+ form = CustomServiceForm(prefix="custom")
+ context['form'] = form
+ return self.render_to_response(context)
+
+ def post(self, request):
+ unused, custom_services = get_pagekite_services()
+ form = CustomServiceForm(request.POST, prefix="custom")
+ if form.is_valid():
+ form.save(request)
+ form = CustomServiceForm(prefix="custom")
+
+ context = self.get_context_data()
+ context['form'] = form
+
+ return self.render_to_response(context)
+
+
+class DefaultServiceView(ContextMixin, FormView):
+ template_name = 'pagekite_default_services.html'
+ title = 'PageKite Default Services'
+ form_class = DefaultServiceForm
+ success_url = reverse_lazy('pagekite:default-services')
+
+ def get_initial(self):
+ return get_pagekite_services()[0]
+
+ def form_valid(self, form):
+ form.save(self.request)
+ return super(DefaultServiceView, self).form_valid(form)
+
+
+class ConfigurationView(ContextMixin, FormView):
+ template_name = 'pagekite_configure.html'
+ form_class = ConfigurationForm
+ prefix = 'pagekite'
+ success_url = reverse_lazy('pagekite:configure')
+
+ def get_initial(self):
+ return get_pagekite_config()
+
+ def form_valid(self, form):
+ form.save(self.request)
+ return super(ConfigurationView, self).form_valid(form)
diff --git a/plinth/templatetags/plinth_extras.py b/plinth/templatetags/plinth_extras.py
index e3def79a1..e454a8269 100644
--- a/plinth/templatetags/plinth_extras.py
+++ b/plinth/templatetags/plinth_extras.py
@@ -15,6 +15,7 @@
# along with this program. If not, see .
#
+import copy
import os
from django import template
@@ -58,3 +59,21 @@ def show_subsubmenu(context, menu):
"""Mark the active menu item and display the subsubmenu"""
menu = mark_active_menuitem(menu, context['request'].path)
return {'subsubmenu': menu}
+
+
+@register.assignment_tag
+def create_pagekite_service_link(service, kite_name):
+ """Create a link (URL) out of a pagekite service
+
+ Parameters: - service: the params dictionary
+ - kite_name: kite name (from the pagekite configuration)
+ """
+ params = {'protocol': service['protocol']}
+ if 'subdomains' in service and service['subdomains']:
+ params['kite_name'] = "*.%s" % kite_name
+ else:
+ params['kite_name'] = kite_name
+ link = "{protocol}://{kite_name}".format(**params)
+ if 'frontend_port' in service and service['frontend_port']:
+ link = "%s:%s" % (link, service['frontend_port'])
+ return link
diff --git a/plinth/tests/test_pagekite_actions.py b/plinth/tests/test_pagekite_actions.py
new file mode 100644
index 000000000..9f03f5fdb
--- /dev/null
+++ b/plinth/tests/test_pagekite_actions.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 .
+#
+
+import os
+import unittest
+
+from actions.pagekite_util import get_augeas_servicefile_path, CONF_PATH, \
+ construct_params, deconstruct_params
+
+
+class TestPagekiteActions(unittest.TestCase):
+ # test-cases to convert parameter-strings into param dicts and back
+ _tests = [
+ {
+ 'line': 'https/8080:*.@kitename:localhost:8080:@kitesecret',
+ 'params': {'kitename': '*.@kitename', 'backend_host': 'localhost',
+ 'secret': '@kitesecret', 'protocol': 'https/8080',
+ 'backend_port': '8080'}
+ },
+ {
+ 'line': 'https:*.@kitename:localhost:80:@kitesecret',
+ 'params': {'protocol': 'https',
+ 'kitename': '*.@kitename',
+ 'backend_port': '80',
+ 'backend_host': 'localhost',
+ 'secret': '@kitesecret'}
+ },
+ {
+ 'line': 'raw/22:@kitename:localhost:22:@kitesecret',
+ 'params': {'protocol': 'raw/22',
+ 'kitename': '@kitename',
+ 'backend_port': '22',
+ 'backend_host': 'localhost',
+ 'secret': '@kitesecret'}
+ },
+ ]
+
+ def test_get_augeas_servicefile_path(self):
+ """ Test the generation of augeas-paths for pagekite services """
+ tests = (('http', '80_http.rc'),
+ ('https', '443_https.rc'),
+ ('http/80', '80_http.rc'),
+ ('http/8080', '8080_http.rc'),
+ ('raw/22', '22_raw.rc'))
+ for protocol, filename in tests:
+ expected_path = os.path.join(CONF_PATH, filename, 'service_on')
+ returned_path = get_augeas_servicefile_path(protocol)
+ self.assertEqual(expected_path, returned_path)
+
+ with self.assertRaises(ValueError):
+ get_augeas_servicefile_path('xmpp')
+
+ def test_deconstruct_params(self):
+ """ Test deconstructing parameter dictionaries into strings """
+ for test in self._tests:
+ self.assertEqual(test['line'], deconstruct_params(test['params']))
+
+ def test_construct_params(self):
+ """ Test constructing parameter dictionaries out of string """
+ for test in self._tests:
+ self.assertEqual(test['params'], construct_params(test['line']))
+
+ line = "'https/80'; touch /etc/fstab':*.@kitename:localhost:80:foo'"
+ with self.assertRaises(RuntimeError):
+ construct_params(line)
From 8955e41f15f5523b6ffd1d450a22fe99f3ed6297 Mon Sep 17 00:00:00 2001
From: fonfon
Date: Tue, 20 Jan 2015 17:18:53 +0000
Subject: [PATCH 03/21] pagekite is required to be installed via package now
---
plinth/modules/pagekite/views.py | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/plinth/modules/pagekite/views.py b/plinth/modules/pagekite/views.py
index 370944b03..f94286473 100644
--- a/plinth/modules/pagekite/views.py
+++ b/plinth/modules/pagekite/views.py
@@ -20,6 +20,7 @@ from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse, reverse_lazy
from django.http.response import HttpResponseRedirect
from django.template.response import TemplateResponse
+from django.utils.decorators import method_decorator
from django.views.generic import View, TemplateView
from django.views.generic.edit import FormView
@@ -46,7 +47,10 @@ def index(request):
class ContextMixin(object):
- """Mixin to add 'subsubmenu' and 'title' to the context."""
+ """Mixin to add 'subsubmenu' and 'title' to the context.
+
+ Also requires 'pagekite' to be installed.
+ """
def get_context_data(self, **kwargs):
"""Use self.title and the module-level subsubmenu"""
context = super(ContextMixin, self).get_context_data(**kwargs)
@@ -54,8 +58,12 @@ class ContextMixin(object):
context['subsubmenu'] = subsubmenu
return context
+ @method_decorator(package.required('pagekite'))
+ def dispatch(self, *args, **kwargs):
+ return super(ContextMixin, self).dispatch(*args, **kwargs)
-class DeleteServiceView(View):
+
+class DeleteServiceView(ContextMixin, View):
def post(self, request):
form = CustomServiceForm(request.POST)
if form.is_valid():
From c04897be8bc672528c41bd9733874b406a3e2716 Mon Sep 17 00:00:00 2001
From: fonfon
Date: Wed, 21 Jan 2015 12:59:47 +0000
Subject: [PATCH 04/21] Moved pagekit url creation to pagekite module
plus some minor cleanup
---
actions/pagekite | 4 +-
.../templates/pagekite_custom_services.html | 12 ++---
.../pagekite/templatetags/pagekite_extras.py | 44 +++++++++++++++++++
plinth/modules/pagekite/util.py | 28 ++++++------
plinth/modules/pagekite/views.py | 7 +--
plinth/templatetags/plinth_extras.py | 19 --------
6 files changed, 70 insertions(+), 44 deletions(-)
create mode 100644 plinth/modules/pagekite/templatetags/pagekite_extras.py
diff --git a/actions/pagekite b/actions/pagekite
index 34bbaadaa..3891802d7 100755
--- a/actions/pagekite
+++ b/actions/pagekite
@@ -223,10 +223,10 @@ def subcommand_add_service(arguments):
root = get_new_service_path(params['protocol'])
# TODO: after adding a service, augeas fails writing the config;
- # so do it manually here
+ # so add the service_on entry manually instead
path = convert_augeas_path_to_filepath(root)
with open(path, 'a') as servicefile:
- line = "service_on = %s" % deconstruct_params(params)
+ line = "\nservice_on = %s\n" % deconstruct_params(params)
servicefile.write(line)
diff --git a/plinth/modules/pagekite/templates/pagekite_custom_services.html b/plinth/modules/pagekite/templates/pagekite_custom_services.html
index d865d9d40..ee0ff9ca3 100644
--- a/plinth/modules/pagekite/templates/pagekite_custom_services.html
+++ b/plinth/modules/pagekite/templates/pagekite_custom_services.html
@@ -20,7 +20,7 @@
{% load bootstrap %}
-{% load plinth_extras %}
+{% load pagekite_extras %}
{% block page_head %}
{% endblock %}
{% block content %}
-
Custom Services
- Warning: Your PageKite frontend server may not support all the
+ Warning: Your PageKite frontend server may not support all the
protocol/port combinations that you are able to define here. For example,
HTTPS on ports other than 443 is known to cause problems.
-
+
+
+
+
+
Existing custom services
{% if not custom_services %}
You don't have any Custom Services enabled
@@ -65,13 +76,15 @@
{% else %}
{{ service_url }}
{% endif %}
+
+ connected to {{ service.backend_host }}:{{ service.backend_port }}