Add rate limit and don't use lock for vote
This commit is contained in:
parent
104cee9e81
commit
2cf386e8b5
5 changed files with 45 additions and 43 deletions
|
@ -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())
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -43,3 +43,4 @@ pymdown-extensions
|
|||
mdx-breakless-lists
|
||||
beautifulsoup4
|
||||
pre-commit
|
||||
django-ratelimit
|
Loading…
Reference in a new issue