147 lines
5.4 KiB
Python
147 lines
5.4 KiB
Python
import json
|
|
|
|
import pytest
|
|
from django.conf import settings
|
|
from django.http import JsonResponse
|
|
from django.test import RequestFactory, override_settings
|
|
from rest_framework.response import Response
|
|
|
|
from infrasynth.audit.middleware import AuditAPIMiddleware
|
|
from infrasynth.audit.models import APIInteractionLog
|
|
|
|
|
|
def audit_config(**overrides):
|
|
return {**settings.INFRASYNTH_AUDIT, **overrides}
|
|
|
|
|
|
@pytest.fixture
|
|
def middleware():
|
|
return AuditAPIMiddleware(get_response=lambda request: None)
|
|
|
|
|
|
def run_request(middleware, request, response):
|
|
middleware.process_request(request)
|
|
return middleware.process_response(request, response)
|
|
|
|
|
|
class TestAPILogging:
|
|
def test_logs_api_interaction(self, middleware, db):
|
|
request = RequestFactory().get("/api/features/active/")
|
|
response = JsonResponse({"ok": True})
|
|
run_request(middleware, request, response)
|
|
|
|
log = APIInteractionLog.objects.get(path="/api/features/active/")
|
|
assert log.method == "GET"
|
|
assert log.status_code == 200
|
|
assert log.request_id
|
|
assert log.duration_ms >= 0
|
|
|
|
def test_logs_request_body(self, middleware, db):
|
|
request = RequestFactory().post(
|
|
"/api/auth/login/",
|
|
data=json.dumps({"username": "a@b.com"}),
|
|
content_type="application/json",
|
|
)
|
|
response = JsonResponse({"detail": "ok"})
|
|
run_request(middleware, request, response)
|
|
|
|
log = APIInteractionLog.objects.get(path="/api/auth/login/")
|
|
assert log.request_body == {"username": "a@b.com"}
|
|
|
|
def test_logs_response_body(self, middleware, db):
|
|
request = RequestFactory().get("/api/audit/changes/")
|
|
response = Response({"count": 3, "results": []})
|
|
run_request(middleware, request, response)
|
|
|
|
log = APIInteractionLog.objects.get(path="/api/audit/changes/")
|
|
assert log.response_body == {"count": 3, "results": []}
|
|
|
|
def test_captures_ip_and_user_agent(self, middleware, db):
|
|
request = RequestFactory().get("/api/audit/changes/", HTTP_USER_AGENT="pytest-agent", REMOTE_ADDR="10.0.0.5")
|
|
response = JsonResponse({})
|
|
run_request(middleware, request, response)
|
|
|
|
log = APIInteractionLog.objects.get(path="/api/audit/changes/")
|
|
assert log.ip_address == "10.0.0.5"
|
|
assert log.user_agent == "pytest-agent"
|
|
|
|
|
|
class TestSensitiveData:
|
|
def test_sensitive_request_body_not_stored(self, middleware, db):
|
|
request = RequestFactory().post(
|
|
"/api/auth/login/",
|
|
data=json.dumps({"username": "a@b.com", "password": "supersecret"}),
|
|
content_type="application/json",
|
|
)
|
|
response = JsonResponse({})
|
|
run_request(middleware, request, response)
|
|
|
|
log = APIInteractionLog.objects.get(path="/api/auth/login/")
|
|
assert log.request_body is None
|
|
|
|
@override_settings(INFRASYNTH_AUDIT=audit_config(SENSITIVE_KEYS=["password", "my_secret"]))
|
|
def test_custom_sensitive_keys(self, db):
|
|
middleware = AuditAPIMiddleware(get_response=lambda request: None)
|
|
request = RequestFactory().post(
|
|
"/api/test/",
|
|
data=json.dumps({"my_secret": "abc", "other": 1}),
|
|
content_type="application/json",
|
|
)
|
|
response = JsonResponse({})
|
|
run_request(middleware, request, response)
|
|
|
|
log = APIInteractionLog.objects.get(path="/api/test/")
|
|
assert log.request_body is None
|
|
|
|
|
|
class TestConfiguration:
|
|
def test_admin_path_not_logged(self, middleware, db):
|
|
request = RequestFactory().get("/admin/auth/user/")
|
|
response = JsonResponse({})
|
|
run_request(middleware, request, response)
|
|
|
|
assert not APIInteractionLog.objects.filter(path="/admin/auth/user/").exists()
|
|
|
|
@override_settings(INFRASYNTH_AUDIT=audit_config(ENABLE_API_LOGGING=False))
|
|
def test_api_logging_disabled(self, db):
|
|
middleware = AuditAPIMiddleware(get_response=lambda request: None)
|
|
request = RequestFactory().get("/api/features/active/")
|
|
response = JsonResponse({})
|
|
run_request(middleware, request, response)
|
|
|
|
assert not APIInteractionLog.objects.exists()
|
|
|
|
@override_settings(INFRASYNTH_AUDIT=audit_config(MAX_BODY_SIZE_BYTES=10))
|
|
def test_large_body_truncated(self, db):
|
|
middleware = AuditAPIMiddleware(get_response=lambda request: None)
|
|
big_payload = {"data": "x" * 100}
|
|
request = RequestFactory().post(
|
|
"/api/test/",
|
|
data=json.dumps(big_payload),
|
|
content_type="application/json",
|
|
)
|
|
response = JsonResponse({})
|
|
run_request(middleware, request, response)
|
|
|
|
log = APIInteractionLog.objects.get(path="/api/test/")
|
|
assert log.request_body is None
|
|
|
|
|
|
class TestActorCapture:
|
|
def test_captures_authenticated_actor(self, middleware, db, user):
|
|
request = RequestFactory().get("/api/audit/changes/")
|
|
request.user = user
|
|
response = JsonResponse({})
|
|
run_request(middleware, request, response)
|
|
|
|
log = APIInteractionLog.objects.get(path="/api/audit/changes/")
|
|
assert log.actor == user
|
|
|
|
def test_anonymous_actor_null(self, middleware, db, user):
|
|
request = RequestFactory().get("/api/audit/changes/")
|
|
request.user = type("AnonUser", (), {"is_authenticated": False})()
|
|
response = JsonResponse({})
|
|
run_request(middleware, request, response)
|
|
|
|
log = APIInteractionLog.objects.get(path="/api/audit/changes/")
|
|
assert log.actor is None
|