mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-03-11 09:04:54 +00:00
Convert firstboot pages to Django forms
This commit is contained in:
parent
5461694b10
commit
b50a99b1e4
@ -1,16 +1,5 @@
|
||||
from urlparse import urlparse
|
||||
import os, cherrypy, re
|
||||
from gettext import gettext as _
|
||||
from plugin_mount import PagePlugin, PluginMount, FormPlugin
|
||||
from modules.auth import require, add_user
|
||||
from forms import Form
|
||||
from withsqlite.withsqlite import sqlite_db
|
||||
import cfg
|
||||
import config
|
||||
from model import User
|
||||
import util
|
||||
|
||||
"""First Boot: Initial Plinth Configuration.
|
||||
"""
|
||||
First Boot: Initial Plinth Configuration.
|
||||
|
||||
See docs/design/first-connection.mdwn for details.
|
||||
|
||||
@ -27,27 +16,76 @@ The Plinth first-connection process has several stages:
|
||||
3. The box detects the network's configuration and restarts networking.
|
||||
|
||||
4. The user interacts with the box normally.
|
||||
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from django.core import validators
|
||||
from gettext import gettext as _
|
||||
from plugin_mount import PagePlugin
|
||||
from modules.auth import add_user
|
||||
from withsqlite.withsqlite import sqlite_db
|
||||
import cfg
|
||||
import config
|
||||
import util
|
||||
|
||||
|
||||
## TODO: flesh out these tests values
|
||||
def valid_box_key(value):
|
||||
"""Check whether box key is valid"""
|
||||
del value # Unused
|
||||
|
||||
return True
|
||||
|
||||
|
||||
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())
|
||||
|
||||
box_key = forms.CharField(
|
||||
label=_('Box\'s key (optional)'), required=False,
|
||||
widget=forms.Textarea(), validators=[valid_box_key],
|
||||
help_text=_('Cryptographic keys are used so that Box\'s identity can \
|
||||
proved when talking to you. This key can be auto-generated, but if one \
|
||||
already exists (from a prior FreedomBox, for example), you can paste it \
|
||||
below. This key should not be the same as your key because you are not your \
|
||||
FreedomBox!'))
|
||||
|
||||
|
||||
class FirstBoot(PagePlugin):
|
||||
"""First boot wizard"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("firstboot") # this is the url this page will hang off of (/firstboot)
|
||||
|
||||
# this is the url this page will hang off of (/firstboot)
|
||||
self.register_page('firstboot')
|
||||
self.register_page('firstboot/state0')
|
||||
self.register_page('firstboot/state1')
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, *args, **kwargs):
|
||||
return self.state0(*args, **kwargs)
|
||||
|
||||
## TODO: flesh out these tests values
|
||||
def valid_box_key_p(self, key):
|
||||
return True
|
||||
def generate_box_key(self):
|
||||
return "fake key"
|
||||
|
||||
@cherrypy.expose
|
||||
def state0(self, message="", hostname="", box_key="", submitted=False, username="", password="", **kwargs):
|
||||
def state0(self, **kwargs):
|
||||
"""
|
||||
In this state, we do time config over HTTP, name the box and
|
||||
server key selection.
|
||||
@ -64,68 +102,70 @@ class FirstBoot(PagePlugin):
|
||||
"""
|
||||
|
||||
# FIXME: reject connection attempt if db["state"] >= 5.
|
||||
## Until LDAP is in place, we'll put the box key in the cfg.store_file
|
||||
status = self.get_state0()
|
||||
|
||||
## Until LDAP is in place, we'll put the box name and key in the cfg.store_file
|
||||
## Must resist the sick temptation to write an LDAP interface to the sqlite file
|
||||
with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as db:
|
||||
db['about'] = "This table is for information about this FreedomBox"
|
||||
if hostname:
|
||||
if '' == config.valid_hostname(hostname):
|
||||
config.set_hostname(hostname)
|
||||
else:
|
||||
message += _("Invalid box name: %s") % config.valid_hostname(hostname)
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs:
|
||||
form = State0Form(kwargs, prefix='firstboot')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
success = self._apply_state0(status, form.cleaned_data,
|
||||
messages)
|
||||
|
||||
if success:
|
||||
# Everything is good, permanently mark and move to page 2
|
||||
with sqlite_db(cfg.store_file, table="firstboot",
|
||||
autocommit=True) as database:
|
||||
database['state'] = 1
|
||||
|
||||
raise cherrypy.InternalRedirect('state1')
|
||||
else:
|
||||
form = State0Form(initial=status, prefix='firstboot')
|
||||
|
||||
return util.render_template(template='firstboot_state0',
|
||||
title=_('First Boot!'), form=form,
|
||||
messages=messages)
|
||||
|
||||
@staticmethod
|
||||
def get_state0():
|
||||
"""Return the state for form state 0"""
|
||||
with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as \
|
||||
database:
|
||||
return {'hostname': config.get_hostname(),
|
||||
'box_key': database.get('box_key', None)}
|
||||
|
||||
def _apply_state0(self, old_state, new_state, messages):
|
||||
"""Apply changes in state 0 form"""
|
||||
success = True
|
||||
with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as \
|
||||
database:
|
||||
database['about'] = 'Information about this FreedomBox'
|
||||
|
||||
if new_state['box_key']:
|
||||
database['box_key'] = new_state['box_key']
|
||||
elif not old_state['box_key']:
|
||||
database['box_key'] = self.generate_box_key()
|
||||
|
||||
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.append(
|
||||
('error', _('User account creation failed: %s') % error))
|
||||
success = False
|
||||
else:
|
||||
hostname = config.get_hostname()
|
||||
if box_key:
|
||||
if self.valid_box_key_p(box_key):
|
||||
db['box_key'] = box_key
|
||||
else:
|
||||
message += _("Invalid key!")
|
||||
elif 'box_key' in db and db['box_key']:
|
||||
box_key = _("We already have a key for this box on file.") # TODO: Think this through and handle more gracefully. Seriously.
|
||||
elif submitted and not box_key:
|
||||
box_key = self.generate_box_key()
|
||||
db['box_key'] = box_key
|
||||
if username and password:
|
||||
error = add_user(username, password, 'First user - please change', '', True)
|
||||
if error:
|
||||
message += _("User account creation failed: %s") % error
|
||||
validuser = False
|
||||
else:
|
||||
validuser = True
|
||||
else:
|
||||
validuser = False
|
||||
messages.append(('success', _('User account created')))
|
||||
|
||||
if hostname and box_key and '' == config.valid_hostname(hostname) and self.valid_box_key_p(box_key) and validuser:
|
||||
## Update state to 1 and head there
|
||||
with sqlite_db(cfg.store_file, table="firstboot", autocommit=True) as db:
|
||||
db['state']=1
|
||||
raise cherrypy.InternalRedirect('state1')
|
||||
|
||||
main = "<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>"
|
||||
form = Form(title="Welcome to Your FreedomBox!",
|
||||
action="", # stay at firstboot
|
||||
name="whats_my_name",
|
||||
message=message)
|
||||
form.html("<p>For convenience, your FreedomBox needs a name. It should be something short that doesn't contain spaces or punctuation. 'Willard' would be a good name. 'Freestyle McFreedomBox!!!' would not.</p>")
|
||||
form.text_input('Name your FreedomBox', id="hostname", value=hostname)
|
||||
form.html("<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>")
|
||||
form.text_input('Username:', id="username", value=username)
|
||||
form.text_input('Password:', id="password", type='password')
|
||||
form.html("<p>%(box_name)s uses cryptographic keys so it can prove its identity when talking to you. %(box_name)s can make a key for itself, but if one already exists (from a prior FreedomBox, for example), you can paste it below. This key should not be the same as your key because you are not your FreedomBox!</p>" % {'box_name':cfg.box_name})
|
||||
form.text_box("If you want, paste your box's key here.", id="box_key", value=box_key)
|
||||
form.hidden(name="submitted", value="True")
|
||||
form.submit("Box it up!")
|
||||
|
||||
main += form.render()
|
||||
return util.render_template(
|
||||
template="base",
|
||||
title=_("First Boot!"),
|
||||
main=main,
|
||||
sidebar_right=sidebar_right)
|
||||
return success
|
||||
|
||||
@staticmethod
|
||||
@cherrypy.expose
|
||||
def state1(self, message=None):
|
||||
def state1():
|
||||
"""
|
||||
State 1 is when we have a box name and key. In this state,
|
||||
our task is to provide a certificate and maybe to guide the
|
||||
@ -134,31 +174,11 @@ class FirstBoot(PagePlugin):
|
||||
|
||||
TODO: HTTPS failure in State 2 should returns to state 1.
|
||||
"""
|
||||
main = """
|
||||
<p>Welcome screen not completely implemented yet. Press <a
|
||||
href="../router">continue</a> to see the rest of the web interface.</p>
|
||||
|
||||
<ul>
|
||||
<li>TODO: explain all this cert stuff to the user.</li>
|
||||
<li>TODO: add instrux for installing certificate.</li>
|
||||
<li>TODO: add steps for after you have installed certificate.</li>
|
||||
<ul>
|
||||
"""
|
||||
# TODO complete first_boot handling
|
||||
# Make sure the user is not stuck on a dead end for now.
|
||||
with sqlite_db(cfg.store_file, table="firstboot", autocommit=True) as db:
|
||||
db['state']=5
|
||||
with sqlite_db(cfg.store_file, table='firstboot', autocommit=True) as \
|
||||
database:
|
||||
database['state'] = 5
|
||||
|
||||
# TODO: Update state to 2 and head there
|
||||
# with sqlite_db(cfg.store_file, table="firstboot", autocommit=True) as db:
|
||||
# db['state']=1
|
||||
# # TODO: switch to HTTPS
|
||||
# raise cherrypy.InternalRedirect('state1')
|
||||
|
||||
return util.render_template(
|
||||
template="base",
|
||||
title=_("Installing the Certificate"),
|
||||
main=main,
|
||||
sidebar_right=sidebar_right)
|
||||
|
||||
sidebar_right=_("""<strong>Getting Help</strong><p>We've done our best to make your FreedomBox easy to use. If you have questions during setup, there are a few places to turn for help. TODO: add links to such help.</p>""")
|
||||
return util.render_template(template='firstboot_state1',
|
||||
title=_('Installing the Certificate'))
|
||||
|
||||
@ -37,6 +37,11 @@ from plugin_mount import PagePlugin
|
||||
import util
|
||||
|
||||
|
||||
def get_hostname():
|
||||
"""Return the hostname"""
|
||||
return socket.gethostname()
|
||||
|
||||
|
||||
class TrimmedCharField(forms.CharField):
|
||||
"""Trim the contents of a CharField"""
|
||||
def clean(self, value):
|
||||
@ -122,7 +127,7 @@ class Configuration(PagePlugin):
|
||||
@staticmethod
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
return {'hostname': socket.gethostname(),
|
||||
return {'hostname': get_hostname(),
|
||||
'time_zone': util.slurp('/etc/timezone').rstrip()}
|
||||
|
||||
@staticmethod
|
||||
|
||||
28
modules/installed/templates/firstboot_sidebar.html
Normal file
28
modules/installed/templates/firstboot_sidebar.html
Normal file
@ -0,0 +1,28 @@
|
||||
{% 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 %}
|
||||
|
||||
<div class="well sidebar-nav">
|
||||
|
||||
<h3>Getting Help</h3>
|
||||
|
||||
<p>We've done our best to make your FreedomBox easy to use. If
|
||||
you have questions during setup, there are a few places to turn
|
||||
for help. TODO: add links to such help.</p>
|
||||
|
||||
</div>
|
||||
59
modules/installed/templates/firstboot_state0.html
Normal file
59
modules/installed/templates/firstboot_state0.html
Normal file
@ -0,0 +1,59 @@
|
||||
{% 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 main_block %}
|
||||
|
||||
<h2>Welcome to Your FreedomBox!</h2>
|
||||
|
||||
{% for severity, message in messages %}
|
||||
<div class='alert alert-{{ severity }}'>{{ message }}</div>
|
||||
{% endfor %}
|
||||
|
||||
<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>
|
||||
|
||||
<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 %}
|
||||
|
||||
{% include 'bootstrapform/field.html' with field=form.box_key %}
|
||||
|
||||
<input type="submit" class="btn-primary" value="Box it up!"/>
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar_right_block %}
|
||||
|
||||
{% include "firstboot_sidebar.html" %}
|
||||
|
||||
{% endblock %}
|
||||
41
modules/installed/templates/firstboot_state1.html
Normal file
41
modules/installed/templates/firstboot_state1.html
Normal file
@ -0,0 +1,41 @@
|
||||
{% 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 main_block %}
|
||||
|
||||
<p>Welcome screen not completely implemented yet. Press <a
|
||||
href="{{basehref }}/router">continue</a> to see the rest of the
|
||||
web interface.</p>
|
||||
|
||||
<ul>
|
||||
<li>TODO: explain all this cert stuff to the user.</li>
|
||||
<li>TODO: add instrux for installing certificate.</li>
|
||||
<li>TODO: add steps for after you have installed certificate.</li>
|
||||
</ul>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar_right_block %}
|
||||
|
||||
{% include "firstboot_sidebar.html" %}
|
||||
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user