From 1dd87fc6f0bab91ca959abbad6b9c34f3a81ff62 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Thu, 23 Apr 2020 20:42:08 -0700 Subject: [PATCH] Move user creation code to new submodule --- amanuensis/user.py | 56 --------------------------- amanuensis/user/__init__.py | 5 +++ amanuensis/user/signup.py | 77 +++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 56 deletions(-) create mode 100644 amanuensis/user/__init__.py create mode 100644 amanuensis/user/signup.py diff --git a/amanuensis/user.py b/amanuensis/user.py index 1777524..bde6532 100644 --- a/amanuensis/user.py +++ b/amanuensis/user.py @@ -75,59 +75,3 @@ class AnonymousUserModel(AnonymousUserMixin): def in_lexicon(self, lexicon): return False - -def valid_username(username): - """ - A valid username is at least three characters long and composed solely of - alpahnumerics, dashes, and underscores. Additionally, usernames may not - be 32 hex digits, since that may be confused for an internal id. - """ - length_and_characters = re.match(r"^[A-Za-z0-9-_]{3,}$", username) - is_a_guid = re.match(r"^[A-Za-z0-9]{32}$", username) - return length_and_characters and not is_a_guid - - -def valid_email(email): - """Vaguely RFC2822 email verifier""" - atom = r"[0-9A-Za-z!#$%&'*+-/=?^_`{|}~]{1,}" - dotatom = atom + r"(\." + atom + r")*" - addrspec = "^" + dotatom + "@" + dotatom + "$" - return re.match(addrspec, email) - - -def create_user(username, displayname, email): - """ - Creates a new user - """ - # Validate arguments - if not valid_username(username): - raise ValueError("Invalid username: '{}'".format(username)) - if email and not valid_email(email): - raise ValueError("Invalid email: '{}'".format(email)) - - # Create the user directory and initialize it with a blank user - uid = uuid.uuid4().hex - user_dir = prepend("user", uid) - os.mkdir(user_dir) - with get_stream("user.json") as s: - with open(prepend(user_dir, 'config.json'), 'wb') as f: - f.write(s.read()) - - # Fill out the new user - with json_rw(user_dir, 'config.json') as cfg: - cfg.uid = uid - cfg.username = username - cfg.displayname = displayname - cfg.email = email - cfg.created = int(time.time()) - - # Update the index with the new user - with json_rw('user', 'index.json') as index: - index[username] = uid - - # Set a temporary password - temp_pw = os.urandom(32).hex() - u = UserModel.by(uid=uid) - u.set_password(temp_pw) - - return u, temp_pw diff --git a/amanuensis/user/__init__.py b/amanuensis/user/__init__.py new file mode 100644 index 0000000..67d4d8e --- /dev/null +++ b/amanuensis/user/__init__.py @@ -0,0 +1,5 @@ +from amanuensis.user.signup import create_user + +__all__ = [member.__name__ for member in [ + create_user +]] diff --git a/amanuensis/user/signup.py b/amanuensis/user/signup.py new file mode 100644 index 0000000..ff24577 --- /dev/null +++ b/amanuensis/user/signup.py @@ -0,0 +1,77 @@ +""" +Submodule encapsulating functionality pertaining to creating users in +an Amanuensis instance. +""" +import os +import re +import time +from typing import Tuple +import uuid + +from amanuensis.config import RootConfigDirectoryContext +from amanuensis.errors import ArgumentError +from amanuensis.models import ModelFactory, UserModel +from amanuensis.resources import get_stream + + +def valid_username(username: str) -> bool: + """ + A valid username is at least three characters long and composed solely of + alpahnumerics, dashes, and underscores. Additionally, usernames may not + be 32 hex digits, since that may be confused for an internal id. + """ + length_and_characters = re.match(r'^[A-Za-z0-9-_]{3,}$', username) + is_a_guid = re.match(r'^[A-Za-z0-9]{32}$', username) + return bool(length_and_characters and not is_a_guid) + + +def valid_email(email: str) -> bool: + """Vaguely RFC2822 email verifier""" + atom = r"[0-9A-Za-z!#$%&'*+-/=?^_`{|}~]{1,}" + dotatom = atom + r"(\." + atom + r")*" + addrspec = '^' + dotatom + '@' + dotatom + '$' + return bool(re.match(addrspec, email)) + + +def create_user( + root: RootConfigDirectoryContext, + model_factory: ModelFactory, + username: str, + displayname: str, + email: str) -> Tuple[UserModel, str]: + """ + Creates a new user + """ + # Validate arguments + if not valid_username(username): + raise ArgumentError('Invalid username: "{}"'.format(username)) + if email and not valid_email(email): + raise ArgumentError('Invalid email: "{}"'.format(email)) + + # Create the user directory and config file + uid: str = uuid.uuid4().hex + user_dir: str = os.path.join(root.user.path, uid) + os.mkdir(user_dir) + with get_stream('user.json') as s: + path: str = os.path.join(user_dir, 'config.json') + with open(path, 'wb') as f: + f.write(s.read()) + + # Create the user index entry + with root.user.edit_index() as index: + index[username] = uid + + # Fill out the new user + with root.user[uid].edit_config() as cfg: + cfg.uid = uid + cfg.username = username + cfg.displayname = displayname + cfg.email = email + cfg.created = int(time.time()) + + # Load the user model and set a temporary password + temporary_password = os.urandom(32).hex() + user = model_factory.user(uid) + user.set_password(temporary_password) + + return user, temporary_password