#!/usr/bin/env python3 """ Co-routines for handling various forms in Buckler. """ import json 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.""" if not request['session']['admin']: return {'main': "You do not have permission to do that."} data = await request.post() data = json.loads(data['perms']) pluses = [] minuses = [] for user_id, perms in data.items(): for app_id, value in perms.items(): if value: # TODO: check for TypeError pluses.append((int(user_id), int(app_id))) else: minuses.append((int(user_id), int(app_id))) async with request.app['pool'].acquire() as conn: await conn.executemany( "INSERT INTO app_user (user_id, app_id) VALUES ($1, $2) " "ON CONFLICT ON CONSTRAINT app_user_pkey DO NOTHING", pluses) await conn.executemany( "DELETE FROM app_user WHERE user_id = $1 AND app_id = $2", minuses) return {} async def new_app(request): """Allows an admin to add a new app to be managed by Buckler.""" if not request['session']['admin']: return {'main': "You do not have permission to do that."} 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 {}