1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00

feat(admin): a password an administrator sets can be one the user must change

The administrator's password form has "The user must change it at their
next login". With it, the user is sent to the change form until they
choose their own, as the generated admin password already does.
This commit is contained in:
catborise 2026-10-25 22:26:23 +03:00
parent 0e8130f1b0
commit 24896aaecd
4 changed files with 33 additions and 7 deletions

View file

@ -7,7 +7,8 @@ ALLOWED_VIEWS = {"accounts:change_password", "accounts:logout", "accounts:login"
class ForcePasswordChangeMiddleware:
"""Send users who still have the generated admin password to the change form."""
"""Send users who must change their password (the generated admin one, or one
an administrator set) to the change form."""
def __init__(self, get_response):
self.get_response = get_response

View file

@ -1,5 +1,6 @@
from django import forms
from django.contrib.auth import password_validation
from django.contrib.auth.forms import AdminPasswordChangeForm
from django.contrib.auth.models import Group, User
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
@ -123,3 +124,12 @@ class UserAttributesForm(forms.ModelForm):
class Meta:
model = UserAttributes
exclude = ["user", "can_clone_instances", "must_change_password"]
class AdminPasswordForm(AdminPasswordChangeForm):
"""A password an administrator sets; the user may have to change it at their next login."""
must_change_password = forms.BooleanField(label=_("The user must change it at their next login"), required=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["must_change_password"] = self.fields.pop("must_change_password") # last, after Django's own

View file

@ -1,5 +1,6 @@
"""The Users pages: an administrator cannot lock their own account out, and
the user form says what its fields mean."""
"""The Users pages: an administrator cannot lock their own account out, the
user form says what its fields mean, and a password set by an administrator
can be one the user must change."""
from accounts.models import UserAttributes
from django.contrib.auth import get_user_model
@ -65,3 +66,16 @@ class UserPagesTests(TestCase):
page = html.fromstring(self.client.get(reverse("admin:user_list")).content)
(row,) = [r for r in page.xpath("//tbody/tr") if r.xpath("td")[0].text_content().strip() == "up-alice"]
self.assertEqual(row.xpath("td")[2].text_content().strip(), "never")
def test_a_password_set_by_an_administrator_can_be_one_to_change(self):
url = reverse("admin:user_update_password", args=[self.alice.pk])
self.client.post(url, {"password1": "New-pw-4567x", "password2": "New-pw-4567x", "must_change_password": "on"})
self.assertTrue(UserAttributes.objects.get(user=self.alice).must_change_password)
# the user is sent to the change form until they choose their own
self.alice.refresh_from_db() # the new password's hash, or the session is not hers
self.client.force_login(self.alice)
self.assertRedirects(self.client.get(reverse("instances:index")), reverse("accounts:change_password"),
fetch_redirect_response=False)
self.client.force_login(self.admin)
self.client.post(url, {"password1": "New-pw-4567y", "password2": "New-pw-4567y"})
self.assertFalse(UserAttributes.objects.get(user=self.alice).must_change_password)

View file

@ -5,7 +5,6 @@ from appsettings.settings import app_settings
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import AdminPasswordChangeForm
from django.contrib.auth.models import Group, User
from django.core.paginator import Paginator
from django.db import transaction
@ -185,10 +184,12 @@ def user_update(request, pk):
def user_update_password(request, pk):
user = get_object_or_404(User, pk=pk)
if request.method == "POST":
form = AdminPasswordChangeForm(user, request.POST)
form = forms.AdminPasswordForm(user, request.POST)
if form.is_valid():
with audit(request, None, f"User {user.username}", "set password"):
with audit(request, None, f"User {user.username}", "set password"), transaction.atomic():
user = form.save()
UserAttributes.objects.update_or_create(
user=user, defaults={"must_change_password": form.cleaned_data["must_change_password"]})
update_session_auth_hash(request, user) # Important!
messages.success(
request, _("Password changed for %(user)s") % {"user": user.username}
@ -197,7 +198,7 @@ def user_update_password(request, pk):
else:
messages.error(request, _("Wrong Data Provided"))
else:
form = AdminPasswordChangeForm(user)
form = forms.AdminPasswordForm(user)
return render(
request,