diff --git a/backend/mixins.py b/backend/mixins.py
index 6d83ef347998aba8f25629638f6808da1b8abecc..bdde115e45091116aca91926a22e38742370a399 100644
--- a/backend/mixins.py
+++ b/backend/mixins.py
@@ -127,14 +127,19 @@ class VisitPersonMixin(VisitorMixin):
"key": self.person.lookup_key}) + f"?url={self.request.build_absolute_uri()}",
_("Impersonate"), "random"))
if self.person.is_dd:
- res.append(NavLink(reverse("mia_wat_ping", kwargs={"key": self.person.lookup_key}), _("WAT ping")))
+ res.append(NavLink(
+ reverse("mia_wat_ping", kwargs={"key": self.person.lookup_key}), _("WAT ping"), "heartbeat"))
from process.views import Emeritus
emeritus_link = Emeritus.get_nonauth_url(self.person, self.request)
if emeritus_link:
- res.append(NavLink(emeritus_link, _("One click emeritus")))
+ res.append(NavLink(emeritus_link, _("One click emeritus"), "bed"))
if self.person.is_am:
res.append(NavLink(
reverse("person_amprofile", kwargs={"key": self.person.lookup_key}), _("AM Profile"), "gear"))
+ if "view_person_audit_log" in self.visit_perms:
+ res.append(NavLink(
+ reverse("signon_identities_person", kwargs={
+ "key": self.person.lookup_key}), _("Logins"), "sign-in"))
return res
def get_visit_perms(self):
diff --git a/signon/templates/signon/audit_identities.html b/signon/templates/signon/audit_identities.html
new file mode 100644
index 0000000000000000000000000000000000000000..8aa7d8b336e271fb11c114cc058f747f69026511
--- /dev/null
+++ b/signon/templates/signon/audit_identities.html
@@ -0,0 +1,69 @@
+{% load i18n %}
+
+{% for identity in identities %}
+
+
+
+
+ {% if identity.picture %}
+
+ {% endif %}
+ {% trans "Account information" %}
+
+
+ | {% trans "Last used" %} | {{identity.last_used|date:"Y-m-d"}} |
+ {% if identity.subject %}
+ | {% trans "Subject" %} | {{identity.subject}} |
+ {% endif %}
+ {% if identity.fullname %}
+ | {% trans "Full name" %} | {{identity.fullname}} |
+ {% endif %}
+ {% if identity.username %}
+ | {% trans "User name" %} | {{identity.username}} |
+ {% endif %}
+
+
{% trans "Audit log" %}
+
+
+
+ | {% trans "Date" %} |
+ {% trans "Author" %} |
+ {% trans "Notes" %} |
+ {% trans "Changes" %} |
+
+
+
+ {% for e in identity.audit_log.all %}
+
+ | {{e.logdate|date:"Y-m-d H:i:s"}} |
+ {{e.author}} |
+ {{e.notes}} |
+
+
+ {% for field, old, new in e.get_changes_list %}
+ - {{field}}: {{old}} → {{new}}
+ {% endfor %}
+
+ |
+
+ {% empty %}
+ | {% trans "No audit log for this identity" %} |
+ {% endfor %}
+
+
+
+
+{% endfor %}
+
diff --git a/signon/templates/signon/identities.html b/signon/templates/signon/identities.html
new file mode 100644
index 0000000000000000000000000000000000000000..2309cdbbcff87b6029aae1ae7e0bf141057e5382
--- /dev/null
+++ b/signon/templates/signon/identities.html
@@ -0,0 +1,13 @@
+{% extends "nm2-base.html" %}
+{% load nm %}
+{% load i18n %}
+
+{% block content %}
+
+{{person.fullname}} identities
+
+{% if identities %}
+{% include "signon/audit_identities.html" with identities=identities only %}
+{% endif %}
+
+{% endblock %}
diff --git a/signon/templates/signon/login.html b/signon/templates/signon/login.html
index 07a43ff219fae7ff0577a2b48dc09acf657155be..080522ae74052a7ad06a773de5ce1df41f0b6a2c 100644
--- a/signon/templates/signon/login.html
+++ b/signon/templates/signon/login.html
@@ -6,6 +6,7 @@
nm.debian.org login
+{% if not impersonator %}
{% if not providers_active and not providers_inactive %}
{% blocktrans %}
@@ -23,19 +24,21 @@
{% for provider in providers_active %}
+ {% with provider.get_active_identity as identity %}
{% if provider.icon %}

