audit: remove foreign key to user, save user data as json
This commit is contained in:
parent
cbcdaaf532
commit
8358574484
|
@ -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"
|
||||||
|
),
|
||||||
|
]
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Reference in New Issue