Skip to content

Commit b55f3d6

Browse files
authored
Security logs: delete old user security logs (#8620)
Delete all logs that aren't attached to an organization, this is since those logs should be deleted according to their plan on .com. Logs that aren't attached to an organization are logs from authentication to the dashboard, aka user security logs.
1 parent 0dca662 commit b55f3d6

File tree

3 files changed

+150
-0
lines changed

3 files changed

+150
-0
lines changed

readthedocs/audit/tasks.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Celery tasks related to audit logs."""
2+
3+
import structlog
4+
from django.conf import settings
5+
from django.utils import timezone
6+
7+
from readthedocs.audit.models import AuditLog
8+
from readthedocs.worker import app
9+
10+
log = structlog.get_logger(__name__)
11+
12+
13+
@app.task(queue="web")
14+
def delete_old_personal_audit_logs(days=None):
15+
"""
16+
Delete personal security logs older than `days`.
17+
18+
If `days` isn't given, default to ``RTD_AUDITLOGS_DEFAULT_RETENTION_DAYS``.
19+
20+
We delete logs that aren't related to an organization,
21+
there are tasks in .com to delete those according to their plan.
22+
"""
23+
days = days or settings.RTD_AUDITLOGS_DEFAULT_RETENTION_DAYS
24+
days_ago = timezone.now() - timezone.timedelta(days=days)
25+
audit_logs = AuditLog.objects.filter(
26+
log_organization_id__isnull=True,
27+
created__lt=days_ago,
28+
)
29+
log.info("Deleting old audit logs.", days=days, count=audit_logs.count())
30+
audit_logs.delete()

readthedocs/audit/tests/test_tasks.py

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
from unittest import mock
2+
3+
from django.contrib.auth.models import User
4+
from django.test import TestCase
5+
from django.utils import timezone
6+
from django_dynamic_fixture import get
7+
8+
from readthedocs.audit.models import AuditLog
9+
from readthedocs.audit.tasks import delete_old_personal_audit_logs
10+
from readthedocs.organizations.models import Organization
11+
from readthedocs.projects.models import Project
12+
13+
14+
class AuditTasksTest(TestCase):
15+
def setUp(self):
16+
self.user = get(User)
17+
self.project = get(
18+
Project,
19+
slug="project",
20+
)
21+
self.organization = get(
22+
Organization,
23+
owners=[self.user],
24+
name="testorg",
25+
)
26+
self.organization.projects.add(self.project)
27+
28+
self.another_user = get(User)
29+
self.another_project = get(
30+
Project,
31+
slug="another-project",
32+
users=[self.user],
33+
)
34+
35+
@mock.patch("django.utils.timezone.now")
36+
def test_delete_old_personal_audit_logs(self, now_mock):
37+
now_mock.return_value = timezone.datetime(
38+
year=2021,
39+
month=5,
40+
day=5,
41+
)
42+
newer_date = timezone.datetime(
43+
year=2021,
44+
month=4,
45+
day=30,
46+
)
47+
middle_date = timezone.datetime(
48+
year=2021,
49+
month=4,
50+
day=5,
51+
)
52+
old_date = timezone.datetime(
53+
year=2021,
54+
month=3,
55+
day=20,
56+
)
57+
for date in [newer_date, middle_date, old_date]:
58+
for user in [self.user, self.another_user]:
59+
# Log attached to a project and organization.
60+
get(
61+
AuditLog,
62+
user=user,
63+
project=self.project,
64+
created=date,
65+
action=AuditLog.PAGEVIEW,
66+
)
67+
# Log attached to a project only.
68+
get(
69+
AuditLog,
70+
user=user,
71+
project=self.another_project,
72+
created=date,
73+
action=AuditLog.PAGEVIEW,
74+
)
75+
76+
# Log attached to the user only.
77+
get(
78+
AuditLog,
79+
user=user,
80+
created=date,
81+
action=AuditLog.AUTHN,
82+
)
83+
84+
# Log without a user.
85+
get(
86+
AuditLog,
87+
created=date,
88+
action=AuditLog.AUTHN_FAILURE,
89+
)
90+
91+
# Log with a organization, and without a user.
92+
get(
93+
AuditLog,
94+
project=self.project,
95+
created=date,
96+
action=AuditLog.AUTHN_FAILURE,
97+
)
98+
99+
self.assertEqual(AuditLog.objects.all().count(), 24)
100+
101+
# We don't have logs older than 90 days.
102+
delete_old_personal_audit_logs(days=90)
103+
self.assertEqual(AuditLog.objects.all().count(), 24)
104+
105+
# Only 5 logs can be deteled.
106+
delete_old_personal_audit_logs(days=30)
107+
self.assertEqual(AuditLog.objects.all().count(), 19)
108+
109+
# Only 5 logs can be deteled.
110+
delete_old_personal_audit_logs(days=10)
111+
self.assertEqual(AuditLog.objects.all().count(), 14)
112+
113+
# Only 5 logs can be deteled.
114+
delete_old_personal_audit_logs(days=1)
115+
self.assertEqual(AuditLog.objects.all().count(), 9)

readthedocs/settings/base.py

+5
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,11 @@ def TEMPLATES(self):
435435
'schedule': crontab(minute=0, hour=2),
436436
'options': {'queue': 'web'},
437437
},
438+
'weekly-delete-old-personal-audit-logs': {
439+
'task': 'readthedocs.audit.tasks.delete_old_personal_audit_logs',
440+
'schedule': crontab(day_of_week='wednesday', minute=0, hour=7),
441+
'options': {'queue': 'web'},
442+
},
438443
'every-day-resync-sso-organization-users': {
439444
'task': 'readthedocs.oauth.tasks.sync_remote_repositories_organizations',
440445
'schedule': crontab(minute=0, hour=4),

0 commit comments

Comments
 (0)