diff --git a/.gitignore b/.gitignore index 89d2742..02c629d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ __pycache__ *.swp buranun.conf +*.db diff --git a/README.md b/README.md index e5d3036..9ec89cb 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,15 @@ Name Requirements ------------ -Buranun requires Beautiful Soup version 4 (bs4) for its html generation. +Buranun requires Beautiful Soup version 4 (bs4) for its html generation and +PassLib 1.7 or higher and either argon2\_cffi or argon2pure for password storage + +Setting up +---------- +Run `initialize.py` to generate database + +License +------- +Everything in the repo is under Unlicense / CC0 unless otherwise noted. + +`sqlshell.py` is based on code in [Python documentation](https://docs.python.org/3.6/library/sqlite3.html#sqlite3.complete_statement), which is under the [PSF license](https://docs.python.org/3.6/license.html) diff --git a/database.py b/database.py new file mode 100644 index 0000000..0257695 --- /dev/null +++ b/database.py @@ -0,0 +1,63 @@ +import enum +import random +import unicodedata +import sqlite3 + +from passlib.hash import argon2 + +class userstatus(enum.Enum): + # These will be stored in the database, be mindful of not changing the numbers + deleted = 0 + normal = 1 + admin = 2 + +csprng = random.SystemRandom() + +def add_user(userdb, *, username, password, email, parent, status): + """Add a user to the database""" + global csprgn + + assert type(username) == str + assert type(password) == str + assert type(email) == str + assert type(parent) == int or parent is None + assert status in userstatus + + # Generate a user ID. SQLite uses 64 bit signed ints, so generate at max 2⁶³-1 + userid = csprng.randrange(2**63) + + # First unicode normalize the password, then hash it with argon2 + password = unicodedata.normalize('NFKC', password) + password = argon2.hash(password) + + # Convert status into an int for storage + status = status.value + + # Add the user into the database + cursor = userdb.cursor() + cursor.execute('INSERT INTO users VALUES (?, ?, ?, ?, ?, ?, ?);', (userid, parent, status, password, username, email, '')) + userdb.commit() + +def initialize_userdb(userdb, admin_user, admin_password): + """Creates a bare-bones user database with only admin + This should never be run outside of the initialization script""" + + cursor = userdb.cursor() + + cursor.execute('''CREATE TABLE users ( + id integer NOT NULL PRIMARY KEY, + + parent integer, + status integer NOT NULL, + + password text NOT NULL, + + username text NOT NULL, + email text NOT NULL, + + comment text NOT NULL + );''') + + userdb.commit() + + add_user(userdb, username = admin_user, password = admin_password, email = '', parent = None, status = userstatus.admin) diff --git a/initialize.py b/initialize.py new file mode 100644 index 0000000..7b04e50 --- /dev/null +++ b/initialize.py @@ -0,0 +1,9 @@ +import sqlite3 + +import database + +if __name__ == '__main__': + with sqlite3.connect('user.db') as userdb: + username = input('admin username: ') + password = input('admin password: ') + database.initialize_userdb(userdb, username, password) diff --git a/sqlshell.py b/sqlshell.py new file mode 100644 index 0000000..13c1199 --- /dev/null +++ b/sqlshell.py @@ -0,0 +1,83 @@ +# Originally based on code from Python 3.6.5 documentation, which is +# Copyright © 2001-2018 Python Software Foundation; All Rights Reserved +# +# A copy of the PSF License agreement is provided below to make sure we're complying with it: +# +# 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and +# the Individual or Organization ("Licensee") accessing and otherwise using Python +# 3.6.5 software in source or binary form and its associated documentation. +# +# 2. Subject to the terms and conditions of this License Agreement, PSF hereby +# grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +# analyze, test, perform and/or display publicly, prepare derivative works, +# distribute, and otherwise use Python 3.6.5 alone or in any derivative +# version, provided, however, that PSF's License Agreement and PSF's notice of +# copyright, i.e., "Copyright © 2001-2018 Python Software Foundation; All Rights +# Reserved" are retained in Python 3.6.5 alone or in any derivative version +# prepared by Licensee. +# +# 3. In the event Licensee prepares a derivative work that is based on or +# incorporates Python 3.6.5 or any part thereof, and wants to make the +# derivative work available to others as provided herein, then Licensee hereby +# agrees to include in any such work a brief summary of the changes made to Python +# 3.6.5. +# +# 4. PSF is making Python 3.6.5 available to Licensee on an "AS IS" basis. +# PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF +# EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR +# WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE +# USE OF PYTHON 3.6.5 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. +# +# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.6.5 +# FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF +# MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.6.5, OR ANY DERIVATIVE +# THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. +# +# 6. This License Agreement will automatically terminate upon a material breach of +# its terms and conditions. +# +# 7. Nothing in this License Agreement shall be deemed to create any relationship +# of agency, partnership, or joint venture between PSF and Licensee. This License +# Agreement does not grant permission to use PSF trademarks or trade name in a +# trademark sense to endorse or promote products or services of Licensee, or any +# third party. +# +# 8. By copying, installing or otherwise using Python 3.6.5, Licensee agrees +# to be bound by the terms and conditions of this License Agreement. + +import sys +import sqlite3 + +con = sqlite3.connect(sys.argv[1]) +con.isolation_level = None +cur = con.cursor() + +buffer = '' + +while True: + if buffer == '': + prompt = 'sql> ' + else: + prompt = '... ' + + try: + line = input(prompt) + except EOFError: + break + + buffer += line + + if sqlite3.complete_statement(buffer): + try: + buffer = buffer.strip() + cur.execute(buffer) + + if buffer.lstrip().upper().startswith('SELECT'): + print(cur.fetchall()) + + except sqlite3.Error as e: + print("An error occurred:", e.args[0]) + + buffer = "" + +con.close()