添加contest ip限制api;

OI problem的AC,total count也算入profile了
This commit is contained in:
zema1 2017-11-10 19:40:54 +08:00
parent 48f65d1a14
commit 727fbf48d8
13 changed files with 96 additions and 21 deletions

View File

@ -82,7 +82,7 @@ def check_contest_permission(check_type="details"):
return self.error("Contest %s doesn't exist" % contest_id) return self.error("Contest %s doesn't exist" % contest_id)
# creator or owner # creator or owner
if self.contest.is_contest_admin(user): if user.is_authenticated() and user.is_contest_admin(self.contest):
return func(*args, **kwargs) return func(*args, **kwargs)
if self.contest.contest_type == ContestType.PASSWORD_PROTECTED_CONTEST: if self.contest.contest_type == ContestType.PASSWORD_PROTECTED_CONTEST:

View File

@ -59,6 +59,9 @@ class User(AbstractBaseUser):
def can_mgmt_all_problem(self): def can_mgmt_all_problem(self):
return self.problem_permission == ProblemPermission.ALL return self.problem_permission == ProblemPermission.ALL
def is_contest_admin(self, contest):
return self.is_authenticated() and (contest.created_by == self or self.admin_type == AdminType.SUPER_ADMIN)
class Meta: class Meta:
db_table = "user" db_table = "user"

View File

@ -277,6 +277,8 @@ class UserChangePasswordAPI(APIView):
class ApplyResetPasswordAPI(APIView): class ApplyResetPasswordAPI(APIView):
@validate_serializer(ApplyResetPasswordSerializer) @validate_serializer(ApplyResetPasswordSerializer)
def post(self, request): def post(self, request):
if request.user.is_authenticated():
return self.error("You have already logged in, are you kidding me? ")
data = request.data data = request.data
captcha = Captcha(request) captcha = Captcha(request)
if not captcha.check(data["captcha"]): if not captcha.check(data["captcha"]):

View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-11-10 06:57
from __future__ import unicode_literals
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('contest', '0007_contestannouncement_visible'),
]
operations = [
migrations.AddField(
model_name='contest',
name='allowed_ip_ranges',
field=django.contrib.postgres.fields.jsonb.JSONField(default=list),
),
]

View File

@ -4,7 +4,7 @@ from django.utils.timezone import now
from utils.models import JSONField from utils.models import JSONField
from utils.constants import ContestStatus, ContestType from utils.constants import ContestStatus, ContestType
from account.models import User, AdminType from account.models import User
from utils.models import RichTextField from utils.models import RichTextField
@ -23,6 +23,7 @@ class Contest(models.Model):
created_by = models.ForeignKey(User) created_by = models.ForeignKey(User)
# 是否可见 false的话相当于删除 # 是否可见 false的话相当于删除
visible = models.BooleanField(default=True) visible = models.BooleanField(default=True)
allowed_ip_ranges = JSONField(default=list)
@property @property
def status(self): def status(self):
@ -42,14 +43,11 @@ class Contest(models.Model):
return ContestType.PASSWORD_PROTECTED_CONTEST return ContestType.PASSWORD_PROTECTED_CONTEST
return ContestType.PUBLIC_CONTEST return ContestType.PUBLIC_CONTEST
def is_contest_admin(self, user):
return user.is_authenticated() and (self.created_by == user or user.admin_type == AdminType.SUPER_ADMIN)
# 是否有权查看problem 的一些统计信息 诸如submission_number, accepted_number 等 # 是否有权查看problem 的一些统计信息 诸如submission_number, accepted_number 等
def problem_details_permission(self, user): def problem_details_permission(self, user):
return self.rule_type == ContestRuleType.ACM or \ return self.rule_type == ContestRuleType.ACM or \
self.status == ContestStatus.CONTEST_ENDED or \ self.status == ContestStatus.CONTEST_ENDED or \
self.is_contest_admin(user) or \ user.is_authenticated() and user.is_contest_admin(self) or \
self.real_time_rank self.real_time_rank
class Meta: class Meta:

