1
0
Fork 0
forked from s3lph/matemat

Merge branch 'staging' into 'master'

User bootstrapping

See merge request s3lph/matemat!33
This commit is contained in:
s3lph 2018-08-15 18:28:07 +00:00
commit b478d4542b
7 changed files with 113 additions and 7 deletions

2
doc

@ -1 +1 @@
Subproject commit c3cbf1fdabc81c3e73d831655fe4d9c2d3d8330c Subproject commit 9449a6dc39843969d3b549f05848b5857c23cfa3

View file

@ -62,9 +62,22 @@ class MatematDatabase(object):
""" """
return self.db.transaction(exclusive=exclusive) return self.db.transaction(exclusive=exclusive)
def list_users(self) -> List[User]: def has_admin_users(self) -> bool:
"""
Check whether the instance has any admin users configured.
:return: True if there are admin users, false otherwise.
"""
with self.db.transaction(exclusive=False) as c:
c.execute('SELECT COUNT(user_id) FROM users WHERE is_admin = 1')
n, = c.fetchone()
return n > 0
def list_users(self, with_touchkey: bool = False) -> List[User]:
""" """
Return a list of users in the database. Return a list of users in the database.
:param with_touchkey: If true, only lists those users that have a touchkey set. Defaults to false.
:return: A list of users. :return: A list of users.
""" """
users: List[User] = [] users: List[User] = []
@ -72,7 +85,10 @@ class MatematDatabase(object):
for row in c.execute(''' for row in c.execute('''
SELECT user_id, username, email, is_admin, is_member, balance SELECT user_id, username, email, is_admin, is_member, balance
FROM users FROM users
'''): WHERE touchkey IS NOT NULL OR NOT :must_have_touchkey
''', {
'must_have_touchkey': with_touchkey
}):
# Decompose each row and put the values into a User object # Decompose each row and put the values into a User object
user_id, username, email, is_admin, is_member, balance = row user_id, username, email, is_admin, is_member, balance = row
users.append(User(user_id, username, balance, email, is_admin, is_member)) users.append(User(user_id, username, balance, email, is_admin, is_member))

View file

@ -4,6 +4,7 @@ import unittest
import crypt import crypt
from matemat.db import MatematDatabase from matemat.db import MatematDatabase
from matemat.db.primitives import User
from matemat.exceptions import AuthenticationError, DatabaseConsistencyError from matemat.exceptions import AuthenticationError, DatabaseConsistencyError
@ -43,10 +44,14 @@ class DatabaseTest(unittest.TestCase):
with self.db as db: with self.db as db:
users = db.list_users() users = db.list_users()
self.assertEqual(0, len(users)) self.assertEqual(0, len(users))
db.create_user('testuser', 'supersecurepassword', 'testuser@example.com', True, True) users = db.list_users(with_touchkey=True)
self.assertEqual(0, len(users))
testuser: User = db.create_user('testuser', 'supersecurepassword', 'testuser@example.com', True, True)
db.change_touchkey(testuser, '', 'touchkey', verify_password=False)
db.create_user('anothertestuser', 'otherpassword', 'anothertestuser@example.com', False, True) db.create_user('anothertestuser', 'otherpassword', 'anothertestuser@example.com', False, True)
db.create_user('yatu', 'igotapasswordtoo', 'yatu@example.com', False, False) db.create_user('yatu', 'igotapasswordtoo', 'yatu@example.com', False, False)
users = db.list_users() users = db.list_users()
users_with_touchkey = db.list_users(with_touchkey=True)
self.assertEqual(3, len(users)) self.assertEqual(3, len(users))
usercheck = {} usercheck = {}
for user in users: for user in users:
@ -64,6 +69,16 @@ class DatabaseTest(unittest.TestCase):
self.assertFalse(user.is_admin) self.assertFalse(user.is_admin)
usercheck[user.id] = 1 usercheck[user.id] = 1
self.assertEqual(3, len(usercheck)) self.assertEqual(3, len(usercheck))
self.assertEqual(1, len(users_with_touchkey))
self.assertEqual('testuser', users_with_touchkey[0].name)
def test_has_admin_users(self):
with self.db as db:
self.assertFalse(db.has_admin_users())
testuser: User = db.create_user('testuser', 'supersecurepassword', 'testuser@example.com', True, True)
self.assertTrue(db.has_admin_users())
db.change_user(testuser, agent=testuser, is_admin=False)
self.assertFalse(db.has_admin_users())
def test_login(self) -> None: def test_login(self) -> None:
with self.db as db: with self.db as db:

