core: add impersonation start/end to audit log

also add impersonated user as context to other logs
This commit is contained in:
Jens Langhammer 2020-09-18 23:39:37 +02:00
parent e0c104ee5c
commit fe4a0c3b44
4 changed files with 62 additions and 15 deletions

View file

@ -0,0 +1,33 @@
# Generated by Django 3.1.1 on 2020-09-18 21:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_audit", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("LOGIN", "login"),
("LOGIN_FAILED", "login_failed"),
("LOGOUT", "logout"),
("AUTHORIZE_APPLICATION", "authorize_application"),
("SUSPICIOUS_REQUEST", "suspicious_request"),
("SIGN_UP", "sign_up"),
("PASSWORD_RESET", "password_reset"),
("INVITE_CREATED", "invitation_created"),
("INVITE_USED", "invitation_used"),
("IMPERSONATION_STARTED", "impersonation_started"),
("IMPERSONATION_ENDED", "impersonation_ended"),
("CUSTOM", "custom"),
]
),
),
]

View file

@ -6,15 +6,16 @@ from uuid import UUID, uuid4
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.db.models.base import Model
from django.http import HttpRequest from django.http import HttpRequest
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.debug import SafeExceptionReporterFilter from django.views.debug import SafeExceptionReporterFilter
from guardian.shortcuts import get_anonymous_user from guardian.shortcuts import get_anonymous_user
from structlog import get_logger from structlog import get_logger
from passbook.core.middleware import SESSION_IMPERSONATE_ORIGINAL_USER
from passbook.lib.utils.http import get_client_ip from passbook.lib.utils.http import get_client_ip
LOGGER = get_logger() LOGGER = get_logger()
@ -36,6 +37,19 @@ def cleanse_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
return final_dict return final_dict
def model_to_dict(model: Model) -> Dict[str, Any]:
"""Convert model to dict"""
name = str(model)
if hasattr(model, "name"):
name = model.name
return {
"app": model._meta.app_label,
"model_name": model._meta.model_name,
"pk": model.pk,
"name": name,
}
def sanitize_dict(source: Dict[Any, Any]) -> Dict[Any, Any]: def sanitize_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
"""clean source of all Models that would interfere with the JSONField. """clean source of all Models that would interfere with the JSONField.
Models are replaced with a dictionary of { Models are replaced with a dictionary of {
@ -48,18 +62,7 @@ def sanitize_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
if isinstance(value, dict): if isinstance(value, dict):
final_dict[key] = sanitize_dict(value) final_dict[key] = sanitize_dict(value)
elif isinstance(value, models.Model): elif isinstance(value, models.Model):
model_content_type = ContentType.objects.get_for_model(value) final_dict[key] = sanitize_dict(model_to_dict(value))
name = str(value)
if hasattr(value, "name"):
name = value.name
final_dict[key] = sanitize_dict(
{
"app": model_content_type.app_label,
"model_name": model_content_type.model,
"pk": value.pk,
"name": name,
}
)
elif isinstance(value, UUID): elif isinstance(value, UUID):
final_dict[key] = value.hex final_dict[key] = value.hex
else: else:
@ -79,6 +82,8 @@ class EventAction(Enum):
PASSWORD_RESET = "password_reset" # noqa # nosec PASSWORD_RESET = "password_reset" # noqa # nosec
INVITE_CREATED = "invitation_created" INVITE_CREATED = "invitation_created"
INVITE_USED = "invitation_used" INVITE_USED = "invitation_used"
IMPERSONATION_STARTED = "impersonation_started"
IMPERSONATION_ENDED = "impersonation_ended"
CUSTOM = "custom" CUSTOM = "custom"
@staticmethod @staticmethod
@ -140,6 +145,12 @@ class Event(models.Model):
self.user = request.user self.user = request.user
if user: if user:
self.user = user self.user = user
# Check if we're currently impersonating, and add that user
if hasattr(request, "session"):
if SESSION_IMPERSONATE_ORIGINAL_USER in request.session:
self.context["on_behalf_of"] = model_to_dict(
request.session[SESSION_IMPERSONATE_ORIGINAL_USER]
)
# User 255.255.255.255 as fallback if IP cannot be determined # User 255.255.255.255 as fallback if IP cannot be determined
self.client_ip = get_client_ip(request) or "255.255.255.255" self.client_ip = get_client_ip(request) or "255.255.255.255"
# If there's no app set, we get it from the requests too # If there's no app set, we get it from the requests too

View file

@ -5,6 +5,7 @@ from django.shortcuts import get_object_or_404, redirect
from django.views import View from django.views import View
from structlog import get_logger from structlog import get_logger
from passbook.audit.models import Event, EventAction
from passbook.core.middleware import ( from passbook.core.middleware import (
SESSION_IMPERSONATE_ORIGINAL_USER, SESSION_IMPERSONATE_ORIGINAL_USER,
SESSION_IMPERSONATE_USER, SESSION_IMPERSONATE_USER,
@ -30,7 +31,7 @@ class ImpersonateInitView(View):
request.session[SESSION_IMPERSONATE_ORIGINAL_USER] = request.user request.session[SESSION_IMPERSONATE_ORIGINAL_USER] = request.user
request.session[SESSION_IMPERSONATE_USER] = user_to_be request.session[SESSION_IMPERSONATE_USER] = user_to_be
# TODO Audit log entry Event.new(EventAction.IMPERSONATION_STARTED).from_http(request)
return redirect("passbook_core:overview") return redirect("passbook_core:overview")
@ -50,6 +51,6 @@ class ImpersonateEndView(View):
del request.session[SESSION_IMPERSONATE_USER] del request.session[SESSION_IMPERSONATE_USER]
del request.session[SESSION_IMPERSONATE_ORIGINAL_USER] del request.session[SESSION_IMPERSONATE_ORIGINAL_USER]
# TODO: Audit log entry Event.new(EventAction.IMPERSONATION_ENDED).from_http(request)
return redirect("passbook_core:overview") return redirect("passbook_core:overview")

View file

@ -5846,6 +5846,8 @@ definitions:
- PASSWORD_RESET - PASSWORD_RESET
- INVITE_CREATED - INVITE_CREATED
- INVITE_USED - INVITE_USED
- IMPERSONATION_STARTED
- IMPERSONATION_ENDED
- CUSTOM - CUSTOM
date: date:
title: Date title: Date