audit: remove foreign key to user, save user data as json

This commit is contained in:
Jens Langhammer 2020-09-21 13:20:50 +02:00
parent cbcdaaf532
commit 8358574484
4 changed files with 108 additions and 16 deletions

View File

@ -0,0 +1,59 @@
# Generated by Django 3.1.1 on 2020-09-17 11:55
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
import passbook.audit.models
def convert_user_to_json(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
Event = apps.get_model("passbook_audit", "Event")
db_alias = schema_editor.connection.alias
for event in Event.objects.all():
event.delete()
# Because event objects cannot be updated, we have to re-create them
event.pk = None
event.user_json = (
passbook.audit.models.get_user(event.user) if event.user else {}
)
event._state.adding = True
event.save()
class Migration(migrations.Migration):
dependencies = [
("passbook_audit", "0002_auto_20200918_2116"),
]
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"),
]
),
),
migrations.AddField(
model_name="event", name="user_json", field=models.JSONField(default=dict),
),
migrations.RunPython(convert_user_to_json),
migrations.RemoveField(model_name="event", name="user",),
migrations.RenameField(
model_name="event", old_name="user_json", new_name="user"
),
]

View File

@ -12,13 +12,14 @@ 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.utils import get_anonymous_user
from structlog import get_logger from structlog import get_logger
from passbook.core.middleware import ( from passbook.core.middleware import (
SESSION_IMPERSONATE_ORIGINAL_USER, SESSION_IMPERSONATE_ORIGINAL_USER,
SESSION_IMPERSONATE_USER, SESSION_IMPERSONATE_USER,
) )
from passbook.core.models import 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()
@ -53,6 +54,22 @@ def model_to_dict(model: Model) -> Dict[str, Any]:
} }
def get_user(user: User, original_user: Optional[User] = None) -> Dict[str, Any]:
"""Convert user object to dictionary, optionally including the original user"""
if isinstance(user, AnonymousUser):
user = get_anonymous_user()
user_data = {
"username": user.username,
"pk": user.pk,
"email": user.email,
}
if original_user:
original_data = get_user(original_user)
original_data["on_behalf_of"] = user_data
return original_data
return user_data
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 {
@ -101,9 +118,7 @@ class Event(models.Model):
"""An individual audit log event""" """An individual audit log event"""
event_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4) event_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
user = models.ForeignKey( user = models.JSONField(default=dict)
settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL
)
action = models.TextField(choices=EventAction.as_choices()) action = models.TextField(choices=EventAction.as_choices())
date = models.DateTimeField(auto_now_add=True) date = models.DateTimeField(auto_now_add=True)
app = models.TextField() app = models.TextField()
@ -142,17 +157,17 @@ class Event(models.Model):
Events independently from requests. Events independently from requests.
`user` arguments optionally overrides user from requests.""" `user` arguments optionally overrides user from requests."""
if hasattr(request, "user"): if hasattr(request, "user"):
if isinstance(request.user, AnonymousUser): self.user = get_user(
self.user = get_anonymous_user() request.user,
else: request.session.get(SESSION_IMPERSONATE_ORIGINAL_USER, None),
self.user = request.user )
if user: if user:
self.user = user self.user = get_user(user)
# Check if we're currently impersonating, and add that user # Check if we're currently impersonating, and add that user
if hasattr(request, "session"): if hasattr(request, "session"):
if SESSION_IMPERSONATE_ORIGINAL_USER in request.session: if SESSION_IMPERSONATE_ORIGINAL_USER in request.session:
self.user = request.session[SESSION_IMPERSONATE_ORIGINAL_USER] self.user = get_user(request.session[SESSION_IMPERSONATE_ORIGINAL_USER])
self.context["on_behalf_of"] = model_to_dict( self.user["on_behalf_of"] = get_user(
request.session[SESSION_IMPERSONATE_USER] request.session[SESSION_IMPERSONATE_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

View File

@ -57,7 +57,9 @@ def on_user_logged_out(sender, request: HttpRequest, user: User, **_):
# pylint: disable=unused-argument # pylint: disable=unused-argument
def on_user_write(sender, request: HttpRequest, user: User, data: Dict[str, Any], **_): def on_user_write(sender, request: HttpRequest, user: User, data: Dict[str, Any], **_):
"""Log User write""" """Log User write"""
thread = EventNewThread(EventAction.CUSTOM, request, **data) thread = EventNewThread(
EventAction.CUSTOM, request, caller="stages/user_write", **data
)
thread.user = user thread.user = user
thread.run() thread.run()

View File

@ -40,12 +40,28 @@
</div> </div>
</th> </th>
<td role="cell"> <td role="cell">
<div>
<div>
<code>{{ entry.context }}</code> <code>{{ entry.context }}</code>
</div>
{% if entry.user.on_behalf_of %}
<small>
{% blocktrans with username=entry.user.on_behalf_of.username %}
On behalf of {{ username }}
{% endblocktrans %}
</small>
{% endif %}
</div>
</td> </td>
<td role="cell"> <td role="cell">
<span> <div>
{{ entry.user }} <div>{{ entry.user.username }}</div>
</span> <small>
{% blocktrans with pk=entry.user.pk %}
ID: {{ pk }}
{% endblocktrans %}
</small>
</div>
</td> </td>
<td role="cell"> <td role="cell">
<span> <span>