mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
- The administrator's password form took "password-based authentication: Disabled" with "must change it at the next login": the change form asks for the old password, which the user then never had, so they could not get past it. That combination is refused, and an administrator cannot disable their own password. - A user who must change the password could still use the API with their tokens. A token is now refused until they change it, as the pages are.
97 lines
5.5 KiB
Python
97 lines
5.5 KiB
Python
"""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
|
|
from django.contrib.messages import get_messages
|
|
from django.test import TestCase
|
|
from django.urls import reverse
|
|
from lxml import html
|
|
|
|
|
|
class UserPagesTests(TestCase):
|
|
def setUp(self):
|
|
User = get_user_model()
|
|
self.admin = User.objects.create_superuser("up-admin", "up@example.com", "pw")
|
|
self.alice = User.objects.create_user("up-alice", password="Alice-pw-123")
|
|
UserAttributes.objects.get_or_create(user=self.alice)
|
|
self.client.force_login(self.admin)
|
|
|
|
def messages(self, response):
|
|
return [str(m) for m in get_messages(response.wsgi_request)]
|
|
|
|
def form_data(self, user, **changes):
|
|
data = {"username": user.username, "is_active": "on", "is_superuser": "on" if user.is_superuser else "",
|
|
"max_instances": "2", "max_cpus": "2", "max_memory": "2048", "max_disk_size": "20", **changes}
|
|
return {key: value for key, value in data.items() if value != ""}
|
|
|
|
def test_an_administrator_cannot_block_delete_or_demote_themselves(self):
|
|
own = self.admin.pk
|
|
for response in (self.client.post(reverse("admin:user_block", args=[own])),
|
|
self.client.post(reverse("admin:user_delete", args=[own]))):
|
|
self.assertIn("You cannot block, delete or demote your own account", self.messages(response))
|
|
for change in ({"is_superuser": ""}, {"is_active": ""}):
|
|
response = self.client.post(reverse("admin:user_update", args=[own]), self.form_data(self.admin, **change))
|
|
self.assertContains(response, "You cannot block, delete or demote your own account")
|
|
self.admin.refresh_from_db()
|
|
self.assertTrue(self.admin.is_active and self.admin.is_superuser)
|
|
# another user's account is theirs to change
|
|
self.client.post(reverse("admin:user_block", args=[self.alice.pk]))
|
|
self.alice.refresh_from_db()
|
|
self.assertFalse(self.alice.is_active)
|
|
|
|
def test_the_form_tells_the_password_state_and_links_its_page(self):
|
|
page = html.fromstring(self.client.get(reverse("admin:user_update", args=[self.alice.pk])).content)
|
|
text = " ".join(page.text_content().split())
|
|
self.assertIn("Password Set Change password", text)
|
|
self.assertNotIn("No password set", text)
|
|
self.assertEqual(page.xpath("//a[normalize-space()='Change password']/@href"),
|
|
[reverse("admin:user_update_password", args=[self.alice.pk])])
|
|
self.assertEqual(page.xpath("//a[@href='../password/']"), [])
|
|
|
|
def test_staff_and_quotas_say_what_they_mean(self):
|
|
page = self.client.get(reverse("admin:user_update", args=[self.alice.pk])).content.decode()
|
|
self.assertIn("Template editor", page)
|
|
self.assertIn("May change and delete the template VMs given to them", page)
|
|
self.assertNotIn("log into this admin site", page)
|
|
self.assertIn("Max memory (MiB)", page)
|
|
self.assertIn("Max disk size (GiB)", page)
|
|
self.assertIn("-1: no limit; 0: none allowed", page)
|
|
|
|
def test_a_missing_users_profile_is_not_found(self):
|
|
self.assertEqual(self.client.get(reverse("accounts:account", args=[99999])).status_code, 404)
|
|
|
|
def test_a_user_who_never_logged_in_says_never(self):
|
|
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)
|
|
|
|
def test_a_user_without_a_password_cannot_be_made_to_change_it(self):
|
|
# the change form asks for the old password: one they never had
|
|
url = reverse("admin:user_update_password", args=[self.alice.pk])
|
|
response = self.client.post(url, {"usable_password": "false", "must_change_password": "on"})
|
|
self.assertContains(response, "A user without a password cannot be made to change it")
|
|
self.alice.refresh_from_db()
|
|
self.assertTrue(self.alice.has_usable_password())
|
|
|
|
def test_an_administrator_keeps_their_own_password(self):
|
|
url = reverse("admin:user_update_password", args=[self.admin.pk])
|
|
response = self.client.post(url, {"usable_password": "false"})
|
|
self.assertContains(response, "You cannot block, delete or demote your own account")
|
|
self.admin.refresh_from_db()
|
|
self.assertTrue(self.admin.has_usable_password())
|
|
|