View File

@ -13,6 +13,7 @@ class CreateConetestSeriaizer(serializers.Serializer):
password = serializers.CharField(allow_blank=True, max_length=32) password = serializers.CharField(allow_blank=True, max_length=32)
visible = serializers.BooleanField() visible = serializers.BooleanField()
real_time_rank = serializers.BooleanField() real_time_rank = serializers.BooleanField()
allowed_ip_ranges = serializers.ListField(child=serializers.CharField(max_length=32), allow_empty=True)
class EditConetestSeriaizer(serializers.Serializer): class EditConetestSeriaizer(serializers.Serializer):
@ -24,6 +25,7 @@ class EditConetestSeriaizer(serializers.Serializer):
password = serializers.CharField(allow_blank=True, allow_null=True, max_length=32) password = serializers.CharField(allow_blank=True, allow_null=True, max_length=32)
visible = serializers.BooleanField() visible = serializers.BooleanField()
real_time_rank = serializers.BooleanField() real_time_rank = serializers.BooleanField()
allowed_ip_ranges = serializers.ListField(child=serializers.CharField(max_length=32))
class ContestAdminSerializer(serializers.ModelSerializer): class ContestAdminSerializer(serializers.ModelSerializer):
@ -42,7 +44,7 @@ class ContestAdminSerializer(serializers.ModelSerializer):
class ContestSerializer(ContestAdminSerializer): class ContestSerializer(ContestAdminSerializer):
class Meta: class Meta:
model = Contest model = Contest
exclude = ("password", "visible") exclude = ("password", "visible", "allowed_ip_ranges")
class ContestAnnouncementSerializer(serializers.ModelSerializer): class ContestAnnouncementSerializer(serializers.ModelSerializer):

View File

@ -13,6 +13,7 @@ DEFAULT_CONTEST_DATA = {"title": "test title", "description": "test description"
"end_time": timezone.localtime(timezone.now()) + timedelta(days=1), "end_time": timezone.localtime(timezone.now()) + timedelta(days=1),
"rule_type": ContestRuleType.ACM, "rule_type": ContestRuleType.ACM,
"password": "123", "password": "123",
"allowed_ip_ranges": [],
"visible": True, "real_time_rank": True} "visible": True, "real_time_rank": True}

View File

@ -1,3 +1,4 @@
from ipaddress import ip_network
import dateutil.parser import dateutil.parser
from utils.api import APIView, validate_serializer from utils.api import APIView, validate_serializer
@ -21,6 +22,11 @@ class ContestAPI(APIView):
return self.error("Start time must occur earlier than end time") return self.error("Start time must occur earlier than end time")
if data.get("password") and data["password"] == "": if data.get("password") and data["password"] == "":
data["password"] = None data["password"] = None
for ip_range in data["allowed_ip_ranges"]:
try:
ip_network(ip_range, strict=False)
except ValueError:
return self.error(f"{ip_range} is not a valid cidr network")
contest = Contest.objects.create(**data) contest = Contest.objects.create(**data)
return self.success(ContestAdminSerializer(contest).data) return self.success(ContestAdminSerializer(contest).data)
@ -39,6 +45,12 @@ class ContestAPI(APIView):
return self.error("Start time must occur earlier than end time") return self.error("Start time must occur earlier than end time")
if not data["password"]: if not data["password"]:
data["password"] = None data["password"] = None
for ip_range in data["allowed_ip_ranges"]:
try:
ip_network(ip_range, strict=False)
except ValueError as e:
return self.error(f"{ip_range} is not a valid cidr network")
for k, v in data.items(): for k, v in data.items():
setattr(contest, k, v) setattr(contest, k, v)
contest.save() contest.save()

View File

