mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-02-04 08:13:38 +00:00
refactored first boot module; user is now logged in automatically after an account is created
This commit is contained in:
parent
e7964a183f
commit
94040e402a
@ -18,7 +18,3 @@
|
||||
"""
|
||||
Plinth module for first boot wizard
|
||||
"""
|
||||
|
||||
from . import first_boot
|
||||
|
||||
__all__ = ['first_boot']
|
||||
|
||||
@ -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')})
|
||||
59
plinth/modules/first_boot/forms.py
Normal file
59
plinth/modules/first_boot/forms.py
Normal 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)
|
||||
@ -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))
|
||||
|
||||
11
plinth/modules/first_boot/static/state0.css
Normal file
11
plinth/modules/first_boot/static/state0.css
Normal 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;
|
||||
}
|
||||
|
||||
@ -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 %}
|
||||
|
||||
@ -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 %}
|
||||
|
||||
@ -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'),
|
||||
)
|
||||
|
||||
42
plinth/modules/first_boot/views.py
Normal file
42
plinth/modules/first_boot/views.py
Normal 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))
|
||||
Loading…
x
Reference in New Issue
Block a user