Skip to content

Commit 16be3c3

Browse files
committed
first commit
0 parents  commit 16be3c3

File tree

158 files changed

+8977
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

158 files changed

+8977
-0
lines changed

.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.idea
2+
venv
3+
logs
4+
__pycache__
5+
*.pyc
6+
docker_deploy
7+
migrations

account/__init__.py

Whitespace-only changes.

account/apps.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.apps import AppConfig
2+
3+
4+
class AccountConfig(AppConfig):
5+
default_auto_field = 'django.db.models.BigAutoField'
6+
name = 'account'

account/decorators.py

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import functools
2+
import hashlib
3+
import time
4+
5+
from .models import User
6+
from problem.models import Problem
7+
from contest.models import Contest, ContestType, ContestStatus, ContestRuleType
8+
from utils.api import JSONResponse, APIError
9+
from utils.constants import CONTEST_PASSWORD_SESSION_KEY
10+
from .models import ProblemPermission
11+
12+
13+
class BasePermissionDecorator(object):
14+
def __init__(self, func):
15+
self.func = func
16+
17+
def __get__(self, obj, obj_type):
18+
return functools.partial(self.__call__, obj)
19+
20+
def error(self, data):
21+
return JSONResponse.response({"error": "permission-denied", "data": data})
22+
23+
def __call__(self, *args, **kwargs):
24+
self.request = args[1]
25+
26+
if self.check_permission():
27+
if self.request.user.is_disabled:
28+
return self.error("Your account is disabled")
29+
return self.func(*args, **kwargs)
30+
else:
31+
return self.error("Please login first")
32+
33+
def check_permission(self):
34+
raise NotImplementedError()
35+
36+
37+
class login_required(BasePermissionDecorator):
38+
def check_permission(self):
39+
return self.request.user.is_authenticated
40+
41+
42+
class super_admin_required(BasePermissionDecorator):
43+
def check_permission(self):
44+
user = self.request.user
45+
return user.is_authenticated and user.is_super_admin()
46+
47+
48+
class admin_role_required(BasePermissionDecorator):
49+
def check_permission(self):
50+
user = self.request.user
51+
return user.is_authenticated and user.is_admin_role()
52+
53+
54+
class problem_permission_required(admin_role_required):
55+
def check_permission(self):
56+
if not super(problem_permission_required, self).check_permission():
57+
return False
58+
if self.request.user.problem_permission == ProblemPermission.NONE:
59+
return False
60+
return True
61+
62+
63+
def check_contest_password(password, contest_password):
64+
if not (password and contest_password):
65+
return False
66+
if password == contest_password:
67+
return True
68+
else:
69+
# sig#timestamp 这种形式的密码也可以,但是在界面上没提供支持
70+
# sig = sha256(contest_password + timestamp)[:8]
71+
if "#" in password:
72+
s = password.split("#")
73+
if len(s) != 2:
74+
return False
75+
sig, ts = s[0], s[1]
76+
77+
if sig == hashlib.sha256((contest_password + ts).encode("utf-8")).hexdigest()[:8]:
78+
try:
79+
ts = int(ts)
80+
except Exception:
81+
return False
82+
return int(time.time()) < ts
83+
else:
84+
return False
85+
else:
86+
return False
87+
88+
89+
def check_contest_permission(check_type="details"):
90+
"""
91+
只供Class based view 使用,检查用户是否有权进入该contest, check_type 可选 details, problems, ranks, submissions
92+
若通过验证,在view中可通过self.contest获得该contest
93+
"""
94+
95+
def decorator(func):
96+
def _check_permission(*args, **kwargs):
97+
self = args[0]
98+
request = args[1]
99+
user = request.user
100+
if request.data.get("contest_id"):
101+
contest_id = request.data["contest_id"]
102+
else:
103+
contest_id = request.GET.get("contest_id")
104+
if not contest_id:
105+
return self.error("Parameter error, contest_id is required")
106+
107+
try:
108+
# use self.contest to avoid query contest again in view.
109+
self.contest = Contest.objects.select_related("created_by").get(id=contest_id, visible=True)
110+
except Contest.DoesNotExist:
111+
return self.error("Contest %s doesn't exist" % contest_id)
112+
113+
# Anonymous
114+
if not user.is_authenticated:
115+
return self.error("Please login first.")
116+
117+
# creator or owner
118+
if user.is_contest_admin(self.contest):
119+
return func(*args, **kwargs)
120+
121+
if self.contest.contest_type == ContestType.PASSWORD_PROTECTED_CONTEST:
122+
# password error
123+
if not check_contest_password(
124+
request.session.get(CONTEST_PASSWORD_SESSION_KEY, {}).get(self.contest.id),
125+
self.contest.password):
126+
return self.error("Wrong password or password expired")
127+
128+
# regular user get contest problems, ranks etc. before contest started
129+
if self.contest.status == ContestStatus.CONTEST_NOT_START:
130+
return self.error("Contest has not started yet.")
131+
132+
133+
return func(*args, **kwargs)
134+
135+
return _check_permission
136+
137+
return decorator
138+
139+
140+
def ensure_created_by(obj, user):
141+
e = APIError(msg=f"{obj.__class__.__name__} does not exist")
142+
if not user.is_admin_role():
143+
raise e
144+
if user.is_super_admin():
145+
return
146+
if isinstance(obj, Problem):
147+
if not user.can_mgmt_all_problem() and obj.created_by != user:
148+
raise e
149+
elif obj.created_by != user:
150+
raise e
151+
152+
153+
# 确定对Lab Contest有管理权限
154+
def ensure_managed_by(contest: Contest, user: User):
155+
e = APIError("Illegal Manager")
156+
if not user.is_admin_role():
157+
raise e
158+
if user.is_super_admin():
159+
return
160+
if contest.created_by == user or str(user.id) in contest.contest_admin:
161+
return
162+
else:
163+
raise e

