Buckler/forms.py
2020-04-09 07:58:38 -04:00

141 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""
Co-routines for handling various forms in Buckler.
"""
from passlib.hash import argon2
import tools
async def invite_user(request):
"""Allows an admin to invite a new user."""
if not request['session']['admin']:
return {'main': "You do not have permission to do that."}
data = await request.post()
email = data.get('email')
if not email:
return {'invite_user': "This field is required."}
# TODO: validate email address
await tools.send_invite(request, email)
return {}
async def change_user_perms(request):
"""Allows an admin to change user permissions."""
data = await request.post()
pluses = []
for key, value in data.items():
if key == 'form_name':
continue
username, _, app_name = key.partition('-')
pluses.append((username, app_name))
async with request.app['pool'].acquire() as conn:
apps = await conn.fetch(
"SELECT id, name FROM app_info")
users = {p[0] for p in pluses}
minuses = []
for user in users:
for app in apps:
minuses.append((user, app['name']))
minuses = [m for m in minuses if m not in pluses]
await conn.executemany(
"INSERT INTO app_user (user_id, app_id) "
"VALUES ((SELECT id FROM user_info WHERE username = $1), "
"(SELECT id FROM app_info WHERE name = $2)) "
"ON CONFLICT ON CONSTRAINT app_user_pkey DO NOTHING",
pluses)
await conn.executemany(
"DELETE FROM app_user "
"WHERE user_id = (SELECT id FROM user_info WHERE username = $1) "
"AND app_id = (SELECT id FROM app_info WHERE name = $2)",
minuses)
return {}
async def new_app(request):
"""Allows an admin to add a new app to be managed by Buckler."""
data = await request.post()
app_name = data.get('app_name')
app_url = data.get('app_url')
app_key = data.get('app_key')
key_hash = argon2.hash(app_key)
async with request.app['pool'].acquire() as conn:
await conn.execute(
"INSERT INTO app_info (name, url, key_hash) "
"VALUES ($1, $2, $3)",
app_name, app_url, key_hash)
return {}
async def change_password(request):
"""Allows a user to change their password."""
errors = {}
data = await request.post()
current_pw = data.get('current_password')
new_pw = data.get('new_password')
verify_pw = data.get('verify_password')
if not all((current_pw, new_pw, verify_pw)):
errors['change_password'] = "All fields are required."
return errors
if not new_pw == verify_pw:
errors['change_password'] = "Passwords do not match."
return errors
async with request.app['pool'].acquire() as conn:
pw_hash = await conn.fetchrow(
"SELECT password_hash FROM user_info WHERE id = $1",
request['session']['id'])
if not argon2.verify(current_pw, pw_hash['password_hash']):
errors['change_password'] = "Invalid password."
return errors
h = argon2.hash(new_pw)
await conn.execute(
"UPDATE user_info SET password_hash = $1 WHERE id = $2",
h, request['session']['id'])
return errors
async def delete_key(request):
"""Allows a user to delete a security key."""
data = await request.post()
async with request.app['pool'].acquire() as conn:
for key, value in data.items():
key_id = key.replace('fido-', '')
if not key_id:
continue
try:
key_id = int(key_id)
except ValueError:
continue
if value != 'on':
continue
await conn.execute(
"DELETE FROM user_credential "
"WHERE id = $1 AND user_id = $2",
key_id, request['session']['id'])
return {}
async def delete_session(request):
"""Allows a user to delete a session ."""
data = await request.post()
async with request.app['pool'].acquire() as conn:
for key, value in data.items():
session_id = key.replace('session-', '', 1)
if not session_id:
continue
if value != 'on':
continue
await conn.execute(
"DELETE FROM user_session "
"WHERE id = $1 AND user_id = $2",
session_id, request['session']['id'])
return {}