Skip to content

Commit 954f20d

Browse files
committed
Audit: track downloads separately from page views
1 parent e162952 commit 954f20d

File tree

3 files changed

+41
-5
lines changed

3 files changed

+41
-5
lines changed

readthedocs/audit/models.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,15 @@ def new(self, action, user=None, request=None, **kwargs):
2222
other fields will be auto-populated from that information.
2323
"""
2424

25-
actions_requiring_user = (AuditLog.PAGEVIEW, AuditLog.AUTHN, AuditLog.LOGOUT)
25+
actions_requiring_user = (
26+
AuditLog.PAGEVIEW,
27+
AuditLog.DOWNLOAD,
28+
AuditLog.AUTHN,
29+
AuditLog.LOGOUT,
30+
)
2631
if action in actions_requiring_user and (not user or not request):
2732
raise TypeError(f'A user and a request is required for the {action} action.')
28-
if action == AuditLog.PAGEVIEW and 'project' not in kwargs:
33+
if action in (AuditLog.PAGEVIEW, AuditLog.DOWNLOAD) and 'project' not in kwargs:
2934
raise TypeError(f'A project is required for the {action} action.')
3035

3136
# Don't save anonymous users.
@@ -62,12 +67,14 @@ class AuditLog(TimeStampedModel):
6267
"""
6368

6469
PAGEVIEW = 'pageview'
70+
DOWNLOAD = 'download'
6571
AUTHN = 'authentication'
6672
AUTHN_FAILURE = 'authentication-failure'
6773
LOGOUT = 'log-out'
6874

6975
CHOICES = (
7076
(PAGEVIEW, 'Page view'),
77+
(DOWNLOAD, 'Download'),
7178
(AUTHN, 'Authentication'),
7279
(AUTHN_FAILURE, 'Authentication failure'),
7380
(LOGOUT, 'Log out'),

readthedocs/proxito/tests/test_full.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,29 @@ def test_track_html_files_only(self, is_audit_enabled):
335335
self.assertIn('x-accel-redirect', resp)
336336
self.assertEqual(AuditLog.objects.all().count(), 1)
337337

338+
@mock.patch.object(ServeDocsMixin, '_is_audit_enabled')
339+
def test_track_downloads(self, is_audit_enabled):
340+
is_audit_enabled.return_value = True
341+
342+
self.project.versions.update(
343+
has_pdf=True,
344+
has_epub=True,
345+
has_htmlzip=True,
346+
)
347+
348+
self.assertEqual(AuditLog.objects.all().count(), 0)
349+
url = '/_/downloads/en/latest/pdf/'
350+
host = 'project.dev.readthedocs.io'
351+
resp = self.client.get(url, HTTP_HOST=host)
352+
self.assertIn('x-accel-redirect', resp)
353+
self.assertEqual(AuditLog.objects.all().count(), 1)
354+
355+
log = AuditLog.objects.last()
356+
self.assertEqual(log.user, None)
357+
self.assertEqual(log.project, self.project)
358+
self.assertEqual(log.resource, url)
359+
self.assertEqual(log.action, AuditLog.DOWNLOAD)
360+
338361

339362
@override_settings(
340363
PYTHON_MEDIA=False,

readthedocs/proxito/views/mixins.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@ def _serve_docs(
5454
or "docs-celeryproject-org-kombu-en-stable.pdf")
5555
"""
5656

57-
self._track_pageview(final_project, path, request)
57+
self._track_pageview(
58+
project=final_project,
59+
path=path,
60+
request=request,
61+
download=download,
62+
)
5863

5964
if settings.PYTHON_MEDIA:
6065
return self._serve_docs_python(
@@ -71,14 +76,15 @@ def _serve_docs(
7176
download=download,
7277
)
7378

74-
def _track_pageview(self, project, path, request):
79+
def _track_pageview(self, project, path, request, download):
7580
"""Create an audit log of the page view if audit is enabled."""
7681
# Remove any query args (like the token access from AWS).
7782
path_only = urlparse(path).path
7883
track_file = path_only.endswith(('.html', '.pdf', '.epub', '.zip'))
7984
if track_file and self._is_audit_enabled(project):
85+
action = AuditLog.DOWNLOAD if download else AuditLog.PAGEVIEW
8086
AuditLog.objects.new(
81-
action=AuditLog.PAGEVIEW,
87+
action=action,
8288
user=request.user,
8389
request=request,
8490
project=project,

0 commit comments

Comments
 (0)