View file

@ -13,3 +13,4 @@ from .deposit import deposit
from .admin import admin from .admin import admin
from .moduser import moduser from .moduser import moduser
from .modproduct import modproduct from .modproduct import modproduct
from .userbootstrap import userbootstrap

View file

@ -1,6 +1,6 @@
from typing import Any, Dict, Union from typing import Any, Dict, Union
from matemat.webserver import pagelet, RequestArguments, PageletResponse, TemplateResponse from matemat.webserver import pagelet, RequestArguments, PageletResponse, TemplateResponse, RedirectResponse
from matemat.db import MatematDatabase from matemat.db import MatematDatabase
@ -30,7 +30,10 @@ def main_page(method: str,
authuser=user, products=products, authlevel=authlevel, authuser=user, products=products, authlevel=authlevel,
setupname=config['InstanceName']) setupname=config['InstanceName'])
else: else:
# If there are no admin users registered, jump to the admin creation procedure
if not db.has_admin_users():
return RedirectResponse('/userbootstrap')
# If no user is logged in, fetch the list of users and render the userlist template # If no user is logged in, fetch the list of users and render the userlist template
users = db.list_users() users = db.list_users(with_touchkey=True)
return TemplateResponse('userlist.html', return TemplateResponse('userlist.html',
users=users, setupname=config['InstanceName']) users=users, setupname=config['InstanceName'])

View file

@ -0,0 +1,43 @@
from typing import Any, Dict, Union
from matemat.db import MatematDatabase
from matemat.webserver import pagelet, RequestArguments, PageletResponse, RedirectResponse, TemplateResponse
from matemat.exceptions import HttpException
@pagelet('/userbootstrap')
def userbootstrap(method: str,
path: str,
args: RequestArguments,
session_vars: Dict[str, Any],
headers: Dict[str, str],
config: Dict[str, str]) \
-> Union[bytes, str, PageletResponse]:
"""
The page for creating a first admin user. To be used when the system is set up the first time, or when there are no
admin users left.
"""
with MatematDatabase(config['DatabaseFile']) as db:
# Redirect to main if there are still administrators registered
if db.has_admin_users():
return RedirectResponse('/')
# Process submission
if method == 'POST':
# Make sure all required values are present
if 'username' not in args or 'password' not in args or 'password2' not in args:
raise HttpException(400, 'Some arguments are missing')
username: str = str(args.username)
password: str = str(args.password)
password2: str = str(args.password2)
# The 2 passwords must match
if password != password2:
return RedirectResponse('/userbootstrap')
# Create the admin user
db.create_user(username, password, None, True, False)
# Redirect to the main page
return RedirectResponse('/')
# Requested via GET; show the user creation UI
else:
return TemplateResponse('userbootstrap.html',
setupname=config['InstanceName'])

View file

@ -0,0 +1,28 @@
{% extends "base.html" %}
{% block header %}
{# Show the setup name, as set in the config file, as page title followed by "Setup". Don't escape HTML entities. #}
<h1>{{ setupname|safe }} Setup</h1>
{{ super() }}
{% endblock %}
{% block main %}
{# Show a user creation form #}
Please create an admin user account
<form method="post" action="/userbootstrap" accept-charset="UTF-8">
<label for="username">Username: </label>
<input id="username" type="text" name="username"/><br/>
<label for="password">Password: </label>
<input id="password" type="password" name="password"/><br/>
<label for="password2">Repeat: </label>
<input id="password2" type="password" name="password2"/><br/>
<input type="submit" value="Create user">
</form>
{{ super() }}
{% endblock %}