Skip to content

Commit

Permalink
Audit Log Model Changes (#323)
Browse files Browse the repository at this point in the history
* Pin to python3.10 and fix pyyaml

* backend work for leaderboard groups

* lol

* return names

* add uritemplate

* Upgrade uvicorn to use a version of websockets from this decade

* Add audit logging

---------

Co-authored-by: Ada Cooke <[email protected]>
  • Loading branch information
Bentechy66 and 0xAda authored Oct 16, 2023
1 parent c77c5bb commit ad04c9b
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 15 deletions.
2 changes: 1 addition & 1 deletion src/admin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ class AuditLogView(APIView):
permission_classes = [IsAdminUser]

def get(self, request):
serializer = AuditLogSerializer(data=AuditLogEntry.objects.all(), many=True)
serializer = AuditLogSerializer(data=AuditLogEntry.objects.order_by("-id").all(), many=True)
serializer.is_valid()
return FormattedResponse(serializer.data)
3 changes: 2 additions & 1 deletion src/announcements/views.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from backend.viewsets import AuditLoggedViewSet
from rest_framework.viewsets import ModelViewSet

from announcements.models import Announcement
from announcements.serializers import AnnouncementSerializer
from backend.permissions import AdminOrReadOnly


class AnnouncementViewSet(ModelViewSet):
class AnnouncementViewSet(AuditLoggedViewSet, ModelViewSet):
queryset = Announcement.objects.all()
permission_classes = (AdminOrReadOnly,)
throttle_scope = "announcement"
Expand Down
4 changes: 2 additions & 2 deletions src/authentication/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
remove_2fa,
verify_2fa,
)
from backend.viewsets import AdminListModelViewSet
from backend.viewsets import AdminListModelViewSet, AuditLoggedViewSet
from config import config
from member.models import Member
from plugins import providers
Expand Down Expand Up @@ -342,7 +342,7 @@ def post(self, request):
return FormattedResponse({"invite_codes": codes})


class InviteViewSet(AdminListModelViewSet):
class InviteViewSet(AuditLoggedViewSet, AdminListModelViewSet):
permission_classes = (permissions.IsAdminUser,)
admin_serializer_class = InviteCodeSerializer
list_admin_serializer_class = InviteCodeSerializer
Expand Down
57 changes: 57 additions & 0 deletions src/backend/viewsets.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from admin.models import AuditLogEntry
from rest_framework import permissions
from rest_framework.viewsets import ModelViewSet

Expand Down Expand Up @@ -37,3 +38,59 @@ def get_serializer_class(self):
if self.request.user.is_staff and not self.request.user.should_deny_admin():
return self.admin_serializer_class
return self.serializer_class


class AuditLoggedViewSet(ModelViewSet):
def create(self, request, *args, **kwargs):
if request.user is not None and request.user.is_staff:
ret = super().create(request, *args, **kwargs)

fields = {}
fields["model_fields"] = ret.data
fields["model_name"] = self.get_serializer().Meta.model.__name__
AuditLogEntry.objects.create(user=request.user, username=request.user.username, action="create_model", extra=fields)

return ret
return super().create(request, *args, **kwargs)

def destroy(self, request, *args, **kwargs):
if request.user is not None and request.user.is_staff:
instance = self.get_object()
fields = {}
fields["model_fields"] = self.get_serializer(instance).data
fields["model_name"] = instance._meta.model.__name__
fields["model_id"] = instance.id
AuditLogEntry.objects.create(user=request.user, username=request.user.username, action="destroy_model", extra=fields)

ret = super().destroy(request, *args, **kwargs)

return ret
return super().destroy(request, *args, **kwargs)

def update(self, request, *args, **kwargs):
if request.user is not None and request.user.is_staff:
old_instance = self.get_object() # Keep track of old data
old_data = self.get_serializer(old_instance).data

ret = super().update(request, *args, **kwargs)

new_instance = self.get_object() # Get the new data
new_data = self.get_serializer(new_instance).data

diffs = {}

for key, value in new_data.items():
if old_data.get(key, None) != value:
diffs[key] = {
"old": old_data.get(key, None),
"new": new_data.get(key, None)
}

fields = {}
fields["updated_fields"] = diffs
fields["model_name"] = new_instance._meta.model.__name__
fields["model_id"] = new_instance.id
AuditLogEntry.objects.create(user=request.user, username=request.user.username, action="update_model", extra=fields)

