refactored first boot module; user is now logged in automatically after an account is created

This commit is contained in:
fonfon 2014-10-07 03:41:49 +02:00 committed by Sunil Mohan Adapa
parent e7964a183f
commit 94040e402a
9 changed files with 145 additions and 161 deletions

View File

@ -18,7 +18,3 @@
"""
Plinth module for first boot wizard
"""
from . import first_boot
__all__ = ['first_boot']

View File

@ -1,125 +0,0 @@
"""First Boot: Initial Plinth Configuration.
See docs/design/first-connection.mdwn for details.
The Plinth first-connection process has several stages:
0. The user connects to Plinth for the first time and is redirected from
the home page to the Hello page.
1. The user sets the Box's name, the administrator user name and password.
2. The user interacts with the box normally.
"""
from django import forms
from django.contrib import messages
from django.core import validators
from django.core.urlresolvers import reverse
from django.http.response import HttpResponseRedirect
from django.template.response import TemplateResponse
from gettext import gettext as _
from plinth.modules.config import config
from plinth.modules.lib.auth import add_user
from plinth import kvstore
class State0Form(forms.Form): # pylint: disable-msg=W0232
"""First boot state 0 form"""
hostname = forms.CharField(
label=_('Name your FreedomBox'),
help_text=_('For convenience, your FreedomBox needs a name. It \
should be something short that does not contain spaces or punctuation. \
"Willard" would be a good name while "Freestyle McFreedomBox!!!" would \
not. It must be alphanumeric, start with an alphabet and must not be greater \
than 63 characters in length.'),
validators=[
validators.RegexValidator(r'^[a-zA-Z][a-zA-Z0-9]{,62}$',
_('Invalid hostname'))])
username = forms.CharField(label=_('Username'))
password = forms.CharField(label=_('Password'),
widget=forms.PasswordInput())
def index(request):
"""Serve the index first boot page"""
return state0(request)
def state0(request):
"""In this state, configure hostname and administrator username and
password.
All the parameters are form inputs. They get passed in when the form is
submitted. This method checks the inputs and if they validate, uses them
to take action. If they do not validate, it displays the form to give the
user a chance to input correct values. It might display an error message.
"""
try:
if kvstore.get_default('firstboot_state', 0) >= 5:
return HttpResponseRedirect(reverse('index'))
except KeyError:
pass
# Until LDAP is in place, we'll put the box key in the cfg.store_file
status = get_state0()
form = None
if request.method == 'POST':
form = State0Form(request.POST, prefix='firstboot')
# pylint: disable-msg=E1101
if form.is_valid():
success = _apply_state0(request, status, form.cleaned_data)
if success:
# Everything is good, permanently mark and move to page 2
kvstore.set('firstboot_state', 1)
return HttpResponseRedirect(reverse('first_boot:state1'))
else:
form = State0Form(initial=status, prefix='firstboot')
return TemplateResponse(request, 'firstboot_state0.html',
{'title': _('First Boot!'),
'form': form})
def get_state0():
"""Return the state for form state 0"""
return {'hostname': config.get_hostname()}
def _apply_state0(request, old_state, new_state):
"""Apply changes in state 0 form"""
success = True
if old_state['hostname'] != new_state['hostname']:
config.set_hostname(new_state['hostname'])
error = add_user(new_state['username'], new_state['password'],
'First user, please change', '', True)
if error:
messages.error(request, _('User account creation failed: %s') % error)
success = False
else:
messages.success(request, _('User account created'))
return success
def state1(request):
"""State 1 is when we have a hostname and administrator account. Redirect
the user to login page after this.
"""
# TODO complete first_boot handling
# Make sure the user is not stuck on a dead end for now.
kvstore.set('firstboot_state', 5)
return TemplateResponse(request, 'firstboot_state1.html',
{'title': _('Setup Complete')})

View File

@ -0,0 +1,59 @@
from django import forms
from django.contrib import auth, messages
from django.core import validators
from gettext import gettext as _
from plinth.modules.config import config
class State0Form(forms.ModelForm):
"""Firstboot state 0: Set hostname and create a new user"""
hostname = forms.CharField(
label=_('Name of your FreedomBox'),
help_text=_('For convenience, your FreedomBox needs a name. It \
should be something short that does not contain spaces or punctuation. \
"Willard" would be a good name while "Freestyle McFreedomBox!!!" would \
not. It must be alphanumeric, start with an alphabet and must not be greater \
than 63 characters in length.'),
validators=[
validators.RegexValidator(r'^[a-zA-Z][a-zA-Z0-9]{,62}$',
_('Invalid hostname'))])
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
super(State0Form, self).__init__(*args, **kwargs)
class Meta:
model = auth.models.User
fields = ("hostname", "username", "password")
widgets = {
'password': forms.PasswordInput,
}
help_texts = {
'username': 'Choose a username and password to access this web\
interface. The password can be changed and other users can\
be added later.',
}
def save(self, commit=True):
"""Set hostname, create and login the user"""
config.set_hostname(self.cleaned_data['hostname'])
user = super(State0Form, self).save(commit=False)
user.set_password(self.cleaned_data["password"])
user.is_expert = True
if commit:
user.save()
self.login_user()
return user
def login_user(self):
"""Try to login the user with the credentials he provided"""
try:
user = auth.authenticate(username=self.request.POST['username'],
password=self.request.POST['password'])
auth.login(self.request, user)
except Exception:
pass
else:
msg = _('User account created, you are now logged in')
messages.success(self.request, msg)

