Skip to content

Commit

Permalink
Add initiall BLL support for audit log
Browse files Browse the repository at this point in the history
Adds 3 specific audit functions for logging file view/downloads

Also adds a wrapper in the BLL for `get_audit_log()`, again mainly for
testing, but also in future for showing to users.
  • Loading branch information
bloodearnest committed Mar 25, 2024
1 parent f9edf21 commit 320dee2
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 3 deletions.
44 changes: 44 additions & 0 deletions airlock/business_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,50 @@ def reject_file(

bll._dal.reject_file(release_request.id, relpath, user.username)

def get_audit_log(
self,
user: str | None = None,
workspace: str | None = None,
request: str | None = None,
) -> list[AuditEvent]:
return bll._dal.get_audit_log(
user=user,
workspace=workspace,
request=request,
)

def audit_workspace_file_access(
self, workspace: Workspace, path: UrlPath, user: User
):
audit = AuditEvent(
type=AuditEventType.WORKSPACE_FILE_VIEW,
user=user.username,
workspace=workspace.name,
)
bll._dal.audit_event(audit)

def audit_request_file_access(
self, request: ReleaseRequest, path: UrlPath, user: User
):
audit = AuditEvent(
type=AuditEventType.REQUEST_FILE_VIEW,
user=user.username,
workspace=request.workspace,
request=request.id,
)
bll._dal.audit_event(audit)

def audit_request_file_download(
self, request: ReleaseRequest, path: UrlPath, user: User
):
audit = AuditEvent(
type=AuditEventType.REQUEST_FILE_DOWNLOAD,
user=user.username,
workspace=request.workspace,
request=request.id,
)
bll._dal.audit_event(audit)


def _get_configured_bll():
DataAccessLayer = import_string(settings.AIRLOCK_DATA_ACCESS_LAYER)
Expand Down
4 changes: 3 additions & 1 deletion airlock/views/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from django.views.decorators.vary import vary_on_headers
from opentelemetry import trace

from airlock.business_logic import RequestStatus, bll
from airlock.business_logic import RequestStatus, UrlPath, bll
from airlock.file_browser_api import get_request_tree
from services.tracing import instrument

Expand Down Expand Up @@ -119,8 +119,10 @@ def request_contents(request, request_id: str, path: str):
):
raise PermissionDenied()

bll.audit_request_file_download(release_request, UrlPath(path), request.user)
return download_file(abspath, filename=path)

bll.audit_request_file_access(release_request, UrlPath(path), request.user)
return serve_file(request, abspath, release_request.get_request_file(path))


Expand Down
2 changes: 2 additions & 0 deletions airlock/views/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ def workspace_contents(request, workspace_name: str, path: str):
if not abspath.is_file():
return HttpResponseBadRequest()

bll.audit_workspace_file_access(workspace, UrlPath(path), request.user)

return serve_file(request, abspath)


Expand Down
19 changes: 18 additions & 1 deletion tests/integration/views/test_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pytest
import requests

from airlock.business_logic import RequestFileType, RequestStatus
from airlock.business_logic import AuditEventType, RequestFileType, RequestStatus, bll
from tests import factories
from tests.conftest import get_trace

Expand Down Expand Up @@ -175,6 +175,11 @@ def test_request_contents_file(airlock_client):
response = airlock_client.get("/requests/content/id/default/file.txt")
assert response.status_code == 200
assert response.content == b'<pre class="txt">\ntest\n</pre>\n'
audit_log = bll.get_audit_log(
user=airlock_client.user.username,
request=release_request.id,
)
assert audit_log[0].type == AuditEventType.REQUEST_FILE_VIEW


def test_request_contents_dir(airlock_client):
Expand Down Expand Up @@ -211,6 +216,12 @@ def test_request_download_file(airlock_client):
assert response.as_attachment
assert list(response.streaming_content) == [b"test"]

audit_log = bll.get_audit_log(
user=airlock_client.user.username,
request=release_request.id,
)
assert audit_log[0].type == AuditEventType.REQUEST_FILE_DOWNLOAD


@pytest.mark.parametrize(
"request_author,user,can_download",
Expand Down Expand Up @@ -285,6 +296,12 @@ def test_request_download_file_permissions(
assert response.status_code == 200
assert response.as_attachment
assert list(response.streaming_content) == [b"test"]

audit_log = bll.get_audit_log(
user=airlock_client.user.username,
request=release_request.id,
)
assert audit_log[0].type == AuditEventType.REQUEST_FILE_DOWNLOAD
else:
assert response.status_code == 403

Expand Down
7 changes: 6 additions & 1 deletion tests/integration/views/test_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django.contrib import messages
from django.shortcuts import reverse

from airlock.business_logic import RequestFileType, UrlPath
from airlock.business_logic import AuditEventType, RequestFileType, UrlPath, bll
from tests import factories
from tests.conftest import get_trace

Expand Down Expand Up @@ -219,6 +219,11 @@ def test_workspace_contents_file(airlock_client):
response = airlock_client.get("/workspaces/content/workspace/file.txt")
assert response.status_code == 200
assert response.content == b'<pre class="txt">\ntest\n</pre>\n'
audit = bll.get_audit_log(
user=airlock_client.user.username,
workspace="workspace",
)
assert audit[0].type == AuditEventType.WORKSPACE_FILE_VIEW


def test_workspace_contents_dir(airlock_client):
Expand Down

0 comments on commit 320dee2

Please sign in to comment.