return ret
return super().update(request, *args, **kwargs)
6 changes: 3 additions & 3 deletions src/challenge/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from backend.permissions import AdminOrReadOnly, IsBot, ReadOnlyBot
from backend.response import FormattedResponse
from backend.signals import flag_reject, flag_score, flag_submit
from backend.viewsets import AdminCreateModelViewSet
from backend.viewsets import AdminCreateModelViewSet, AuditLoggedViewSet
from challenge.models import (
Category,
Challenge,
Expand Down Expand Up @@ -64,7 +64,7 @@ def get_cache_key(user):
return str(caches["default"].get("challenge_mod_index", 0)) + "categoryvs_team_" + str(user.team.pk)


class CategoryViewset(AdminCreateModelViewSet):
class CategoryViewset(AuditLoggedViewSet, AdminCreateModelViewSet):
queryset = Category.objects.all()
permission_classes = (CompetitionOpen & AdminOrReadOnly,)
throttle_scope = "challenges"
Expand Down Expand Up @@ -147,7 +147,7 @@ def list(self, request, *args, **kwargs):
return FormattedResponse(categories)


class ChallengeViewset(AdminCreateModelViewSet):
class ChallengeViewset(AuditLoggedViewSet, AdminCreateModelViewSet):
queryset = Challenge.objects.all()
permission_classes = (CompetitionOpen & AdminOrReadOnly,)
throttle_scope = "challenges"
Expand Down
4 changes: 2 additions & 2 deletions src/hint/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from backend.permissions import IsBot
from backend.response import FormattedResponse
from backend.signals import use_hint
from backend.viewsets import AdminCreateModelViewSet
from backend.viewsets import AdminCreateModelViewSet, AuditLoggedViewSet
from challenge.permissions import CompetitionOpen
from challenge.views import get_cache_key
from hint.models import Hint, HintUse
Expand All @@ -21,7 +21,7 @@
from team.permissions import HasTeam


class HintViewSet(AdminCreateModelViewSet):
class HintViewSet(AuditLoggedViewSet, AdminCreateModelViewSet):
queryset = Hint.objects.all()
permission_classes = (HasUsedHint,)
throttle_scope = "hint"
Expand Down
4 changes: 2 additions & 2 deletions src/member/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from rest_framework.viewsets import ModelViewSet

from backend.permissions import AdminOrReadOnlyVisible, ReadOnlyBot
from backend.viewsets import AdminListModelViewSet
from backend.viewsets import AdminListModelViewSet, AuditLoggedViewSet
from member.models import UserIP, Member
from member.serializers import (
AdminMemberSerializer,
Expand Down Expand Up @@ -43,7 +43,7 @@ def get_object(self):
)


class MemberViewSet(AdminListModelViewSet):
class MemberViewSet(AuditLoggedViewSet, AdminListModelViewSet):
permission_classes = (AdminOrReadOnlyVisible,)
throttle_scope = "member"
serializer_class = MemberSerializer
Expand Down
3 changes: 2 additions & 1 deletion src/pages/views.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from backend.viewsets import AuditLoggedViewSet
from rest_framework.viewsets import ModelViewSet

from backend.permissions import AdminOrAnonymousReadOnly
from pages.models import Page
from pages.serializers import PageSerializer


class TagViewSet(ModelViewSet):
class TagViewSet(AuditLoggedViewSet, ModelViewSet):
queryset = Page.objects.all()
permission_classes = (AdminOrAnonymousReadOnly,)
throttle_scope = "pages"
Expand Down
6 changes: 3 additions & 3 deletions src/team/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from backend.permissions import AdminOrReadOnlyVisible, ReadOnlyBot, AdminOrReadOnly
from backend.response import FormattedResponse
from backend.signals import team_join, team_join_attempt, team_join_reject
from backend.viewsets import AdminListModelViewSet
from backend.viewsets import AdminListModelViewSet, AuditLoggedViewSet
from challenge.models import Solve
from config import config
from member.models import Member
Expand Down Expand Up @@ -57,7 +57,7 @@ def get_object(self):
)


class LeaderboardGroupViewSet(AdminListModelViewSet):
class LeaderboardGroupViewSet(AuditLoggedViewSet, AdminListModelViewSet):
permission_classes = (AdminOrReadOnly,)
serializer_class = LeaderboardGroupSerializer
admin_serializer_class = LeaderboardGroupSerializer
Expand All @@ -66,7 +66,7 @@ class LeaderboardGroupViewSet(AdminListModelViewSet):
queryset = LeaderboardGroup.objects.all()


class TeamViewSet(AdminListModelViewSet):
class TeamViewSet(AuditLoggedViewSet, AdminListModelViewSet):
permission_classes = (AdminOrReadOnlyVisible,)
throttle_scope = "team"
serializer_class = TeamSerializer
Expand Down

0 comments on commit ad04c9b

Please sign in to comment.