View File

@ -35,19 +35,14 @@ class FirstBootMiddleware(object):
@staticmethod
def process_request(request):
"""Handle a request as Django middleware request handler."""
# Prevent redirecting to first boot wizard in a loop by
# checking if we are already in first boot wizard.
if request.path.startswith(reverse('first_boot:index')):
return
state = kvstore.get_default('firstboot_state', 0)
if not state:
# Permanent redirect causes the browser to cache the redirect,
# preventing the user from navigating to /plinth until the
# browser is restarted.
return HttpResponseRedirect(reverse('first_boot:index'))
firstboot_index_url = reverse('first_boot:index')
user_requests_firstboot = request.path.startswith(firstboot_index_url)
if state < 5:
LOGGER.info('First boot state - %d', state)
# Setup is complete: Forbid accessing firstboot
if state >= 10 and user_requests_firstboot:
return HttpResponseRedirect(reverse('index'))
# Setup is not complete: Forbid accessing anything but firstboot
if state < 10 and not user_requests_firstboot:
return HttpResponseRedirect(reverse('first_boot:state%d' % state))

View File

@ -0,0 +1,11 @@
form .control-label {
font-size: large;
}
form .help-block {
font-size: small;
}
/* TODO: remove this customization once we use bootstrap3 */
.has-error span.help-block {
background-color: red;
}

View File

@ -18,28 +18,24 @@
#
{% endcomment %}
{% load static %}
{% load bootstrap %}
{% block page_head %}
<link rel="stylesheet" href="{% static 'first_boot/state0.css' %}"/>
{% endblock %}
{% block main_block %}
<h2>Welcome to Your FreedomBox!</h2>
<p>Welcome. It looks like this FreedomBox isn't set up yet. We'll
need to ask you a just few questions to get started.</p>
<p>It looks like this FreedomBox isn't set up yet. We
need to ask you a few questions to get started.</p>
<br>
<form class="form" method="post">
{% csrf_token %}
{% include 'bootstrapform/field.html' with field=form.hostname %}
<p><strong>Initial user and password.</strong> Access to this web
interface is protected by knowing a username and password.
Provide one here to register the initial privileged user. The
password can be changed and other users added later.</p>
{% include 'bootstrapform/field.html' with field=form.username %}
{% include 'bootstrapform/field.html' with field=form.password %}
{{ form|bootstrap }}
<input type="submit" class="btn-primary" value="Box it up!"/>
</form>
@ -47,7 +43,11 @@
{% endblock %}
{% block sidebar_right_block %}
{% include "firstboot_sidebar.html" %}
{% endblock %}
{% block page_js %}
<script>
$('#id_hostname').focus();
</script>
{% endblock %}

View File

@ -22,7 +22,11 @@
{% block main_block %}
{% if user.is_authenticated %}
<p>Have fun with your freedombox!</p>
{% else %}
<p>Proceed to <a href="{% url 'lib:login' %}">login</a>.</p>
{% endif %}
{% endblock %}

View File

@ -20,11 +20,13 @@ URLs for the First Boot module
"""
from django.conf.urls import patterns, url
from .views import State0View
urlpatterns = patterns( # pylint: disable-msg=C0103
'plinth.modules.first_boot.first_boot',
url(r'^firstboot/$', 'index', name='index'),
url(r'^firstboot/state0/$', 'state0', name='state0'),
url(r'^firstboot/state1/$', 'state1', name='state1')
'plinth.modules.first_boot.views',
# Take care of the firstboot middleware when changing URLs
url(r'^firstboot/$', State0View.as_view(), name='index'),
url(r'^firstboot/state0/$', State0View.as_view(), name='state0'),
url(r'^firstboot/state10/$', 'state10', name='state10'),
)

View File

@ -0,0 +1,42 @@
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse_lazy
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.views.generic.edit import CreateView
from gettext import gettext as _
from plinth import kvstore
from plinth.modules.config import config
from .forms import State0Form
class State0View(CreateView):
"""Setup hostname and create user account"""
template_name = 'firstboot_state0.html'
form_class = State0Form
success_url = reverse_lazy('first_boot:state10')
def get_initial(self):
initial = super(State0View, self).get_initial()
initial['hostname'] = config.get_hostname()
return initial
def get_form_kwargs(self):
"""Make request available to the form (to insert messages)"""
kwargs = super(State0View, self).get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def state10(request):
"""State 10 is when all firstboot setup is done.
After viewing this page the firstboot module can't be accessed anymore.
"""
# Make sure that a user exists before finishing firstboot
if User.objects.all():
kvstore.set('firstboot_state', 10)
return render_to_response('firstboot_state10.html',
{'title': _('Setup Complete')},
context_instance=RequestContext(request))