@ -174,8 +174,8 @@ class JudgeDispatcher(object):
# update_userprofile # update_userprofile
user = User.objects.select_for_update().get(id=self.submission.user_id) user = User.objects.select_for_update().get(id=self.submission.user_id)
user_profile = user.userprofile user_profile = user.userprofile
user_profile.submission_number += 1
if problem.rule_type == ProblemRuleType.ACM: if problem.rule_type == ProblemRuleType.ACM:
user_profile.submission_number += 1
acm_problems_status = user_profile.acm_problems_status.get("problems", {}) acm_problems_status = user_profile.acm_problems_status.get("problems", {})
if problem_id not in acm_problems_status: if problem_id not in acm_problems_status:
acm_problems_status[problem_id] = {"status": self.submission.result, "_id": self.problem._id} acm_problems_status[problem_id] = {"status": self.submission.result, "_id": self.problem._id}
@ -196,14 +196,23 @@ class JudgeDispatcher(object):
oi_problems_status[problem_id] = {"status": self.submission.result, oi_problems_status[problem_id] = {"status": self.submission.result,
"_id": self.problem._id, "_id": self.problem._id,
"score": score} "score": score}
if self.submission.result == JudgeStatus.ACCEPTED:
user_profile.accepted_number += 1
else: else:
if oi_problems_status[problem_id]["status"] == JudgeStatus.ACCEPTED and \
self.submission.result != JudgeStatus.ACCEPTED:
user_profile.accepted_number -= 1
elif oi_problems_status[problem_id]["status"] != JudgeStatus.ACCEPTED and \
self.submission.result == JudgeStatus:
user_profile.accepted_number += 1
# minus last time score, add this time score # minus last time score, add this time score
user_profile.add_score(this_time_score=score, user_profile.add_score(this_time_score=score,
last_time_score=oi_problems_status[problem_id]["score"]) last_time_score=oi_problems_status[problem_id]["score"])
oi_problems_status[problem_id]["score"] = score oi_problems_status[problem_id]["score"] = score
oi_problems_status[problem_id]["status"] = self.submission.result oi_problems_status[problem_id]["status"] = self.submission.result
user_profile.oi_problems_status["problems"] = oi_problems_status user_profile.oi_problems_status["problems"] = oi_problems_status
user_profile.save(update_fields=["oi_problems_status"]) user_profile.save(update_fields=["submission_number", "accepted_number", "oi_problems_status"])
def update_contest_problem_status(self): def update_contest_problem_status(self):
if self.contest_id and self.contest.status != ContestStatus.CONTEST_UNDERWAY: if self.contest_id and self.contest.status != ContestStatus.CONTEST_UNDERWAY:

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-11-10 06:57
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('submission', '0007_auto_20170923_1318'),
]
operations = [
migrations.AddField(
model_name='submission',
name='ip',
field=models.CharField(blank=True, max_length=32, null=True),
),
]

View File

@ -36,6 +36,7 @@ class Submission(models.Model):
# 存储该提交所用时间和内存值,方便提交列表显示 # 存储该提交所用时间和内存值,方便提交列表显示
# {time_cost: "", memory_cost: "", err_info: "", score: 0} # {time_cost: "", memory_cost: "", err_info: "", score: 0}
statistic_info = JSONField(default=dict) statistic_info = JSONField(default=dict)
ip = models.CharField(max_length=32, null=True, blank=True)
def check_user_permission(self, user, check_share=True): def check_user_permission(self, user, check_share=True):
return self.user_id == user.id or \ return self.user_id == user.id or \

View File

@ -31,7 +31,7 @@ class SubmissionSafeModelSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Submission model = Submission
exclude = ("info", "contest") exclude = ("info", "contest", "ip")
class SubmissionListSerializer(serializers.ModelSerializer): class SubmissionListSerializer(serializers.ModelSerializer):
@ -45,7 +45,7 @@ class SubmissionListSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Submission model = Submission
exclude = ("info", "contest", "code") exclude = ("info", "contest", "code", "ip")
def get_show_link(self, obj): def get_show_link(self, obj):
# 没传user或为匿名user # 没传user或为匿名user

View File

