mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
fix(accounts): an admin still on the password "admin" must change it
Every upstream release and this fork's up to v2.0.0 created the first user as admin/admin. The first-login password change only covered the password the panel generates itself (data/admin_password), so an upgraded installation whose admin never changed the default kept a working admin/admin login with no prompt, on the pages and through API tokens. Migration accounts.0013 marks a user named admin whose password is "admin" with must_change_password: the next login goes to the password change form, and the API refuses the user's tokens until then. Only that one name is checked, so the migration hashes one password at most. Found by upgrading an installation of the upstream release of September 2026 (installer and Docker) to this version: admin/admin still signed in.
This commit is contained in:
parent
402a109633
commit
21bd56a9f9
3 changed files with 53 additions and 0 deletions
|
|
@ -769,6 +769,7 @@ What an update from an older version changes for operators and users:
|
|||
- Failed logins lock a user (django-axes). The limit and lock time are on the Settings page; unlock with `python3 manage.py axes_reset_username <username>`.
|
||||
- Changing one's own password is the "Can change password" permission (per user or group; new users have it); `SHOW_PROFILE_EDIT_PASSWORD` is gone.
|
||||
- The admin's first-login password change survives losing `data/admin_password` (migration `accounts.0007`); run `migrate` as a user who can read that file.
|
||||
- An `admin` account that still has the password `admin` (the first user of versions up to v2.0.0) must change it at its next login, and its API tokens are refused until then (migration `accounts.0013`).
|
||||
- A `settings.py` kept from an older version must match the template (apps, middleware, the SQLite `OPTIONS`, the REST framework, knox, axes and password settings): rebuild it from the template and carry your own values over, as step 2 of the update does.
|
||||
- The VNC console connects to `/novncd/` on the panel's own address through nginx, and the serial console to `/socket.io/`: they work on HTTPS and behind another proxy without settings. `WS_PUBLIC_PORT`, `WS_PUBLIC_HOST`, `WS_PUBLIC_PATH`, `WS_CERT` and `SOCKETIO_PUBLIC_HOST`/`_PORT`/`_PATH` are gone; remove them from `docker-compose.override.yml`, `/etc/default/webvirtcloud` or a kept `settings.py`, set `WS_HOST = "127.0.0.1"` in a kept `settings.py`, and close port 6080 in the firewall.
|
||||
- Installer and manual installations run the panel as systemd units (`webvirt-cloud`, `webvirt-novncd`, `webvirt-socketiod`, `webvirt-worker`, `webvirt-backup.timer`) and computes run `webvirt-gstfsd`, instead of supervisor; see [Moving from supervisor to systemd](#moving-from-supervisor-to-systemd). Service settings go into `/etc/default/webvirtcloud`, service output into the journal (`journalctl -u webvirt-cloud`); the app log stays in `data/`. novncd and socketiod run sandboxed (read-only system directories, no `/home`).
|
||||
|
|
|
|||
19
accounts/migrations/0013_default_admin_password.py
Normal file
19
accounts/migrations/0013_default_admin_password.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import check_password
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def flag_default_admin_password(apps, schema_editor):
|
||||
"""Versions up to v2.0.0 created the first user as admin/admin. An
|
||||
installation that never changed it must replace it at the next login."""
|
||||
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(username="admin"):
|
||||
if check_password("admin", user.password):
|
||||
Attributes.objects.using(alias).update_or_create(user_id=user.pk, defaults={"must_change_password": True})
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [("accounts", "0012_user_instance_constraint")]
|
||||
operations = [migrations.RunPython(flag_default_admin_password, migrations.RunPython.noop)]
|
||||
|
|
@ -114,3 +114,36 @@ class FirstLoginPasswordChangeTestCase(TestCase):
|
|||
with patch("pathlib.Path.read_text", side_effect=PermissionError("unreadable")):
|
||||
with self.assertRaisesRegex(RuntimeError, "run migrate as its owner"):
|
||||
migration.flag_legacy_generated_password(None, None)
|
||||
|
||||
|
||||
class DefaultAdminPasswordMigrationTestCase(TestCase):
|
||||
"""An installation of a version up to v2.0.0 still signed in as admin/admin."""
|
||||
|
||||
def admin_with(self, password):
|
||||
admin, _ = get_user_model().objects.get_or_create(username="admin", defaults={"is_superuser": True})
|
||||
admin.set_password(password)
|
||||
admin.save()
|
||||
UserAttributes.objects.filter(user=admin).delete() # the migration also makes them
|
||||
return admin
|
||||
|
||||
def migrate(self):
|
||||
import importlib
|
||||
from types import SimpleNamespace
|
||||
from django.apps import apps
|
||||
from django.db import connection
|
||||
migration = importlib.import_module("accounts.migrations.0013_default_admin_password")
|
||||
migration.flag_default_admin_password(apps, SimpleNamespace(connection=connection))
|
||||
|
||||
def test_the_default_admin_password_must_be_changed(self):
|
||||
admin = self.admin_with("admin")
|
||||
self.migrate()
|
||||
self.assertTrue(UserAttributes.objects.get(user=admin).must_change_password)
|
||||
self.client.force_login(admin)
|
||||
self.assertRedirects(self.client.get(reverse("instances:index")), reverse("accounts:change_password"),
|
||||
fetch_redirect_response=False)
|
||||
|
||||
def test_a_changed_admin_password_is_left_alone(self):
|
||||
admin = self.admin_with("Changed-pw-2026")
|
||||
UserAttributes.objects.create(user=admin)
|
||||
self.migrate()
|
||||
self.assertFalse(UserAttributes.objects.get(user=admin).must_change_password)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue