audit: sanitize kwargs when creating audit event
This commit is contained in:
parent
74b2b26a20
commit
766518ee0e
|
@ -1,12 +1,13 @@
|
||||||
"""passbook audit models"""
|
"""passbook audit models"""
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from inspect import getmodule, stack
|
from inspect import getmodule, stack
|
||||||
from typing import Optional
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
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.postgres.fields import JSONField
|
from django.contrib.postgres.fields import JSONField
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
@ -19,6 +20,26 @@ from passbook.lib.utils.http import get_client_ip
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
|
||||||
|
"""clean source of all Models that would interfere with the JSONField.
|
||||||
|
Models are replaced with a dictionary of {
|
||||||
|
app: str,
|
||||||
|
name: str,
|
||||||
|
pk: Any
|
||||||
|
}"""
|
||||||
|
for key, value in source.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
source[key] = sanitize_dict(value)
|
||||||
|
elif isinstance(value, models.Model):
|
||||||
|
model_content_type = ContentType.objects.get_for_model(value)
|
||||||
|
source[key] = {
|
||||||
|
"app": model_content_type.app_label,
|
||||||
|
"name": model_content_type.model,
|
||||||
|
"pk": value.pk,
|
||||||
|
}
|
||||||
|
return source
|
||||||
|
|
||||||
|
|
||||||
class EventAction(Enum):
|
class EventAction(Enum):
|
||||||
"""All possible actions to save into the audit log"""
|
"""All possible actions to save into the audit log"""
|
||||||
|
|
||||||
|
@ -72,8 +93,9 @@ class Event(UUIDModel):
|
||||||
)
|
)
|
||||||
if not app:
|
if not app:
|
||||||
app = getmodule(stack()[_inspect_offset][0]).__name__
|
app = getmodule(stack()[_inspect_offset][0]).__name__
|
||||||
event = Event(action=action.value, app=app, context=kwargs)
|
cleaned_kwargs = sanitize_dict(kwargs)
|
||||||
LOGGER.debug("Created Audit event", action=action, context=kwargs)
|
event = Event(action=action.value, app=app, context=cleaned_kwargs)
|
||||||
|
LOGGER.debug("Created Audit event", action=action, context=cleaned_kwargs)
|
||||||
return event
|
return event
|
||||||
|
|
||||||
def from_http(
|
def from_http(
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
"""audit event tests"""
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from guardian.shortcuts import get_anonymous_user
|
||||||
|
|
||||||
|
from passbook.audit.models import Event, EventAction
|
||||||
|
|
||||||
|
|
||||||
|
class TestAuditEvent(TestCase):
|
||||||
|
"""Test Audit Event"""
|
||||||
|
|
||||||
|
def test_new_with_model(self):
|
||||||
|
"""Create a new Event passing a model as kwarg"""
|
||||||
|
event = Event.new(EventAction.CUSTOM, model=get_anonymous_user())
|
||||||
|
event.save()
|
||||||
|
self.assertIsNotNone(event.pk)
|
|
@ -82,8 +82,7 @@ class PassbookAuthorizationView(AccessMixin, AuthorizationView):
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
# User has clicked on "Authorize"
|
# User has clicked on "Authorize"
|
||||||
Event.new(
|
Event.new(
|
||||||
EventAction.AUTHORIZE_APPLICATION,
|
EventAction.AUTHORIZE_APPLICATION, authorized_application=self._application,
|
||||||
authorized_application=self._application.pk,
|
|
||||||
).from_http(self.request)
|
).from_http(self.request)
|
||||||
LOGGER.debug(
|
LOGGER.debug(
|
||||||
"User authorized Application",
|
"User authorized Application",
|
||||||
|
|
|
@ -33,7 +33,7 @@ def check_permissions(request, user, client):
|
||||||
|
|
||||||
Event.new(
|
Event.new(
|
||||||
EventAction.AUTHORIZE_APPLICATION,
|
EventAction.AUTHORIZE_APPLICATION,
|
||||||
authorized_application=application.pk,
|
authorized_application=application,
|
||||||
skipped_authorization=False,
|
skipped_authorization=False,
|
||||||
).from_http(request)
|
).from_http(request)
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -137,7 +137,7 @@ class LoginProcessView(AccessRequiredView):
|
||||||
# Log Application Authorization
|
# Log Application Authorization
|
||||||
Event.new(
|
Event.new(
|
||||||
EventAction.AUTHORIZE_APPLICATION,
|
EventAction.AUTHORIZE_APPLICATION,
|
||||||
authorized_application=self.provider.application.pk,
|
authorized_application=self.provider.application,
|
||||||
skipped_authorization=True,
|
skipped_authorization=True,
|
||||||
).from_http(request)
|
).from_http(request)
|
||||||
return RedirectToSPView.as_view()(
|
return RedirectToSPView.as_view()(
|
||||||
|
@ -161,7 +161,7 @@ class LoginProcessView(AccessRequiredView):
|
||||||
# User accepted request
|
# User accepted request
|
||||||
Event.new(
|
Event.new(
|
||||||
EventAction.AUTHORIZE_APPLICATION,
|
EventAction.AUTHORIZE_APPLICATION,
|
||||||
authorized_application=self.provider.application.pk,
|
authorized_application=self.provider.application,
|
||||||
skipped_authorization=False,
|
skipped_authorization=False,
|
||||||
).from_http(request)
|
).from_http(request)
|
||||||
return RedirectToSPView.as_view()(
|
return RedirectToSPView.as_view()(
|
||||||
|
|
|
@ -196,7 +196,7 @@ class OAuthCallback(OAuthClientMixin, View):
|
||||||
access.save()
|
access.save()
|
||||||
UserOAuthSourceConnection.objects.filter(pk=access.pk).update(user=user)
|
UserOAuthSourceConnection.objects.filter(pk=access.pk).update(user=user)
|
||||||
Event.new(
|
Event.new(
|
||||||
EventAction.CUSTOM, message="Linked OAuth Source", source=source.pk
|
EventAction.CUSTOM, message="Linked OAuth Source", source=source
|
||||||
).from_http(self.request)
|
).from_http(self.request)
|
||||||
if was_authenticated:
|
if was_authenticated:
|
||||||
messages.success(
|
messages.success(
|
||||||
|
|
Reference in New Issue