Merge pull request #62 from SunilMohanAdapa/master

Merge: Sunil's PageKite UI module.
This commit is contained in:
Nick Daly 2014-03-25 00:27:22 +00:00
commit 6d6b5148cc
8 changed files with 612 additions and 23 deletions

View File

@ -17,7 +17,7 @@ default: config dirs template css docs
all: default
predepend:
sudo sh -c "apt-get install augeas-tools libpython2.7 pandoc psmisc python2.7 python-augeas python-bcrypt python-bjsonrpc python-cheetah python-cherrypy3 python-simplejson sudo"
sudo sh -c "apt-get install augeas-tools libpython2.7 pandoc psmisc python2.7 python-augeas python-passlib python-bcrypt python-bjsonrpc python-cheetah python-cherrypy3 python-simplejson python-contract sudo"
git submodule init
git submodule update
touch predepend

302
actions/pagekite-configure Executable file
View File

@ -0,0 +1,302 @@
#!/usr/bin/python
# -*- 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 <http://www.gnu.org/licenses/>.
#
"""
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')
# Get installed status
subparsers.add_parser('get-installed',
help='Get whether PakeKite is installed')
# 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_get_installed(_):
"""Print whether PageKite is installed"""
with open('/dev/null', 'w') as file_handle:
status = subprocess.call(['which', 'pagekite'], stdout=file_handle)
print 'installed' if not status else 'not installed'
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,
0400), '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()

4
cfg.py
View File

@ -8,8 +8,8 @@ def get_item(parser, section, name):
try:
return parser.get(section, name)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
print ("The config file {} does not contain the {}.{} option.".format(
parser[0], section, name))
print ("Configuration does not contain the {}.{} option.".format(
section, name))
raise
parser = SafeConfigParser(

View File

@ -0,0 +1,291 @@
#
# 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 <http://www.gnu.org/licenses/>.
#
"""
Plinth module for configuring PageKite service
"""
import cherrypy
from gettext import gettext as _
import actions
import cfg
from forms import Form
from modules.auth import require
from plugin_mount import PagePlugin, FormPlugin
import re
import util
class PageKite(PagePlugin):
"""PageKite menu entry and introduction page"""
order = 60
def __init__(self, *args, **kwargs):
PagePlugin.__init__(self, *args, **kwargs)
self.register_page("router.setup.pagekite")
self.register_page("router.setup.pagekite.configure")
cfg.html_root.router.setup.menu.add_item(
"Public Visibility (PageKite)", "icon-flag",
"/router/setup/pagekite", 50)
@cherrypy.expose
@require()
def index(self, **kwargs):
"""Serve introcution page"""
del kwargs # Unused
main = _("""
<p>PageKite is a system for exposing FreedomBox services when you
don't have a direct connection to the Internet. You only need this
service if your FreedomBox services are unreachable from the rest of
the Internet. This includes the following situations: </p>
<ul>
<li>FreedomBox is behind a restricted firewall.</li>
<li>FreedomBox is connected to a (wireless) router which you don't
control.</li>
<li>Your ISP does not provide you an external IP address and instead
provides Internet connection through NAT.</li>
<li>Your ISP does not provide you a static IP address and your IP
address changes evertime you connect to Internet.</li>
<li>Your ISP limits incoming connections.</li>
</ul>
<p>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 FreedomBox
for this.</p>
<p><a class='btn btn-primary btn-lg'
href="{server_dir}/router/setup/pagekite/configure">Configure
PageKite</a></p>
""".format(server_dir=cfg.server_dir))
sidebar_right = _('''
<strong>PageKite</strong>
<p><a href="{server_dir}/router/setup/pagekite/configure">Configure
PageKite</a> </p>'''.format(server_dir=cfg.server_dir))
return self.fill_template(title="Public Visibility (PageKite)",
main=main, sidebar_right=sidebar_right)
class configure(FormPlugin, PagePlugin): # pylint: disable-msg=C0103
"""Main configuration form"""
order = 65
url = ["/router/setup/pagekite/configure"]
js = """
<script type="text/javascript">
(function($) {
$('#pagekite-server').attr("disabled", "disabled");
$('#pagekite-enable').change(function() {
if ($('#pagekite-enable').prop('checked')) {
$('#pagekite-post-enable-form').show('slow');
} else {
$('#pagekite-post-enable-form').hide('slow');
}
});
})(jQuery);
</script>
"""
def get_status(self):
"""
Return the current status of PageKite configuration by
executing various actions.
"""
status = {}
# Check if PageKite is installed
output = self._run(['get-installed'])
cfg.log('Output - %s' % output)
if output.split()[0] != 'installed':
return None
# PageKite service enabled/disabled
output = self._run(['get-status'])
status['enabled'] = (output.split()[0] == 'enabled')
# PageKite kite details
output = self._run(['get-kite'])
kite_details = output.split()
status['kite_name'] = kite_details[0]
status['kite_secret'] = kite_details[1]
# Service status
status['service'] = {}
for service in ('http', 'ssh'):
output = self._run(['get-service-status', service])
status['service'][service] = (output.split()[0] == 'enabled')
return status
def main(self, *args, **kwargs):
"""Build and return the main content area which is the form"""
del args # unused
status = self.get_status()
if not status:
return _('''
<p>PageKite is not installed, please install it. PageKite comes
pre-installed with FreedomBox. On any Debian based system (such as
FreedomBox) you may install it using the command 'aptitude install
pagekite'</p>''')
try:
message = kwargs['message'].text
except KeyError:
message = None
form = Form(
title="Configure PageKite",
action=cfg.server_dir + "/router/setup/pagekite/configure/",
name="configure_pagekite_form", message=message)
form.checkbox(_("Enable PageKite"), name="pagekite_enable",
id="pagekite-enable", checked=status['enabled'])
show_form = "block" if status['enabled'] else "none"
form.html('''
<div id='pagekite-post-enable-form'
style='display: {show_form}'>'''.format(show_form=show_form))
form.html(_("<h3>PageKite Account</h3>"))
form.text_input(_("Server"), name="pagekite_server",
id="pagekite-server", value="pagekite.net")
form.text_input(_("Kite name"), name="pagekite_kite_name",
id="pagekite-kite-name", value=status['kite_name'])
form.text_input(_("Kite secret"), name="pagekite_kite_secret",
id="pagekite-kite-secret", value=status['kite_secret'])
form.html(_("<h3>Services</h3>"))
form.checkbox(_("Web Server (HTTP)"), name="pagekite_http_enable",
id="pagekite-http-enable",
checked=status['service']['http'])
form.checkbox(_("Secure Shell (SSH)"), name="pagekite_ssh_enable",
id="pagekite-ssh-enable",
checked=status['service']['ssh'])
form.html("</div>") # pagekite-post-enable-form
form.submit(_("Update setup"))
return form.render()
def process_form(self, **kwargs):
"""Handle form submission"""
status = self.get_status()
message, new_status = self.validate_form(**kwargs)
if not message.text:
self.apply_changes(status, new_status, message)
return message
@staticmethod
def validate_form(**kwargs):
"""Check whether all the input form values are correct"""
new_status = {}
message = util.Message()
domain_name_re = r'^[\w-]{1,63}(\.[\w-]{1,63})*$'
pagekite_kite_name = kwargs.get('pagekite_kite_name', '').strip()
if not re.match(domain_name_re, pagekite_kite_name):
message.add(_('Invalid kite name'))
else:
new_status['kite_name'] = pagekite_kite_name
pagekite_kite_secret = kwargs.get('pagekite_kite_secret', '').strip()
if not pagekite_kite_secret:
message.add(_('Invalid kite secret'))
else:
new_status['kite_secret'] = pagekite_kite_secret
new_status['enabled'] = (kwargs.get('pagekite_enable') == 'on')
new_status['service'] = {
'http': (kwargs.get('pagekite_http_enable') == 'on'),
'ssh': (kwargs.get('pagekite_ssh_enable') == 'on')
}
return message, new_status
def apply_changes(self, old_status, new_status, message):
"""Apply the changes to PageKite configuration"""
cfg.log.info('New status is - %s' % new_status)
if old_status != new_status:
self._run(['stop'])
if old_status['enabled'] != new_status['enabled']:
if new_status['enabled']:
self._run(['set-status', 'enable'])
message.add(_('PageKite enabled'))
else:
self._run(['set-status', 'disable'])
message.add(_('PageKite disabled'))
if old_status['kite_name'] != new_status['kite_name'] or \
old_status['kite_secret'] != new_status['kite_secret']:
self._run(['set-kite', '--kite-name', new_status['kite_name'],
'--kite-secret', new_status['kite_secret']])
message.add(_('Kite details set'))
for service, old_value in old_status['service'].items():
if old_value != new_status['service'][service]:
if new_status['service'][service]:
self._run(['set-service-status', service, 'enable'])
message.add(_('Service enabled: {service}')
.format(service=service))
else:
self._run(['set-service-status', service, 'disable'])
message.add(_('Service disabled: {service}')
.format(service=service))
if old_status != new_status:
self._run(['start'])
@staticmethod
def _run(arguments, superuser=True):
"""Run an given command and raise exception if there was an error"""
command = 'pagekite-configure'
cfg.log.info('Running command - %s, %s, %s' % (command, arguments,
superuser))
if superuser:
output, error = actions.superuser_run(command, arguments)
else:
output, error = actions.run(command, arguments)
if error:
raise Exception('Error setting/getting PageKite confguration - %s'
% error)
return output

View File

@ -88,19 +88,19 @@ class wan(FormPlugin, PagePlugin):
url = ["/router/setup"]
order = 10
js = """<script type="text/javascript">
<!--
function hideshow_static() {
var d = document.getElementById('connect_type');
connect_type = d.value;
if (connect_type != 'Static IP') {
hide("static_ip_form");
} else {
show("static_ip_form");
js = """
<script type="text/javascript">
(function($) {
function hideshow_static() {
var show_or_hide = ($('#connect_type').val() == 'Static IP')
$('#static_ip_form').toggle(show_or_hide);
}
}
// -->
</script>"""
$(document).ready(function() {
$('#connect_type').change(hideshow_static);
hideshow_static();
});
})(jQuery);
</script>"""
def sidebar_right(self, *args, **kwargs):
side=''
@ -141,7 +141,7 @@ class wan(FormPlugin, PagePlugin):
action=cfg.server_dir + "/router/setup/wan/index",
name="wan_connection_form",
message=message)
form.dropdown('Connection Type', vals=["DHCP", "Static IP"], id="connect_type", onchange="hideshow_static()")
form.dropdown('Connection Type', vals=["DHCP", "Static IP"], id="connect_type")
form.html('<div id="static_ip_form">')
form.dotted_quad("WAN IP Address", name="wan_ip", quad=[wan_ip0, wan_ip1, wan_ip2, wan_ip3])
form.dotted_quad("Subnet Mask", name="subnet", quad=[subnet0, subnet1, subnet2, subnet3])
@ -150,11 +150,6 @@ class wan(FormPlugin, PagePlugin):
form.dotted_quad("Static DNS 2", name="dns2", quad=[dns20, dns21, dns22, dns23])
form.dotted_quad("Static DNS 3", name="dns3", quad=[dns30, dns31, dns32, dns33])
form.html('</div>')
form.html(""" <script type="text/javascript">
<!--
hideshow_static();
// -->
</script>""")
form.submit("Set Wan")
return form.render()

1
modules/pagekite.py Symbolic link
View File

@ -0,0 +1 @@
installed/router/pagekite.py

View File

@ -140,7 +140,7 @@ class FormPlugin():
"""If the user has tried to fill in the form, process it, otherwise, just display a default form."""
if kwargs:
kwargs['message'] = self.process_form(**kwargs)
parts = get_parts(self)
parts = get_parts(self, **kwargs)
return self.fill_template(**parts)
def process_form(self, **kwargs):

View File

@ -96,7 +96,6 @@
<!-- JS from previous Plinth template, not sure what to keep yet -->
<script type="text/javascript" src="$basehref/static/theme/js/menu.js"></script>
<script type="text/javascript" src="$basehref/static/theme/js/plinth.js"></script>
$js
$main_menu_js
$sub_menu_js
<script type="text/javascript">
@ -199,5 +198,6 @@
<!--<script type="text/javascript" src="$basehref/static/theme/js/libs/bootstrap/typeahead.js"></script>-->
<!-- JS plugins -->
<script type="text/javascript" src="$basehref/static/theme/js/plugins.js"></script>
$js
</body>
</html>