diff --git a/account/decorators.py b/account/decorators.py index 08aaa811..0b6f236d 100644 --- a/account/decorators.py +++ b/account/decorators.py @@ -1,7 +1,11 @@ import functools +import hashlib +import time + from problem.models import Problem from contest.models import Contest, ContestType, ContestStatus, ContestRuleType from utils.api import JSONResponse, APIError +from utils.constants import CONTEST_PASSWORD_SESSION_KEY from .models import ProblemPermission @@ -55,6 +59,32 @@ class problem_permission_required(admin_role_required): return True +def check_contest_password(password, contest_password): + if not (password and contest_password): + return False + if password == contest_password: + return True + else: + # sig#timestamp 这种形式的密码也可以,但是在界面上没提供支持 + # sig = sha256(contest_password + timestamp)[:8] + if "#" in password: + s = password.split("#") + if len(s) != 2: + return False + sig, ts = s[0], s[1] + + if sig == hashlib.sha256((contest_password + ts).encode("utf-8")).hexdigest()[:8]: + try: + ts = int(ts) + except Exception: + return False + return int(time.time()) < ts + else: + return False + else: + return False + + def check_contest_permission(check_type="details"): """ 只供Class based view 使用,检查用户是否有权进入该contest, check_type 可选 details, problems, ranks, submissions @@ -89,8 +119,8 @@ def check_contest_permission(check_type="details"): if self.contest.contest_type == ContestType.PASSWORD_PROTECTED_CONTEST: # password error - if self.contest.id not in request.session.get("accessible_contests", []): - return self.error("Password is required.") + if not check_contest_password(request.session.get(CONTEST_PASSWORD_SESSION_KEY, {}).get(self.contest.id), self.contest.password): + return self.error("Wrong password or password expired") # regular user get contest problems, ranks etc. before contest started if self.contest.status == ContestStatus.CONTEST_NOT_START and check_type != "details": diff --git a/contest/views/oj.py b/contest/views/oj.py index 3aeb6810..4164ec33 100644 --- a/contest/views/oj.py +++ b/contest/views/oj.py @@ -7,10 +7,10 @@ from django.core.cache import cache from problem.models import Problem from utils.api import APIView, validate_serializer -from utils.constants import CacheKey +from utils.constants import CacheKey, CONTEST_PASSWORD_SESSION_KEY from utils.shortcuts import datetime2str, check_is_id from account.models import AdminType -from account.decorators import login_required, check_contest_permission +from account.decorators import login_required, check_contest_permission, check_contest_password from utils.constants import ContestRuleType, ContestStatus from ..models import ContestAnnouncement, Contest, OIContestRank, ACMContestRank @@ -76,13 +76,13 @@ class ContestPasswordVerifyAPI(APIView): contest = Contest.objects.get(id=data["contest_id"], visible=True, password__isnull=False) except Contest.DoesNotExist: return self.error("Contest does not exist") - if contest.password != data["password"]: - return self.error("Wrong password") + if not check_contest_password(data["password"], contest.password): + return self.error("Wrong password or password expired") # password verify OK. - if "accessible_contests" not in request.session: - request.session["accessible_contests"] = [] - request.session["accessible_contests"].append(contest.id) + if CONTEST_PASSWORD_SESSION_KEY not in request.session: + request.session[CONTEST_PASSWORD_SESSION_KEY] = {} + request.session[CONTEST_PASSWORD_SESSION_KEY][contest.id] = data["password"] # https://docs.djangoproject.com/en/dev/topics/http/sessions/#when-sessions-are-saved request.session.modified = True return self.success(True) @@ -94,7 +94,12 @@ class ContestAccessAPI(APIView): contest_id = request.GET.get("contest_id") if not contest_id: return self.error() - return self.success({"access": int(contest_id) in request.session.get("accessible_contests", [])}) + try: + contest = Contest.objects.get(id=contest_id, visible=True, password__isnull=False) + except Contest.DoesNotExist: + return self.error("Contest does not exist") + session_pass = request.session.get(CONTEST_PASSWORD_SESSION_KEY, {}).get(contest.id) + return self.success({"access": check_contest_password(session_pass, contest.password)}) class ContestRankAPI(APIView): diff --git a/utils/constants.py b/utils/constants.py index 004b8013..ac0c97b6 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -31,3 +31,6 @@ class Difficulty(Choices): LOW = "Low" MID = "Mid" HIGH = "High" + + +CONTEST_PASSWORD_SESSION_KEY = "contest_password" \ No newline at end of file