From a4b50928fc22ae3aaba617befc23a079c4da6d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20H=C3=B8rl=C3=BCck=20Berg?= <36937807+henrikhorluck@users.noreply.github.com> Date: Wed, 28 Feb 2024 17:54:20 +0100 Subject: [PATCH] Update export script - Do not export inactive users, they have never logged in. - Set OW4 userid on export - Support exporting without password for updating a previous export - Fix off-by one in number of scripts --- scripts/management/commands/auth0_export.py | 48 +++++++++++++-------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/scripts/management/commands/auth0_export.py b/scripts/management/commands/auth0_export.py index 394f5b599..376bfd72d 100644 --- a/scripts/management/commands/auth0_export.py +++ b/scripts/management/commands/auth0_export.py @@ -6,7 +6,7 @@ from django.core.management.base import BaseCommand -from apps.authentication.models import OnlineUser +from apps.authentication.models import Email, OnlineUser ws = re.compile(r"\s+") @@ -36,10 +36,12 @@ def extract_phone_number(u: OnlineUser) -> Optional[str]: def create_auth0_user(u: OnlineUser): if not u.has_usable_password: - print(f"Skipping {u}, no usable password") + print(f"Skipping {u.pk}, no usable password") return None if u.email is None or len(u.email) == 0: - print(f"Skipping {u}, no email") + emails = Email.objects.filter(user=u) + # print(u.email_user) + print(f"Skipping {u.pk}, no email or? {emails}") return None # if u.auth0_subject is not None: # print(f"Skipping {u}, already migrated") @@ -48,7 +50,7 @@ def create_auth0_user(u: OnlineUser): try: algorithm, iterations, salt, hash = u.password.split("$", 3) except ValueError as e: - print(f"{e=}\n{u=}\n{u.password=}") + print(f"{e}\n{u.pk=}\n{u.password=}") return None # thank you https://community.auth0.com/t/wrong-password-for-imported-users-from-django/61105 @@ -58,24 +60,34 @@ def create_auth0_user(u: OnlineUser): # we probably only use pbkdf2_sha256, in auth0 they use - algorithm = algorithm.replace("_", "-") - id = str(uuid4()) - u.auth0_subject = f"auth0|{id}" + user_previously_exported = True + if not u.auth0_subject: + user_previously_exported = False + id = str(uuid4()) + u.auth0_subject = f"auth0|{id}" auth0_user = { - "user_id": id, + "user_id": u.auth0_subject.split("|")[1], "email": u.email, "email_verified": u.is_active, "given_name": u.first_name, "family_name": u.last_name, - "name": f"{u.first_name} {u.last_name}", - "custom_password_hash": { + "user_metadata": {}, + "app_metadata": { + "ow4_userid": u.pk, + }, + } + + if not user_previously_exported: + # we do not want to export passwords of existing users in auth0 + # auth0 then just errors out, and users might not remember their passwords + auth0_user["custom_password_hash"] = { "algorithm": "pbkdf2", "hash": { "encoding": "utf-8", "value": f"$pbkdf2-sha256$i={iterations},l=32${salt}${hash}", }, - }, - } + } if len(u.first_name) == 0: del auth0_user["given_name"] @@ -84,21 +96,23 @@ def create_auth0_user(u: OnlineUser): del auth0_user["family_name"] if num := extract_phone_number(u): - auth0_user["mfa_factors"] = [{"phone": {"value": num}}] + auth0_user["user_metadata"]["phone"] = num + + if len(auth0_user["user_metadata"]) == 0: + del auth0_user["user_metadata"] return (u, auth0_user) class Command(BaseCommand): def handle(self, *args, **options): - users = [ - create_auth0_user(u) for u in OnlineUser.objects.iterator(chunk_size=100) - ] + qs = OnlineUser.objects.filter(is_active=True) + users = [create_auth0_user(u) for u in qs.iterator(chunk_size=100)] users = [u for u in users if u is not None] N = 700 - for i in range(int(OnlineUser.objects.count() / 700)): + for i in range(int(qs.count() / N) + 1): chunk = users[i * N : i * N + N] OnlineUser.objects.bulk_update([u for (u, _) in chunk], ["auth0_subject"]) file = json.dumps([a0u for (_, a0u) in chunk]) - with open(f"auth0_users_staging_{i}.json", "w") as f: + with open(f"auth0_users_prod_{i}.json", "w") as f: f.write(file)