From d57cf64afa943b82c0a87195c7d9977bad67f72a Mon Sep 17 00:00:00 2001 From: virusdefender Date: Sun, 22 Sep 2019 16:45:25 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=AF=94=E8=B5=9B=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=AF=86=E7=A0=81=E4=B9=8B=E5=90=8E=EF=BC=8C=E4=B9=8B?= =?UTF-8?q?=E5=89=8D=E4=BF=9D=E5=AD=98=E7=9A=84=20session=20=E4=BB=8D?= =?UTF-8?q?=E7=84=B6=E5=8F=AF=E4=BB=A5=E4=BD=BF=E7=94=A8=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- account/decorators.py | 34 ++++++++++++++++++++++++++++++++-- contest/views/oj.py | 21 +++++++++++++-------- utils/constants.py | 3 +++ 3 files changed, 48 insertions(+), 10 deletions(-) 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