firstboot: freedombox.me pagekite vouchers

- Show PageKite voucher only if cfg.danube_edition is enabled
This commit is contained in:
fonfon 2015-05-06 15:43:55 +02:00 committed by Sunil Mohan Adapa
parent 9b4497212d
commit 435f980c6f
No known key found for this signature in database
GPG Key ID: 36C361440C9BC971
10 changed files with 258 additions and 22 deletions

View File

@ -29,6 +29,7 @@
python3-django-stronghold \
python3-gi \
python3-psutil \
python3-requests \
python3-setuptools \
python3-yaml \
xmlto

View File

@ -28,3 +28,8 @@ class PlinthError(Exception):
class ActionError(PlinthError):
"""Use this error for exceptions when executing an action."""
pass
class DomainRegistrationError(PlinthError):
"""Domain registration failed"""
pass

View File

@ -19,13 +19,23 @@
Forms for first boot module.
"""
import json
import logging
import requests
from django import forms
from django.contrib import auth
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
from plinth import actions
from plinth.errors import ActionError
from plinth import cfg
from plinth.errors import ActionError, DomainRegistrationError
from plinth.modules.pagekite.utils import PREDEFINED_SERVICES, run
from plinth.modules.users.forms import GROUP_CHOICES
from plinth.utils import format_lazy
LOGGER = logging.getLogger(__name__)
class State1Form(auth.forms.UserCreationForm):
@ -77,3 +87,99 @@ class State1Form(auth.forms.UserCreationForm):
else:
message = _('User account created, you are now logged in')
messages.success(self.request, message)
class SubdomainWidget(forms.widgets.TextInput):
"""Append the domain to the subdomain bootstrap input field"""
def render(self, *args, **kwargs):
inputfield = super(SubdomainWidget, self).render(*args, **kwargs)
domain = State5Form.DOMAIN_APPENDIX
return """<div class="input-group">
{0}
<span class="input-group-addon">{1}</span>
</div>""".format(inputfield, domain)
class State5Form(forms.Form):
"""Set up freedombox.me pagekite subdomain"""
DOMAIN_APPENDIX = ".freedombox.me"
# webservice url for domain validation and registration
service_url = "http://freedombox.me/cgi-bin/freedomkite.pl"
code_help_text = _("The voucher you received with your {box_name} Danube "
"Edition")
code = forms.CharField(help_text=format_lazy(code_help_text,
box_name=_(cfg.box_name)))
domain = forms.SlugField(label=_("Subdomain"),
widget=SubdomainWidget,
help_text=_("The subdomain you want to register"))
def clean_domain(self):
"""Append the domain to the users' subdomain"""
return self.cleaned_data['domain'] + self.DOMAIN_APPENDIX
def clean(self):
"""Validate user input (subdomain and code)"""
cleaned_data = super(State5Form, self).clean()
# if the subdomain is wrong don't look if the domain is available
if self.errors:
return cleaned_data
self.domain_already_registered = False
code = cleaned_data.get("code")
domain = cleaned_data.get("domain")
response = requests.get(self.service_url, params={'code': code}).json()
# The validation response looks like:
# 1. code invalid: {}
if 'domain' not in response:
raise ValidationError(_('This code is not valid'), code='invalid')
# 2. code valid, domain registered: {'domain': 'xx.freedombox.me'}
elif response['domain']:
if response['domain'] == domain:
self.domain_already_registered = True
else:
msg = _('This code is bound to the domain %s' %
response['domain'])
raise ValidationError(msg, code='invalid')
# 3. code valid, no domain registered: {'domain': None}
elif response['domain'] is None:
# make sure that the desired domain is available
data = {'domain': domain}
domain_response = requests.get(self.service_url, params=data)
registered_domain = domain_response.json()['domain']
if registered_domain is not None:
msg = _('The requested Domain is already registered')
raise ValidationError(msg, code='invalid')
return cleaned_data
def register_domain(self):
"""Register a domain (only if it's not already registered)"""
if not self.domain_already_registered:
data = {'domain': self.cleaned_data['domain'],
'code': self.cleaned_data['code']}
response = requests.post(self.service_url, data)
if not response.ok:
msg = "Domain registration failed: %s" % response.text
LOGGER.error(msg)
raise DomainRegistrationError(msg)
def setup_pagekite(self):
"""Configure pagekite and enable the pagekite service"""
# set kite name and secret
run(['set-kite', '--kite-name', self.cleaned_data['domain']],
input=self.cleaned_data['code'].encode())
# set frontend
run(['set-frontend', '%s:80' % self.cleaned_data['domain']])
# enable pagekite http+https service
for service_name in ['http', 'https']:
service = PREDEFINED_SERVICES[service_name]['params']
try:
run(['add-service', '--service', json.dumps(service)])
except ActionError as err:
if 'already exists' not in str(err):
raise
run(['start-and-enable'])

View File

