import base64 import json import os from cryptography.fernet import Fernet from pathlib import Path from django.conf import settings from django.contrib.auth.models import User from django.db import models from troggle.core.models.troggle import DataIssue, Person, PersonExpedition from troggle.core.utils import current_expo """This imports the registered troggle users, who are nearly-all, but not quite, Persons. exceptions are "expo" and "expoadmin" which are created by the databaseReset.py import program. This can import unencrypted email addresses but never exports them. Passwords are only ever stored as hashes using the standard Django functions. """ todo = """ - Need to check/register with lists.wookware.org for email """ SUPER_USERS = ["philip-sargent", "wookey"] # list of userids who get the same rights as "expoadmin" i.e. the Django control panel USERS_FILE = "users.json" ENCRYPTED_DIR = "encrypted" def how_many_previous_expos(person): return PersonExpedition.objects.filter(person=person).exclude(expedition__year=current_expo()).count() def register_user(u, email, password=None, pwhash=None, fullname=""): """Create User and we may not have a Person to tie it to if it is a future caver. Do not use the lastname field, put the whole free text identification into firstname as this saves hassle and works with Wookey too """ try: if existing_user := User.objects.filter(username=u): # WALRUS # print(f" - deleting existing user '{existing_user[0]}' before importing") existing_user[0].delete() user = User.objects.create_user(u, email, first_name=fullname) if pwhash: user.password = pwhash elif password: user.set_password(password) # function creates hash and stores hash print(f" # hashing provided clear-text password {password} to make pwhash for user {u}") else: # user.set_password(None) # use Django special setting for invalid password, but then FAILS to send password reset email user.set_password("secret") # Why is the Django logic broken. Hmph. print(f" # setting INVALID password for user {u}, must be reset by password_reset") if u in SUPER_USERS: user.is_staff = True user.is_superuser = True print(f"** {u} is SUPER and can access everything on the Django control panel") else: user.is_staff = False user.is_superuser = False user.save() except Exception as e: print(f"Exception <{e}>") print(f"{len(User.objects.all())} users now in db:\n{User.objects.all()}") raise expoers = Person.objects.filter(slug=u) if len(expoers) == 1: person = expoers[0] person.user = user person.save() return user def get_encryptor(): key = settings.LONGTERM_SECRET_KEY # Django generated k = base64.urlsafe_b64encode(key.encode("utf8")[:32]) # make Fernet compatible f = Fernet(k) return f def load_users(): """These are the previously registered users of the troggle system. It loads then from JSON permanent storage into the database. It does allow unencrypted emails in the import, if labelled as such, but only stores encrypted emails. It refreshes the JSON every time this functionis called, to allow for password rotation in the future. """ PARSER_USERS = "_users" DataIssue.objects.filter(parser=PARSER_USERS).delete() f = get_encryptor() jsonfile = settings.EXPOWEB / ENCRYPTED_DIR / USERS_FILE jsonurl = "/" + str(Path(ENCRYPTED_DIR) / USERS_FILE) if not (jsonfile.is_file()): message = f" ! Users json file does not exist: '{jsonfile}'" DataIssue.objects.create(parser=PARSER_USERS, message=message) print(message) return None with open(jsonfile, 'r', encoding='utf-8') as json_f: message = "" try: registered_users_dict = json.load(json_f) except FileNotFoundError: message = f"File {jsonfile} not found!" except json.JSONDecodeError: message = f"Invalid JSON format! - JSONDecodeError for {jsonfile}" except Exception as e: message = f"! Troggle USERs. Failed to load {jsonfile} JSON file. Exception <{e}>" if message: print(message) DataIssue.objects.update_or_create(parser=PARSER_USERS, message=message, url=jsonurl) return None users_list = registered_users_dict["registered_users"] print(f" - {len(users_list)} users read from JSON") print(f"-- Registering users in database") for userdata in users_list: if not userdata["username"]: message = f"! user: BAD username for {userdata} in {jsonfile}" print(message) DataIssue.objects.update_or_create(parser=PARSER_USERS, message=message, url=jsonurl) continue else: if userdata["username"] in [ "expo", "expoadmin" ]: continue if "encrypted" not in userdata: userdata["encrypted"] = True try: u = userdata["username"] email = userdata["email"] if userdata["encrypted"]: email = f.decrypt(email).decode() print(f" - user: '{u} <{email}>' (decrypted)") except Exception as e: print(f"Exception <{e}>\n") formatted_json = json.dumps(userdata, indent=4) print(formatted_json) raise return None if "pwhash" in userdata: pwhash = userdata["pwhash"] new_user = register_user(u, email, pwhash=pwhash) else: new_user = register_user(u, email) # save_users() no need on initial parsing