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 %} +
+
+ {% with identity.get_provider as provider %} + {% if identity.profile %}{% endif %} + {% if provider.icon %} + + {% else %} + + {% endif %} + {{provider.label}}: + {{identity.fullname}} <{{identity.username}}> + {% endwith %} + {% if identity.profile %}{% endif %} +
+
+
+ {% if identity.picture %} + + {% endif %} + {% trans "Account information" %} +
+ + + {% if identity.subject %} + + {% endif %} + {% if identity.fullname %} + + {% endif %} + {% if identity.username %} + + {% endif %} +
{% trans "Last used" %}{{identity.last_used|date:"Y-m-d"}}
{% trans "Subject" %}{{identity.subject}}
{% trans "Full name" %}{{identity.fullname}}
{% trans "User name" %}{{identity.username}}
+
{% trans "Audit log" %}
+ + + + + + + + + + + {% for e in identity.audit_log.all %} + + + + + + + {% empty %} + + {% endfor %} + +
{% trans "Date" %}{% trans "Author" %}{% trans "Notes" %}{% trans "Changes" %}
{{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 %} +
+
{% trans "No audit log for this identity" %}
+
+
+{% 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 %} -
    - - - - - - - - - - - - {% for e in identity.audit_log.all %} - - - - - - - {% empty %} - - {% endfor %} - -
    {% trans "Date" %}{% trans "Author" %}{% trans "Notes" %}{% trans "Changes" %}
    {{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 %} -
    -
    {% trans "No audit log for this identity" %}
    -
    -{% 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"]