Add rate limit and don't use lock for vote

This commit is contained in:
cuom1999 2024-01-13 18:23:37 -06:00
parent 104cee9e81
commit 2cf386e8b5
5 changed files with 45 additions and 43 deletions

View file

@ -491,6 +491,11 @@ DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
# Chunk upload
CHUNK_UPLOAD_DIR = "/tmp/chunk_upload_tmp"
# Rate limit
RL_VOTE = "200/h"
RL_COMMENT = "30/h"
try:
with open(os.path.join(os.path.dirname(__file__), "local_settings.py")) as f:
exec(f.read(), globals())

View file

@ -21,6 +21,7 @@ from django.views.generic.base import TemplateResponseMixin
from django.views.generic.detail import SingleObjectMixin
from reversion import revisions
from reversion.models import Revision, Version
from django_ratelimit.decorators import ratelimit
from judge.dblock import LockModel
from judge.models import Comment, Notification
@ -93,6 +94,7 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
and self.request.participation.contest.use_clarifications
)
@method_decorator(ratelimit(key="user", rate=settings.RL_COMMENT))
@method_decorator(login_required)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
@ -115,9 +117,7 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
comment.author = request.profile
comment.linked_object = self.object
with LockModel(
write=(Comment, Revision, Version), read=(ContentType,)
), revisions.create_revision():
with revisions.create_revision():
revisions.set_user(request.user)
revisions.set_comment(_("Posted comment"))
comment.save()

View file

@ -22,6 +22,8 @@ from django.views.generic import DetailView, UpdateView
from django.urls import reverse_lazy
from reversion import revisions
from reversion.models import Version
from django.conf import settings
from django_ratelimit.decorators import ratelimit
from judge.dblock import LockModel
from judge.models import Comment, CommentVote, Notification, BlogPost
@ -40,6 +42,7 @@ __all__ = [
]
@ratelimit(key="user", rate=settings.RL_VOTE)
@login_required
def vote_comment(request, delta):
if abs(delta) != 1:
@ -77,27 +80,24 @@ def vote_comment(request, delta):
vote.voter = request.profile
vote.score = delta
while True:
try:
vote.save()
except IntegrityError:
with LockModel(write=(CommentVote,)):
try:
vote = CommentVote.objects.get(
comment_id=comment_id, voter=request.profile
)
except CommentVote.DoesNotExist:
# We must continue racing in case this is exploited to manipulate votes.
continue
if -vote.score != delta:
return HttpResponseBadRequest(
_("You already voted."), content_type="text/plain"
)
vote.delete()
Comment.objects.filter(id=comment_id).update(score=F("score") - vote.score)
else:
Comment.objects.filter(id=comment_id).update(score=F("score") + delta)
break
try:
vote.save()
except IntegrityError:
with LockModel(write=(CommentVote,)):
try:
vote = CommentVote.objects.get(
comment_id=comment_id, voter=request.profile
)
except CommentVote.DoesNotExist:
raise Http404()
if -vote.score != delta:
return HttpResponseBadRequest(
_("You already voted."), content_type="text/plain"
)
vote.delete()
Comment.objects.filter(id=comment_id).update(score=F("score") - vote.score)
else:
Comment.objects.filter(id=comment_id).update(score=F("score") + delta)
return HttpResponse("success", content_type="text/plain")

View file

@ -12,8 +12,9 @@ from judge.models.pagevote import PageVote, PageVoteVoter
from django.views.generic.base import TemplateResponseMixin
from django.views.generic.detail import SingleObjectMixin
from judge.dblock import LockModel
from django.views.generic import View, ListView
from django_ratelimit.decorators import ratelimit
from django.conf import settings
__all__ = [
@ -24,6 +25,7 @@ __all__ = [
]
@ratelimit(key="user", rate=settings.RL_VOTE)
@login_required
def vote_page(request, delta):
if abs(delta) != 1:
@ -61,25 +63,19 @@ def vote_page(request, delta):
vote.voter = request.profile
vote.score = delta
while True:
try:
vote.save()
except IntegrityError:
try:
vote.save()
except IntegrityError:
with LockModel(write=(PageVoteVoter,)):
try:
vote = PageVoteVoter.objects.get(
pagevote_id=pagevote_id, voter=request.profile
)
except PageVoteVoter.DoesNotExist:
# We must continue racing in case this is exploited to manipulate votes.
continue
vote.delete()
PageVote.objects.filter(id=pagevote_id).update(
score=F("score") - vote.score
vote = PageVoteVoter.objects.get(
pagevote_id=pagevote_id, voter=request.profile
)
else:
PageVote.objects.filter(id=pagevote_id).update(score=F("score") + delta)
break
except PageVoteVoter.DoesNotExist:
raise Http404()
vote.delete()
PageVote.objects.filter(id=pagevote_id).update(score=F("score") - vote.score)
else:
PageVote.objects.filter(id=pagevote_id).update(score=F("score") + delta)
_dirty_vote_score(pagevote_id, request.profile)
return HttpResponse("success", content_type="text/plain")

View file

@ -43,3 +43,4 @@ pymdown-extensions
mdx-breakless-lists
beautifulsoup4
pre-commit
django-ratelimit