diff --git a/README.md b/README.md index 32df6ce9..e9d2d139 100644 --- a/README.md +++ b/README.md @@ -810,7 +810,7 @@ The REST API (Django REST Framework, documented with `drf-spectacular`) takes th * **Swagger UI:** `http:///swagger/` * **ReDoc UI:** `http:///redoc/` * **OpenAPI 3.0 schema:** `http:///api/schema/` (JSON or YAML) -* **API tokens**, for scripts and tools: **Profile → API Tokens**. A token acts as its user and is shown once; send it as a header: +* **API tokens**, for scripts and tools: **Profile → API Tokens**. A token acts as its user and is shown once; an administrator who sets the user's password deletes them. Send it as a header: ```bash curl -H "Authorization: Token wvc_..." https:///api/v1/instances/ diff --git a/admin/templates/admin/user_password_form.html b/admin/templates/admin/user_password_form.html index 3d49ce08..d1c12b8f 100644 --- a/admin/templates/admin/user_password_form.html +++ b/admin/templates/admin/user_password_form.html @@ -16,7 +16,7 @@

{{ edited_user.username }}

-

{% trans "How the user signs in." %}

+

{% trans "How the user signs in." %} {% trans "Changing it deletes the user's API tokens." %}

{% trans "Signs in with the password set here." as with_help %} diff --git a/admin/tests/test_user_pages.py b/admin/tests/test_user_pages.py index 21fa849c..d87c78a0 100644 --- a/admin/tests/test_user_pages.py +++ b/admin/tests/test_user_pages.py @@ -2,7 +2,7 @@ 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 accounts.models import ApiToken, UserAttributes from django.contrib.auth import get_user_model from django.contrib.messages import get_messages from django.test import TestCase @@ -140,6 +140,15 @@ class UserPagesTests(TestCase): 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_password_set_by_an_administrator_ends_the_users_api_tokens(self): + # a reset closes a taken-over account: a stolen token must not outlive it + ApiToken.objects.create(self.alice, expiry=None, name="script") + ApiToken.objects.create(self.admin, expiry=None, name="mine") + url = reverse("admin:user_update_password", args=[self.alice.pk]) + self.client.post(url, {"password1": "New-pw-4567x", "password2": "New-pw-4567x"}) + self.assertFalse(ApiToken.objects.filter(user=self.alice).exists()) + self.assertTrue(ApiToken.objects.filter(user=self.admin).exists()) + 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]) diff --git a/admin/views.py b/admin/views.py index 2b8937e5..998ee82e 100644 --- a/admin/views.py +++ b/admin/views.py @@ -1,6 +1,6 @@ from datetime import timedelta -from accounts.models import Instance, UserAttributes, UserInstance +from accounts.models import ApiToken, Instance, UserAttributes, UserInstance from appsettings.settings import app_settings from django.conf import settings from django.contrib import messages @@ -208,6 +208,8 @@ def user_update_password(request, pk): user = form.save() UserAttributes.objects.update_or_create( user=user, defaults={"must_change_password": form.cleaned_data["must_change_password"]}) + # a reset closes a taken-over account: its tokens would outlive the old password + ApiToken.objects.filter(user=user).delete() update_session_auth_hash(request, user) # Important! messages.success( request, _("Password changed for %(user)s") % {"user": user.username} diff --git a/locale/tr/LC_MESSAGES/django.mo b/locale/tr/LC_MESSAGES/django.mo index 26e5c400..17c8e066 100644 Binary files a/locale/tr/LC_MESSAGES/django.mo and b/locale/tr/LC_MESSAGES/django.mo differ diff --git a/locale/tr/LC_MESSAGES/django.po b/locale/tr/LC_MESSAGES/django.po index 4bca13d8..b7029573 100644 --- a/locale/tr/LC_MESSAGES/django.po +++ b/locale/tr/LC_MESSAGES/django.po @@ -913,16 +913,16 @@ msgstr "Sonuç" msgid "IP" msgstr "IP" -#: admin/templates/admin/logs.html:56 admin/views.py:286 instances/states.py:19 +#: admin/templates/admin/logs.html:56 admin/views.py:288 instances/states.py:19 #: instances/views.py:419 instances/views.py:421 logs/views.py:17 msgid "Unknown" msgstr "Bilinmeyen" -#: admin/templates/admin/logs.html:56 admin/views.py:286 logs/views.py:17 +#: admin/templates/admin/logs.html:56 admin/views.py:288 logs/views.py:17 msgid "Done" msgstr "Tamamlandı" -#: admin/templates/admin/logs.html:56 admin/views.py:286 logs/views.py:17 +#: admin/templates/admin/logs.html:56 admin/views.py:288 logs/views.py:17 #: operations/templates/operations/job.html:107 msgid "Failed" msgstr "Başarısız" @@ -1106,6 +1106,10 @@ msgstr "Engeli Kaldır" msgid "How the user signs in." msgstr "Kullanıcının nasıl oturum açtığı." +#: admin/templates/admin/user_password_form.html:19 +msgid "Changing it deletes the user's API tokens." +msgstr "Değiştirmek kullanıcının API token'larını siler." + #: admin/templates/admin/user_password_form.html:22 msgid "Signs in with the password set here." msgstr "Burada belirlenen parolayla oturum açar." @@ -1138,27 +1142,27 @@ msgstr "Kullanıcı Oluştur" msgid "Update User" msgstr "Kullanıcı Güncelle" -#: admin/views.py:213 +#: admin/views.py:215 #, python-format msgid "Password changed for %(user)s" msgstr "%(user)s parolası değiştirildi" -#: admin/views.py:237 +#: admin/views.py:239 msgid "Their access to VMs, SSH keys and settings go; the VMs themselves stay." msgstr "" "Sanal makinelere erişimi, SSH anahtarları ve ayarları silinir; sanal " "makinelerin kendisi kalır." -#: admin/views.py:335 +#: admin/views.py:337 msgid "deleted user" msgstr "silinmiş kullanıcı" -#: admin/views.py:346 +#: admin/views.py:348 #, python-format msgid "Removed VM %(name)s deleted" msgstr "Kaldırılan sanal makine %(name)s silindi" -#: admin/views.py:353 +#: admin/views.py:355 msgid "Its owners no longer get the VM back if its UUID shows up again." msgstr "" "UUID'si yeniden görünse bile sahipleri sanal makineyi artık geri almaz." diff --git a/locale/zh/LC_MESSAGES/django.mo b/locale/zh/LC_MESSAGES/django.mo index 434c0b9b..710c2baa 100644 Binary files a/locale/zh/LC_MESSAGES/django.mo and b/locale/zh/LC_MESSAGES/django.mo differ diff --git a/locale/zh/LC_MESSAGES/django.po b/locale/zh/LC_MESSAGES/django.po index 105a24e9..ab1a8108 100644 --- a/locale/zh/LC_MESSAGES/django.po +++ b/locale/zh/LC_MESSAGES/django.po @@ -893,16 +893,16 @@ msgstr "结果" msgid "IP" msgstr "IP" -#: admin/templates/admin/logs.html:56 admin/views.py:286 instances/states.py:19 +#: admin/templates/admin/logs.html:56 admin/views.py:288 instances/states.py:19 #: instances/views.py:419 instances/views.py:421 logs/views.py:17 msgid "Unknown" msgstr "未知" -#: admin/templates/admin/logs.html:56 admin/views.py:286 logs/views.py:17 +#: admin/templates/admin/logs.html:56 admin/views.py:288 logs/views.py:17 msgid "Done" msgstr "完成" -#: admin/templates/admin/logs.html:56 admin/views.py:286 logs/views.py:17 +#: admin/templates/admin/logs.html:56 admin/views.py:288 logs/views.py:17 #: operations/templates/operations/job.html:107 msgid "Failed" msgstr "失败" @@ -1079,6 +1079,10 @@ msgstr "解除禁用" msgid "How the user signs in." msgstr "用户的登录方式。" +#: admin/templates/admin/user_password_form.html:19 +msgid "Changing it deletes the user's API tokens." +msgstr "更改密码会删除该用户的 API 令牌。" + #: admin/templates/admin/user_password_form.html:22 msgid "Signs in with the password set here." msgstr "使用此处设置的密码登录。" @@ -1111,25 +1115,25 @@ msgstr "创建用户" msgid "Update User" msgstr "更新用户" -#: admin/views.py:213 +#: admin/views.py:215 #, python-format msgid "Password changed for %(user)s" msgstr "已为 %(user)s 修改密码" -#: admin/views.py:237 +#: admin/views.py:239 msgid "Their access to VMs, SSH keys and settings go; the VMs themselves stay." msgstr "其对虚拟机、SSH 密钥和设置的访问将被移除;虚拟机本身保留。" -#: admin/views.py:335 +#: admin/views.py:337 msgid "deleted user" msgstr "已删除的用户" -#: admin/views.py:346 +#: admin/views.py:348 #, python-format msgid "Removed VM %(name)s deleted" msgstr "已删除已移除的虚拟机 %(name)s" -#: admin/views.py:353 +#: admin/views.py:355 msgid "Its owners no longer get the VM back if its UUID shows up again." msgstr "若其 UUID 再次出现,其所有者将不再恢复对该虚拟机的所有权。"