refactoring pagekite: configuration form works

splitting the services to a separate page is not yet finished
This commit is contained in:
fonfon 2015-01-14 19:14:27 +00:00
parent cf96797040
commit 1fc0064fd0
15 changed files with 818 additions and 123 deletions

View File

@ -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'])

View File

@ -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 <http://www.gnu.org/licenses/>.
#
"""
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

104
actions/pagekite_util.py Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
#
"""
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')

View File

@ -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)

View File

@ -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 <http://www.gnu.org/licenses/>.
#
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])

View File

@ -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)

View File

@ -27,20 +27,15 @@
{% include 'bootstrapform/field.html' with field=form.enabled %}
<div id='pagekite-post-enabled-form'
<div class='pagekite-post-enabled-form'
style='display: {{ form.enabled.value|yesno:'block,none' }};'>
<h3>PageKite Account</h3>
{{ form.server|bootstrap_horizontal }}
{{ form.kite_name|bootstrap_horizontal }}
{{ form.kite_secret|bootstrap_horizontal }}
<h3>Services</h3>
{{ form.http_enabled|bootstrap_horizontal }}
{{ form.ssh_enabled|bootstrap_horizontal }}
</div>
<input type="submit" class="btn btn-primary" value="Update setup"/>
<input type="submit" class="btn btn-primary" value="Save settings"/>
</form>
{% 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');
}
});

View File

@ -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 <http://www.gnu.org/licenses/>.
#
{% endcomment %}
{% load bootstrap %}
{% load plinth_extras %}
{% block page_head %}
<style type="text/css">
div.checkbox .help-block {
display: inline-block;
margin: 0px 10px;
}
div.custom-services span.service {
display: inline-block;
padding-top: 6px;
}
input.btn {
margin: 10px 15px;
}
</style>
{% endblock %}
{% block content %}
<h3>Custom Services</h3>
<div class="row custom-services">
<div class="col-lg-5">
<h4>Existing custom services</h4>
{% if not custom_services %}
<i>You don't have any Custom Services enabled</i>
{% endif %}
<div class="list-group">
{% for service in custom_services %}
{% create_pagekite_service_link service kite_name as service_link %}
<div class="list-group-item clearfix">
<span class="service">
<span title="Forwards {{ service_link }} to {{ service.backend_host }}:{{ service.backend_port }}">
{% if service_link|slice:":4" == "http" %}
<a href="{{ service_link }}">{{ service_link }}</a>
{% else %}
{{ service_link }}
{% endif %}
</span>
</span>
<form class="form pull-right" method="post"
action="{% url 'pagekite:delete-custom-service' %}">
<div style='display:none'>
{% csrf_token %}
{{ service.form }}
</div>
<button type="submit" class="btn btn-default"
title="Delete this service">
<span class="glyphicon glyphicon-trash" aria-hidden="true">
</span>
</button>
</form>
</div>
{% endfor %}
</div>
</div>
<div class="col-lg-6 col-lg-offset-1">
<form class="form" method="post">
<h4>Create a custom service</h4>
{{ form|bootstrap_horizontal:'col-lg-6' }}
{% csrf_token %}
<input type="submit" class="btn btn-primary pull-right" value="Add Service"/>
</form>
</div>
</div>
{% endblock %}

View File

@ -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 <http://www.gnu.org/licenses/>.
#
{% endcomment %}
{% load bootstrap %}
{% block page_head %}
<style type="text/css">
div.checkbox .help-block {
display: inline-block;
margin: 0px 10px;
}
div.custom-services span.service {
display: inline-block;
padding-top: 6px;
}
input.btn {
margin: 10px 15px;
}
</style>
{% endblock %}
{% block content %}
<h3>Default Services</h3>
<p>Exposing services makes them accessible and attackable from the evil
internet. Be cautious!</p>
<form class="form predefined" method="post">
{{ form.http|bootstrap_horizontal:'col-lg-0' }}
{{ form.https|bootstrap_horizontal:'col-lg-0' }}
{{ form.ssh|bootstrap_horizontal:'col-lg-0' }}
{% csrf_token %}
<input type="submit" class="btn btn-primary" value="Save settings"/>
</form>
{% endblock %}

View File

@ -42,10 +42,10 @@ rest of the Internet. This includes the following situations: </p>
<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
exposing web server and SSH server are supported. You can use any
server that offers a pagekite service, for example
<a href="https://pagekite.net">pagekite.net</a>.
In future, it might be possible to use your buddy's
{{ cfg.box_name }} for this.</p>
<p>

View File

@ -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'),
)

View File

@ -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 <http://www.gnu.org/licenses/>.
#
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 "
"<a href=\"http://{0}\">http://{0}</a>"),
},
'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 "
"<a href=\"https://{0}\">https://{0}</a>"),
},
'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 <a href=\""
"https://pagekite.net/wiki/Howto/SshOverPageKite/\">"
"instructions</a>")
},
}
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)

View File

@ -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 <http://www.gnu.org/licenses/>.
#
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)

View File

@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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

View File

@ -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 <http://www.gnu.org/licenses/>.
#
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)