account/middleware.py

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from django.conf import settings
2+
from django.db import connection
3+
from django.utils.timezone import now
4+
from django.utils.deprecation import MiddlewareMixin
5+
6+
from utils.api import JSONResponse
7+
from account.models import User
8+
9+
10+
class APITokenAuthMiddleware(MiddlewareMixin):
11+
def process_request(self, request):
12+
appkey = request.META.get("HTTP_APPKEY")
13+
if appkey:
14+
try:
15+
request.user = User.objects.get(open_api_appkey=appkey, open_api=True, is_disabled=False)
16+
request.csrf_processing_done = True
17+
request.auth_method = "api_key"
18+
except User.DoesNotExist:
19+
pass
20+
21+
22+
class SessionRecordMiddleware(MiddlewareMixin):
23+
def process_request(self, request):
24+
request.ip = request.META.get(settings.IP_HEADER, request.META.get("REMOTE_ADDR"))
25+
if request.user.is_authenticated:
26+
session = request.session
27+
session["user_agent"] = request.META.get("HTTP_USER_AGENT", "")
28+
session["ip"] = request.ip
29+
session["last_activity"] = now()
30+
user_sessions = request.user.session_keys
31+
if session.session_key not in user_sessions:
32+
user_sessions.append(session.session_key)
33+
request.user.save()
34+
35+
36+
class AdminRoleRequiredMiddleware(MiddlewareMixin):
37+
def process_request(self, request):
38+
path = request.path_info
39+
if path.startswith("/admin/") or path.startswith("/api/admin/"):
40+
if not (request.user.is_authenticated and request.user.is_admin_role()):
41+
return JSONResponse.response({"error": "login-required", "data": "Please login in first"})
42+
43+
44+
class LogSqlMiddleware(MiddlewareMixin):
45+
def process_response(self, request, response):
46+
print("\033[94m", "#" * 30, "\033[0m")
47+
time_threshold = 0.03
48+
for query in connection.queries:
49+
if float(query["time"]) > time_threshold:
50+
print("\033[93m", query, "\n", "-" * 30, "\033[0m")
51+
else:
52+
print(query, "\n", "-" * 30)
53+
return response

