diff --git a/accounts/templates/account.html b/accounts/templates/account.html index ee09e82d..e56d7259 100644 --- a/accounts/templates/account.html +++ b/accounts/templates/account.html @@ -109,7 +109,7 @@
{% if api_tokens %} - {% include "api_token_table.html" with owner=user %} +
{% include "api_token_list.html" with owner=user %}
{% else %}

{% trans "No API tokens." %}

{% endif %} diff --git a/accounts/templates/api_token_list.html b/accounts/templates/api_token_list.html new file mode 100644 index 00000000..994d6494 --- /dev/null +++ b/accounts/templates/api_token_list.html @@ -0,0 +1,23 @@ +{% load i18n %} +{% load bootstrap_icons %} +{# the tokens of the signed-in user, or of owner on a superuser's view of a user: the items of a list-group #} +{% for token in api_tokens %} +
+
+ {{ token.name }} + +
+
{% trans "Starts with" %} {{ token.token_key }}
+
+
{% trans "Created" %} {{ token.created|date:"Y-m-d H:i" }}
+
{% trans "Expires" %} {% if token.expiry %}{{ token.expiry|date:"Y-m-d H:i" }}{% else %}{% trans "never" %}{% endif %}
+
{% trans "Last used" %} {% if token.last_used %}{{ token.last_used|date:"Y-m-d H:i" }}{% else %}{% trans "never" %}{% endif %}
+
+
+{% endfor %} diff --git a/accounts/templates/api_token_table.html b/accounts/templates/api_token_table.html deleted file mode 100644 index 0b330e84..00000000 --- a/accounts/templates/api_token_table.html +++ /dev/null @@ -1,33 +0,0 @@ -{% load i18n %} -{% load bootstrap_icons %} -{# the tokens of the signed-in user, or of owner on a superuser's view of a user #} -
- - - - - - - - - - - - - {% for token in api_tokens %} - - - - - - - - - {% endfor %} - -
{% trans "Name" %}{% trans "Starts with" %}{% trans "Created" %}{% trans "Expires" %}{% trans "Last used" %}
{{ token.name }}{{ token.token_key }}{{ token.created|date:"Y-m-d H:i" }}{% if token.expiry %}{{ token.expiry|date:"Y-m-d H:i" }}{% else %}{% trans "never" %}{% endif %}{% if token.last_used %}{{ token.last_used|date:"Y-m-d H:i" }}{% else %}{% trans "never" %}{% endif %} - - {% bs_icon 'trash' %} - -
-
diff --git a/accounts/templates/profile.html b/accounts/templates/profile.html index 9f054dab..af08750d 100644 --- a/accounts/templates/profile.html +++ b/accounts/templates/profile.html @@ -4,112 +4,93 @@ {% load bootstrap_icons %} {% load tags_fingerprint %} -{% block title %}{% trans "Profile" %}: {{ request.user.first_name }} {{ request.user.last_name}}{% endblock %} +{% block title %}{% trans "Profile" %}{% endblock %} -{% block page_heading %}{% trans "Profile" %}: {{ request.user.first_name }} {{ request.user.last_name}}{% endblock page_heading %} +{% block page_heading %}{% trans "Profile" %}{% endblock page_heading %} {% block content %} - -
-
-
-
-
- {% csrf_token %} - {% bootstrap_form profile_form layout='horizontal' %} - {# an LDAP user's password is changed in LDAP #} - {% if perms.accounts.change_password and user.has_usable_password %} - - {% bs_icon 'lock' %} {% trans "Change Password" %} - +
+
+
+
+
+

{{ user.get_full_name|default:user.username }}

+ {% if user.is_superuser %} + {% trans "Superuser" %} + {% elif user.is_staff %} + {% trans "Template editor" %} {% endif %} -
- -
- +
+
{{ user.username }}
+ {% if user.email %}
{{ user.email }}
{% endif %} +
{% trans "Last login" %} {{ user.last_login|date:"Y-m-d H:i"|default:_("never") }}
-
+
+ {% csrf_token %} + {% bootstrap_form profile_form %} + +
+ {# an LDAP user's password is changed in LDAP #} + {% if perms.accounts.change_password and user.has_usable_password %} + + {% endif %} +
-
- {% if publickeys %} -
-
- - - {% for key in publickeys %} - - - - - {% endfor %} - -
{{ key.keyname }} ({% ssh_to_fingerprint key.keypublic %}) - - {% bs_icon 'trash' %} - -
+ +
+
+
+

{% trans "SSH Keys" %} {{ publickeys|length }}

+
-
- {% endif %} -
-
- {%trans "Add SSH Key" %} -
-
-
- {% csrf_token %} - {% bootstrap_form ssh_key_form layout='horizontal' %} -
- +
{% trans "Offered when you create a VM and on a VM's Access tab." %}
+
+ {% for key in publickeys %} +
+
+ {{ key.keyname }} +
+
{% ssh_to_fingerprint key.keypublic %}
+
+ {% endfor %} +
+
+ + {% csrf_token %} + {% bootstrap_form ssh_key_form %} +
-
-
-
-

{% blocktrans %}A token lets a script or a tool use the REST API as you, without your password or OTP: send it as Authorization: Token <token>. A password change does not delete tokens: delete a token you no longer use, or one that may have leaked.{% endblocktrans %}

- {% if api_tokens %} - {% include "api_token_table.html" %} - {% endif %} -
-
- {% trans "Create an API token" %} + + +
+
+

{% trans "API Tokens" %} {{ api_tokens|length }}

+
-
-
+
{% blocktrans %}A token lets a script or a tool use the REST API as you, without your password or OTP: send it as Authorization: Token <token>. A password change does not delete tokens: delete a token you no longer use, or one that may have leaked.{% endblocktrans %}
+
{% include "api_token_list.html" %}
+
+ {% csrf_token %} - {% bootstrap_form api_token_form layout='horizontal' %} -
- -
+ {% bootstrap_form api_token_form %} +
-
+
{% endblock %} -{% block script %} -{% include "tab_from_hash.html" %} -{% endblock %} diff --git a/accounts/tests/test_views.py b/accounts/tests/test_views.py index 46f01ffe..789395ae 100644 --- a/accounts/tests/test_views.py +++ b/accounts/tests/test_views.py @@ -236,7 +236,7 @@ class AccountsTestCase(TestCase): "keypublic": self.rsa_key, }, ) - self.assertRedirects(response, reverse("accounts:profile")) + self.assertRedirects(response, reverse("accounts:profile") + "#sshkeys") key = UserSSHKey.objects.get(keyname="keyname") self.assertEqual(key.keyname, "keyname") @@ -246,7 +246,22 @@ class AccountsTestCase(TestCase): self.assertEqual(response.status_code, 200) response = self.client.post(reverse("accounts:ssh_key_delete", args=[key.id])) - self.assertRedirects(response, reverse("accounts:profile")) + self.assertRedirects(response, reverse("accounts:profile") + "#sshkeys") + + def test_the_profile_lists_keys_and_tokens_where_links_land(self): + admin = get_user_model().objects.get(username="admin") + key = UserSSHKey.objects.create(user=admin, keyname="laptop", keypublic=self.rsa_key) + response = self.client.get(reverse("accounts:profile")) + # other pages link to #sshkeys and #apitokens + self.assertContains(response, 'id="sshkeys"') + self.assertContains(response, 'id="apitokens"') + self.assertContains(response, f'href="{reverse("accounts:ssh_key_delete", args=[key.id])}"') + self.assertContains(response, f'href="{reverse("accounts:change_password")}"') + # an LDAP user's password is changed in LDAP + admin.set_unusable_password() + admin.save() + self.client.force_login(admin) + self.assertNotContains(self.client.get(reverse("accounts:profile")), f'href="{reverse("accounts:change_password")}"') def test_validate_ssh_key(self): self.assertFalse(validate_ssh_key("")) diff --git a/accounts/views.py b/accounts/views.py index 9ec4f9be..ef75f062 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -124,7 +124,7 @@ def ssh_key_create(request): with audit(request, None, f"SSH key {key_form.cleaned_data['keyname']}", "add"): key_form.save() messages.success(request, _("SSH key added")) - return redirect("accounts:profile") + return redirect(reverse("accounts:profile") + "#sshkeys") return render( request, @@ -142,7 +142,7 @@ def ssh_key_delete(request, pk, user_id=None): if user_id is None: ssh_key = get_object_or_404(UserSSHKey, pk=pk, user=request.user) subject = f"SSH key {ssh_key.keyname}" - back = reverse("accounts:profile") + back = reverse("accounts:profile") + "#sshkeys" elif request.user.is_superuser: ssh_key = get_object_or_404(UserSSHKey, pk=pk, user_id=user_id) subject = f"SSH key {ssh_key.keyname} of {ssh_key.user.username}"