summaryrefslogtreecommitdiffstats
path: root/parsers/users.py
blob: f3947ff589c966bafd51b41c51077f06b2871050 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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 = """  
- Not fully tested, needs experience
"""

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
    """
    print(f" - {u} {fullname=}")
    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")
        user.is_staff = False
        user.is_superuser = False
        user.save()
        print(f" - receated and reset user '{user}'")
    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.
    """
    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