1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-11 04:36:48 +00:00
webvirtcloud/accounts/migrations/0007_userattributes_must_change_password.py
catborise 6dabd69445 fix(accounts): first-login password change is stored, not in the session
The forced change of the generated admin password was a session flag
set at login when the password still matched data/admin_password. A
new session after the file was gone bypassed it.

- UserAttributes.must_change_password (migration 0007) holds it; the
  admin created with a generated password gets it, and a password
  change clears it. ForcePasswordChangeMiddleware reads it.
- A system check (accounts.E001) stops the server unless the middleware
  follows AuthenticationMiddleware; the README has the settings change.
- If the generated password cannot be stored privately, provisioning
  stops without printing it and without creating the admin (one
  transaction for the user and its attributes).
- The admin is provisioned on any real migrate run while no user
  exists, so running migrate again after a failure creates it; flush
  (plan=None) and rollbacks of accounts are skipped.
- The post_migrate receivers check the migration state for the models
  they use, so a targeted migrate on a fresh database (e.g. migrate
  appsettings) no longer fails with 'no such table'.
2026-10-04 01:08:11 +03:00

44 lines
1.6 KiB
Python

from pathlib import Path
from django.conf import settings
from django.contrib.auth.hashers import check_password
from django.db import migrations, models
def flag_legacy_generated_password(apps, schema_editor):
"""Preserve first-login enforcement for installations with a password file."""
path = Path(settings.BASE_DIR) / "data" / "admin_password"
try:
generated = path.read_text().strip()
except FileNotFoundError:
return
except OSError as err:
raise RuntimeError(
f"Cannot read {path}; run migrate as its owner to preserve first-login enforcement."
) from err
if not generated:
return
alias = schema_editor.connection.alias
User = apps.get_model(*settings.AUTH_USER_MODEL.split("."))
Attributes = apps.get_model("accounts", "UserAttributes")
for user in User.objects.using(alias).filter(is_superuser=True):
if check_password(generated, user.password):
attributes, _ = Attributes.objects.using(alias).get_or_create(
user_id=user.pk
)
attributes.must_change_password = True
attributes.save(using=alias, update_fields=["must_change_password"])
class Migration(migrations.Migration):
dependencies = [
("accounts", "0006_alter_userattributes_id_alter_userinstance_id_and_more")
]
operations = [
migrations.AddField(
model_name="userattributes",
name="must_change_password",
field=models.BooleanField(default=False),
),
migrations.RunPython(flag_legacy_generated_password, migrations.RunPython.noop),
]