2020-01-09 06:48:00 +00:00
|
|
|
import os
|
2020-01-04 01:07:28 +00:00
|
|
|
import re
|
2020-01-09 06:48:00 +00:00
|
|
|
import time
|
|
|
|
import uuid
|
|
|
|
|
2020-01-13 20:48:37 +00:00
|
|
|
from flask_login import UserMixin
|
2020-01-09 06:48:00 +00:00
|
|
|
from werkzeug.security import generate_password_hash, check_password_hash
|
|
|
|
|
2020-01-23 23:13:34 +00:00
|
|
|
from amanuensis.errors import InternalMisuseError, MissingConfigError, IndexMismatchError
|
|
|
|
from amanuensis.config import prepend, json_ro, json_rw
|
|
|
|
from amanuensis.resources import get_stream
|
|
|
|
from amanuensis.lexicon.manage import get_all_lexicons
|
2020-01-04 01:07:28 +00:00
|
|
|
|
2020-01-18 20:39:54 +00:00
|
|
|
class UserModel(UserMixin):
|
|
|
|
@staticmethod
|
|
|
|
def by(uid=None, name=None):
|
|
|
|
"""
|
|
|
|
Gets the UserModel with the given uid or username
|
|
|
|
|
|
|
|
If the uid or name simply does not match an existing user, returns
|
|
|
|
None. If the uid matches the index but there is something wrong with
|
|
|
|
the user's config, raises an error.
|
|
|
|
"""
|
|
|
|
if uid and name:
|
|
|
|
raise InternalMisuseError("uid and name both specified to UserModel.by()")
|
|
|
|
if not uid and not name:
|
|
|
|
raise ValueError("One of uid or name must be not None")
|
|
|
|
if not uid:
|
2020-01-23 23:13:34 +00:00
|
|
|
with json_ro('user', 'index.json') as index:
|
2020-01-18 20:39:54 +00:00
|
|
|
uid = index.get(name)
|
|
|
|
if not uid:
|
|
|
|
return None
|
2020-01-23 23:13:34 +00:00
|
|
|
if not os.path.isdir(prepend('user', uid)):
|
2020-01-18 20:39:54 +00:00
|
|
|
raise IndexMismatchError("username={} uid={}".format(name, uid))
|
2020-01-23 23:13:34 +00:00
|
|
|
if not os.path.isfile(prepend('user', uid, 'config.json')):
|
2020-01-18 20:39:54 +00:00
|
|
|
raise MissingConfigError("uid={}".format(uid))
|
|
|
|
return UserModel(uid)
|
|
|
|
|
|
|
|
def __init__(self, uid):
|
|
|
|
"""User model initializer, assume all checks were done by by()"""
|
2020-01-17 07:49:37 +00:00
|
|
|
self.id = str(uid) # Flask-Login checks for this
|
2020-01-23 23:13:34 +00:00
|
|
|
self.config_path = prepend('user', uid, 'config.json')
|
|
|
|
with json_ro(self.config_path) as j:
|
2020-01-13 20:48:37 +00:00
|
|
|
self.config = j
|
|
|
|
|
2020-01-16 01:16:09 +00:00
|
|
|
def __getattr__(self, key):
|
|
|
|
if key not in self.config:
|
|
|
|
raise AttributeError(key)
|
|
|
|
return self.config.get(key)
|
|
|
|
|
2020-01-09 21:28:15 +00:00
|
|
|
def set_password(self, pw):
|
|
|
|
h = generate_password_hash(pw)
|
2020-01-23 23:13:34 +00:00
|
|
|
with json_rw(self.config_path) as j:
|
2020-01-09 21:28:15 +00:00
|
|
|
j['password'] = h
|
|
|
|
|
|
|
|
def check_password(self, pw):
|
2020-01-23 23:13:34 +00:00
|
|
|
with json_ro(self.config_path) as j:
|
2020-01-09 21:28:15 +00:00
|
|
|
return check_password_hash(j['password'], pw)
|
|
|
|
|
2020-01-20 15:52:41 +00:00
|
|
|
def lexicons_in(self):
|
|
|
|
return [
|
|
|
|
lex
|
2020-01-23 23:13:34 +00:00
|
|
|
for lex in get_all_lexicons()
|
2020-01-20 15:52:41 +00:00
|
|
|
if self.id in lex.join.joined
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2020-01-04 01:07:28 +00:00
|
|
|
def valid_username(username):
|
|
|
|
return re.match(r"^[A-Za-z0-9-_]{3,}$", username) is not None
|
|
|
|
|
|
|
|
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):
|
2020-01-17 07:49:37 +00:00
|
|
|
"""
|
|
|
|
Creates a new user
|
|
|
|
"""
|
|
|
|
# Validate arguments
|
2020-01-13 20:48:37 +00:00
|
|
|
if not valid_username(username):
|
|
|
|
raise ValueError("Invalid username: '{}'".format(username))
|
|
|
|
if not valid_email(email):
|
|
|
|
raise ValueError("Invalid email: '{}'".format(email))
|
2020-01-17 07:49:37 +00:00
|
|
|
|
|
|
|
# Create the user directory and initialize it with a blank user
|
2020-01-09 06:48:00 +00:00
|
|
|
uid = uuid.uuid4().hex
|
2020-01-23 23:13:34 +00:00
|
|
|
user_dir = prepend("user", uid)
|
2020-01-17 07:49:37 +00:00
|
|
|
os.mkdir(user_dir)
|
2020-01-23 23:13:34 +00:00
|
|
|
with get_stream("user.json") as s:
|
|
|
|
with open(prepend(user_dir, 'config.json'), 'wb') as f:
|
2020-01-17 07:49:37 +00:00
|
|
|
f.write(s.read())
|
|
|
|
|
|
|
|
# Fill out the new user
|
2020-01-23 23:13:34 +00:00
|
|
|
with json_rw(user_dir, 'config.json') as cfg:
|
2020-01-21 01:23:40 +00:00
|
|
|
cfg.uid = uid
|
|
|
|
cfg.username = username
|
|
|
|
cfg.displayname = displayname
|
|
|
|
cfg.email = email
|
|
|
|
cfg.created = int(time.time())
|
2020-01-17 07:49:37 +00:00
|
|
|
|
|
|
|
# Update the index with the new user
|
2020-01-23 23:13:34 +00:00
|
|
|
with json_rw('user', 'index.json') as index:
|
2020-01-17 07:49:37 +00:00
|
|
|
index[username] = uid
|
|
|
|
|
|
|
|
# Set a temporary password
|
2020-01-12 06:36:39 +00:00
|
|
|
temp_pw = os.urandom(32).hex()
|
2020-01-18 20:39:54 +00:00
|
|
|
u = UserModel.by(uid=uid)
|
2020-01-12 06:36:39 +00:00
|
|
|
u.set_password(temp_pw)
|
2020-01-17 07:49:37 +00:00
|
|
|
|
2020-01-12 06:36:39 +00:00
|
|
|
return u, temp_pw
|