@ -0,0 +1,33 @@
{% 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 static %}
{% block page_head %}
<style type="text/css">
a.navbar-brand {
display: none;
}
</style>
{% endblock %}
{% block mainmenu_right %}
{% include "firstboot_navbar.html" %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "base_firstboot.html" %}
{% comment %}
#
# This file is part of Plinth.
@ -30,10 +30,6 @@
</style>
{% endblock %}
{% block mainmenu_right %}
{% include "firstboot_navbar.html" %}
{% endblock %}
{% block content_row %}
<p class="text-center">
<img src="{% static 'theme/img/FreedomBox-logo-standard.png' %}"

View File

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "base_firstboot.html" %}
{% comment %}
#
# This file is part of Plinth.
@ -22,18 +22,6 @@
{% load i18n %}
{% load static %}
{% block page_head %}
<style type="text/css">
a.navbar-brand {
display: none;
}
</style>
{% endblock %}
{% block mainmenu_right %}
{% include "firstboot_navbar.html" %}
{% endblock %}
{% block content_row %}
<div class="col-md-6 col-md-offset-3">
{% include 'messages.html' %}

View File

@ -0,0 +1,70 @@
{% extends "base_firstboot.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 i18n %}
{% load static %}
{% block content %}
<h3>{% trans "Set up a freedombox.me subdomain with your voucher" %}</h3>
<p>
{% url 'first_boot:state10' as finish_firstboot_url %}
{% blocktrans trimmed %}
<a href="{{ finish_firstboot_url }}">Skip the setup</a> if you do not have a
voucher or want to configure pagekite without using a freedombox.me subdomain.
{% endblocktrans %}
</p>
<p>
{% blocktrans trimmed %}
You can use a redeemed voucher but it will only work with the initially
registered subdomain.
{% endblocktrans %}
</p>
<div class="row">
<div class="col-md-8">
<form class='firstboot form-horizontal' role="form" action="" method="post">
{% csrf_token %}
{{ form|bootstrap_horizontal:'col-lg-3' }}
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<button type="submit" class="btn btn-primary">
{% trans "Register" %}
</button>
<a href="{% url 'first_boot:state10' %}" class="btn btn-primary"
role="button">
{% trans "Skip Registration" %}
</a>
</div>
</div>
</form>
</div>
</div>
{% endblock %}
{% block page_js %}
<script>
$('#id_code').focus();
</script>
{% endblock %}

View File

@ -22,7 +22,7 @@ URLs for the First Boot module
from django.conf.urls import url
from stronghold.decorators import public
from .views import State0View, State1View, state10
from .views import State0View, State1View, State5View, state10
urlpatterns = [
@ -30,5 +30,6 @@ urlpatterns = [
url(r'^firstboot/$', public(State0View.as_view()), name='index'),
url(r'^firstboot/state0/$', public(State0View.as_view()), name='state0'),
url(r'^firstboot/state1/$', public(State1View.as_view()), name='state1'),
url(r'^firstboot/state5/$', State5View.as_view(), name='state5'),
url(r'^firstboot/state10/$', state10, name='state10'),
]

View File

@ -15,16 +15,20 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from django.contrib import messages
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse_lazy
from django.http.response import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.utils.translation import ugettext as _
from django.views.generic import CreateView, TemplateView
from django.views.generic import CreateView, FormView, TemplateView
from plinth import cfg
from plinth import kvstore
from plinth import network
from .forms import State1Form
from plinth.errors import DomainRegistrationError
from .forms import State1Form, State5Form
class State0View(TemplateView):
@ -38,6 +42,11 @@ class State1View(CreateView):
form_class = State1Form
success_url = reverse_lazy('first_boot:state10')
def __init__(self, *args, **kwargs):
if cfg.danube_edition:
self.success_url = reverse_lazy('first_boot:state5')
return super(State1View, self).__init__(*args, **kwargs)
def get_form_kwargs(self):
"""Make request available to the form (to insert messages)"""
kwargs = super(State1View, self).get_form_kwargs()
@ -60,3 +69,29 @@ def state10(request):
{'title': _('Setup Complete'),
'connections': connections},
context_instance=RequestContext(request))
class State5View(FormView):
"""
State 5 is is the (optional) setup of the pagekite freedombox.me subdomain
"""
template_name = 'firstboot_state5.html'
form_class = State5Form
success_url = reverse_lazy('first_boot:state10')
def get(self, *args, **kwargs):
kvstore.set('firstboot_state', 5)
return super(State5View, self).get(*args, **kwargs)
def form_valid(self, form):
try:
form.register_domain()
except DomainRegistrationError as err:
messages.error(self.request, err)
return HttpResponseRedirect(reverse_lazy('first_boot:state5'))
else:
form.setup_pagekite()
msg = _("Pagekite setup finished. The HTTP and HTTPS services \
are activated now.")
messages.success(self.request, msg)
return super(State5View, self).form_valid(form)

View File

@ -185,6 +185,7 @@ setuptools.setup(
'django-stronghold',
'psutil',
'python-augeas',
'requests',
'pyyaml',
],
tests_require=['coverage >= 3.7'],