account/models.py

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import uuid
2+
3+
from django.db import models
4+
from django.contrib.auth.models import AbstractBaseUser
5+
6+
# Create your models here.
7+
from utils.models import JSONField, ListFeild
8+
9+
10+
class AdminType(object):
11+
REGULAR_USER = "Regular User" #student
12+
ADMIN = "Admin" # tutor
13+
SUPER_ADMIN = "Super Admin" #teacher
14+
15+
class ProblemPermission(object):
16+
NONE = "None"
17+
OWN = "Own"
18+
ALL = "All"
19+
20+
class UserManager(models.Manager):
21+
use_in_migrations = True
22+
23+
def get_by_natural_key(self, username):
24+
return self.get(**{f"{self.model.USERNAME_FIELD}__iexact": username})
25+
26+
class User(AbstractBaseUser):
27+
#每次添加都会自动生成
28+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
29+
username = models.TextField(unique=True)
30+
email = models.TextField(null=True, unique=True)
31+
create_time = models.DateTimeField(auto_now_add=True, null=True)
32+
#UserType
33+
admin_type = models.TextField(default=AdminType.REGULAR_USER)
34+
problem_permission = models.TextField(default=ProblemPermission.NONE)
35+
reset_password_token = models.TextField(null=True)
36+
reset_password_token_expire_time = models.DateTimeField(null=True)
37+
#SSO auth token
38+
auth_token = models.TextField(null=True)
39+
two_factor_auth = models.BooleanField(default=False)
40+
tfa_token = models.TextField(null=True)
41+
session_keys = JSONField(default=list)
42+
# open api key
43+
open_api = models.BooleanField(default=False)
44+
open_api_appkey = models.TextField(null=True)
45+
is_disabled = models.BooleanField(default=False)
46+
47+
48+
USERNAME_FIELD = "username"
49+
REQUIRED_FIELDS = []
50+
51+
objects = UserManager() #
52+
53+
def is_admin(self):
54+
return self.admin_type == AdminType.ADMIN
55+
56+
def is_super_admin(self):
57+
return self.admin_type == AdminType.SUPER_ADMIN
58+
59+
def is_admin_role(self):
60+
return self.admin_type in [AdminType.ADMIN, AdminType.SUPER_ADMIN]
61+
62+
def can_mgmt_all_problem(self):
63+
return self.problem_permission == ProblemPermission.ALL
64+
65+
def is_contest_admin(self, contest):
66+
return self.is_authenticated and (str(self.id) in contest.contest_admin or self.admin_type == AdminType.SUPER_ADMIN)
67+
68+
class Meta:
69+
db_table = "user"
70+
71+
class UserProfile(models.Model):
72+
user = models.OneToOneField(User, on_delete=models.CASCADE)
73+
# Task_problems_status examples:
74+
# {
75+
# "problems": {
76+
# "1": {
77+
# "status": JudgeStatus.ACCEPTED,
78+
# "Score:" 80,
79+
# "passed test case": [1, 2],
80+
# "_id": "1000"
81+
# }
82+
# },
83+
# "contest_problems": {
84+
# "1": {
85+
# "status": JudgeStatus.Error,
86+
# "Score:" 80,
87+
# "passed test case": [1, 2],
88+
# "_id": "1000"
89+
# }
90+
# }
91+
# }
92+
problems_status = JSONField(default=dict)
93+
real_name = models.TextField(null=True)
94+
blog = models.URLField(null=True)
95+
github = models.TextField(null=True)
96+
school = models.TextField(null=True)
97+
major = models.TextField(null=True)
98+
language = models.TextField(null=True)
99+
#for Contest
100+
accepted_number = models.IntegerField(default=0)
101+
total_score = models.IntegerField(default=0)
102+
103+
def add_accepted_problem_number(self):
104+
self.accepted_number = models.F("accepted_number") + 1
105+
self.save()
106+
107+
def add_score(self, this_time_score, last_time_score=None):
108+
last_time_score = last_time_score or 0
109+
self.total_score = models.F("total_score") - last_time_score + this_time_score
110+
self.save()
111+
class Meta:
112+
db_table = "user_profile"

0 commit comments

Comments
 (0)