@ -1,3 +1,5 @@
import ipaddress
from django.conf import settings from django.conf import settings
from account.decorators import login_required, check_contest_permission from account.decorators import login_required, check_contest_permission
from judge.tasks import judge_task from judge.tasks import judge_task
@ -54,23 +56,26 @@ class SubmissionAPI(APIView):
return self.error("Contest doesn't exist.") return self.error("Contest doesn't exist.")
if contest.status == ContestStatus.CONTEST_ENDED: if contest.status == ContestStatus.CONTEST_ENDED:
return self.error("The contest have ended") return self.error("The contest have ended")
if contest.status == ContestStatus.CONTEST_NOT_START and not contest.is_contest_admin(request.user): if not request.user.is_contest_admin(contest):
return self.error("Contest have not started") if contest.status == ContestStatus.CONTEST_NOT_START:
return self.error("Contest have not started")
user_ip = ipaddress.ip_address(request.session.get("ip"))
if contest.allowed_ip_ranges:
if not any(user_ip in ipaddress.ip_network(cidr) for cidr in contest.allowed_ip_ranges):
return self.error("Your IP is not allowed in this contest")
if not contest.problem_details_permission(request.user): if not contest.problem_details_permission(request.user):
hide_id = True hide_id = True
if data.get("captcha"): if data.get("captcha"):
if not Captcha(request).check(data["captcha"]): if not Captcha(request).check(data["captcha"]):
return self.error("Invalid captcha") return self.error("Invalid captcha")
error = self.throttling(request) error = self.throttling(request)
if error: if error:
return self.error(error) return self.error(error)
try: try:
problem = Problem.objects.get(id=data["problem_id"], problem = Problem.objects.get(id=data["problem_id"], contest_id=data.get("contest_id"), visible=True)
contest_id=data.get("contest_id"),
visible=True)
except Problem.DoesNotExist: except Problem.DoesNotExist:
return self.error("Problem not exist") return self.error("Problem not exist")
@ -79,6 +84,7 @@ class SubmissionAPI(APIView):
language=data["language"], language=data["language"],
code=data["code"], code=data["code"],
problem_id=problem.id, problem_id=problem.id,
ip=request.session["ip"],
contest_id=data.get("contest_id")) contest_id=data.get("contest_id"))
# use this for debug # use this for debug
# JudgeDispatcher(submission.id, problem.id).judge() # JudgeDispatcher(submission.id, problem.id).judge()
@ -100,10 +106,10 @@ class SubmissionAPI(APIView):
if not submission.check_user_permission(request.user): if not submission.check_user_permission(request.user):
return self.error("No permission for this submission") return self.error("No permission for this submission")
if submission.problem.rule_type == ProblemRuleType.ACM: if submission.problem.rule_type == ProblemRuleType.OI or request.user.is_admin_role():
submission_data = SubmissionSafeModelSerializer(submission).data
else:
submission_data = SubmissionModelSerializer(submission).data submission_data = SubmissionModelSerializer(submission).data
else:
submission_data = SubmissionSafeModelSerializer(submission).data
# 是否有权限取消共享 # 是否有权限取消共享
submission_data["can_unshare"] = submission.check_user_permission(request.user, check_share=False) submission_data["can_unshare"] = submission.check_user_permission(request.user, check_share=False)
return self.success(submission_data) return self.success(submission_data)
@ -145,7 +151,7 @@ class SubmissionListAPI(APIView):
except Problem.DoesNotExist: except Problem.DoesNotExist:
return self.error("Problem doesn't exist") return self.error("Problem doesn't exist")
submissions = submissions.filter(problem=problem) submissions = submissions.filter(problem=problem)
if (myself and myself == "1") and not SysOptions.submission_list_show_all: if (myself and myself == "1") or not SysOptions.submission_list_show_all:
submissions = submissions.filter(user_id=request.user.id) submissions = submissions.filter(user_id=request.user.id)
elif username: elif username:
submissions = submissions.filter(username=username) submissions = submissions.filter(username=username)