{% else %}
{% endif %}
- {% if provider.profile %}
-
{{provider.label}}
+ {% if identity.profile %}
+
{{provider.label}}
{% else %}
{{provider.label}}
{% endif %}
- {% if provider.username %}
- ({{provider.username}})
+ {% if identity.username %}
+ ({{identity.username}})
{% endif %}
+ {% endwith %}
{% if provider.get_active_identity %}
@@ -79,54 +82,13 @@
{% endif %}
{% endif %}
+{% else %}
+
{% trans "Current login information not displayed while impersonating." %}
+{% endif %}
{% if identities %}
-
Identity audit logs
-{% for identity in identities %}
-
-
-
-
-
- | {% trans "Date" %} |
- {% trans "Author" %} |
- {% trans "Notes" %} |
- {% trans "Changes" %} |
-
-
-
- {% for e in identity.audit_log.all %}
-
- | {{e.logdate|date:"Y-m-d H:i:s"}} |
- {{e.author}} |
- {{e.notes}} |
-
-
- {% for field, old, new in e.get_changes_list %}
- - {{field}}: {{old}} → {{new}}
- {% endfor %}
-
- |
-
- {% empty %}
- | {% trans "No audit log for this identity" %} |
- {% endfor %}
-
-
-
-{% endfor %}
+
{% trans "Identity audit logs" %}
+{% include "signon/audit_identities.html" with identities=identities only %}
{% endif %}
{% endblock %}
diff --git a/signon/urls.py b/signon/urls.py
index 21b919aa2631688ccd2826e38506a5dbb5439c7a..065b4b053bd7862b1807eee330afeee389b8c427 100644
--- a/signon/urls.py
+++ b/signon/urls.py
@@ -3,6 +3,8 @@ from . import views
urlpatterns = [
path('login/', views.Login.as_view(), name='signon_login'),
+ path('identities/', views.Identities.as_view(), name='signon_identities'),
+ path('identities/
/', views.Identities.as_view(), name='signon_identities_person'),
path('logout/', views.Logout.as_view(), name='signon_logout'),
path('oidc/callback//', views.OIDCAuthenticationCallbackView.as_view(), name='signon_oidc_callback'),
]
diff --git a/signon/views.py b/signon/views.py
index 68c61ca0efeb1fd2c77a6ef6cb7dd510a4bc444c..8f20912170d49620d6074b572449ac52313de5f0 100644
--- a/signon/views.py
+++ b/signon/views.py
@@ -4,7 +4,7 @@ from django.views.generic import View, TemplateView
from django import http
from django.shortcuts import redirect
from django.contrib import auth
-from backend.mixins import VisitorMixin
+from backend.mixins import VisitorMixin, VisitPersonMixin
from backend.models import Person
from .models import Identity
from . import providers
@@ -45,6 +45,16 @@ class Logout(View):
return redirect("home")
+class Identities(VisitPersonMixin, TemplateView):
+ template_name = "signon/identities.html"
+ require_visit_perms = "view_person_audit_log"
+
+ def get_context_data(self, **kw):
+ ctx = super().get_context_data(**kw)
+ ctx["identities"] = self.person.identities.all()
+ return ctx
+
+
class OIDCAuthenticationCallbackView(View):
def get(self, request, *args, **kw):
name = self.kwargs["name"]