audit: sanitize kwargs when creating audit event

This commit is contained in:
Jens Langhammer 2019-12-31 13:33:07 +01:00
parent 74b2b26a20
commit 766518ee0e
7 changed files with 46 additions and 9 deletions

View File

@ -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(

View File

View File

@ -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)

View File

@ -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",

View File

@ -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

View File

@ -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()(

View File

@ -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(