tiny work

This commit is contained in:
virusdefender 2017-10-02 05:16:14 +08:00
parent edb32eaf7b
commit a324d55364
12 changed files with 111 additions and 161 deletions

View File

@ -24,22 +24,22 @@ class UserManager(models.Manager):
class User(AbstractBaseUser):
username = models.CharField(max_length=30, unique=True)
email = models.EmailField(max_length=254, null=True)
username = models.CharField(max_length=32, unique=True)
email = models.EmailField(max_length=64, null=True)
create_time = models.DateTimeField(auto_now_add=True, null=True)
# One of UserType
admin_type = models.CharField(max_length=24, default=AdminType.REGULAR_USER)
problem_permission = models.CharField(max_length=24, default=ProblemPermission.NONE)
reset_password_token = models.CharField(max_length=40, null=True)
admin_type = models.CharField(max_length=32, default=AdminType.REGULAR_USER)
problem_permission = models.CharField(max_length=32, default=ProblemPermission.NONE)
reset_password_token = models.CharField(max_length=32, null=True)
reset_password_token_expire_time = models.DateTimeField(null=True)
# SSO auth token
auth_token = models.CharField(max_length=40, null=True)
auth_token = models.CharField(max_length=32, null=True)
two_factor_auth = models.BooleanField(default=False)
tfa_token = models.CharField(max_length=40, null=True)
tfa_token = models.CharField(max_length=32, null=True)
session_keys = JSONField(default=[])
# open api key
open_api = models.BooleanField(default=False)
open_api_appkey = models.CharField(max_length=35, null=True)
open_api_appkey = models.CharField(max_length=32, null=True)
is_disabled = models.BooleanField(default=False)
USERNAME_FIELD = "username"
@ -63,10 +63,6 @@ class User(AbstractBaseUser):
db_table = "user"
def _default_avatar():
return f"/{settings.IMAGE_UPLOAD_DIR}/default.png"
class UserProfile(models.Model):
user = models.OneToOneField(User)
# Store user problem solution status with json string format
@ -75,14 +71,13 @@ class UserProfile(models.Model):
# {problems: {1: 33}, contest_problems: {1: 44}, record problem_id and score
oi_problems_status = JSONField(default={})
real_name = models.CharField(max_length=30, blank=True, null=True)
avatar = models.CharField(max_length=50, default=_default_avatar())
real_name = models.CharField(max_length=32, blank=True, null=True)
avatar = models.CharField(max_length=256, default=f"{settings.IMAGE_UPLOAD_DIR}/default.png")
blog = models.URLField(blank=True, null=True)
mood = models.CharField(max_length=200, blank=True, null=True)
github = models.CharField(max_length=50, blank=True, null=True)
school = models.CharField(max_length=200, blank=True, null=True)
major = models.CharField(max_length=200, blank=True, null=True)
language = models.CharField(max_length=32, blank=True, null=True)
mood = models.CharField(max_length=256, blank=True, null=True)
github = models.CharField(max_length=64, blank=True, null=True)
school = models.CharField(max_length=64, blank=True, null=True)
major = models.CharField(max_length=64, blank=True, null=True)
# for ACM
accepted_number = models.IntegerField(default=0)
# for OI

View File

@ -6,27 +6,27 @@ from .models import AdminType, ProblemPermission, User, UserProfile
class UserLoginSerializer(serializers.Serializer):
username = serializers.CharField(max_length=30)
password = serializers.CharField(max_length=30)
tfa_code = serializers.CharField(min_length=6, max_length=6, required=False, allow_null=True)
username = serializers.CharField()
password = serializers.CharField()
tfa_code = serializers.CharField(required=False, allow_null=True)
class UsernameOrEmailCheckSerializer(serializers.Serializer):
username = serializers.CharField(max_length=30, required=False)
email = serializers.EmailField(max_length=30, required=False)
username = serializers.CharField(required=False)
email = serializers.EmailField(required=False)
class UserRegisterSerializer(serializers.Serializer):
username = serializers.CharField(max_length=30)
password = serializers.CharField(max_length=30, min_length=6)
email = serializers.EmailField(max_length=30)
captcha = serializers.CharField(max_length=4, min_length=1)
username = serializers.CharField(max_length=32)
password = serializers.CharField(min_length=6)
email = serializers.EmailField(max_length=64)
captcha = serializers.CharField()
class UserChangePasswordSerializer(serializers.Serializer):
old_password = serializers.CharField()
new_password = serializers.CharField(max_length=30, min_length=6)
captcha = serializers.CharField(max_length=4, min_length=4)
new_password = serializers.CharField(min_length=6)
captcha = serializers.CharField()
class UserSerializer(serializers.ModelSerializer):
@ -58,9 +58,9 @@ class UserInfoSerializer(serializers.ModelSerializer):
class EditUserSerializer(serializers.Serializer):
id = serializers.IntegerField()
username = serializers.CharField(max_length=30)
password = serializers.CharField(max_length=30, min_length=6, allow_blank=True, required=False, default=None)
email = serializers.EmailField(max_length=254)
username = serializers.CharField(max_length=32)
password = serializers.CharField(min_length=6, allow_blank=True, required=False, default=None)
email = serializers.EmailField(max_length=64)
admin_type = serializers.ChoiceField(choices=(AdminType.REGULAR_USER, AdminType.ADMIN, AdminType.SUPER_ADMIN))
problem_permission = serializers.ChoiceField(choices=(ProblemPermission.NONE, ProblemPermission.OWN,
ProblemPermission.ALL))
@ -70,29 +70,29 @@ class EditUserSerializer(serializers.Serializer):
class EditUserProfileSerializer(serializers.Serializer):
real_name = serializers.CharField(max_length=30, allow_blank=True)
avatar = serializers.CharField(max_length=100, allow_blank=True, required=False)
blog = serializers.URLField(allow_blank=True, required=False)
mood = serializers.CharField(max_length=200, allow_blank=True, required=False)
github = serializers.CharField(max_length=50, allow_blank=True, required=False)
school = serializers.CharField(max_length=200, allow_blank=True, required=False)
major = serializers.CharField(max_length=200, allow_blank=True, required=False)
real_name = serializers.CharField(max_length=32, allow_blank=True)
avatar = serializers.CharField(max_length=256, allow_blank=True, required=False)
blog = serializers.URLField(max_length=256, allow_blank=True, required=False)
mood = serializers.CharField(max_length=256, allow_blank=True, required=False)
github = serializers.CharField(max_length=64, allow_blank=True, required=False)
school = serializers.CharField(max_length=64, allow_blank=True, required=False)
major = serializers.CharField(max_length=64, allow_blank=True, required=False)
class ApplyResetPasswordSerializer(serializers.Serializer):
email = serializers.EmailField()
captcha = serializers.CharField(max_length=4, min_length=4)
captcha = serializers.CharField()
class ResetPasswordSerializer(serializers.Serializer):
token = serializers.CharField(min_length=1, max_length=40)
password = serializers.CharField(min_length=6, max_length=30)
captcha = serializers.CharField(max_length=4, min_length=4)
token = serializers.CharField()
password = serializers.CharField(min_length=6)
captcha = serializers.CharField()
class SSOSerializer(serializers.Serializer):
appkey = serializers.CharField(max_length=35)
token = serializers.CharField(max_length=40)
appkey = serializers.CharField()
token = serializers.CharField()
class TwoFactorAuthCodeSerializer(serializers.Serializer):

View File

@ -19,7 +19,6 @@ urlpatterns = [
url(r"^check_username_or_email", UsernameOrEmailCheck.as_view(), name="check_username_or_email"),
url(r"^profile/?$", UserProfileAPI.as_view(), name="user_profile_api"),
url(r"^upload_avatar/?$", AvatarUploadAPI.as_view(), name="avatar_upload_api"),
url(r"^sso/?$", SSOAPI.as_view(), name="sso_api"),
url(r"^tfa_required/?$", CheckTFARequiredAPI.as_view(), name="tfa_required_check"),
url(r"^two_factor_auth/?$", TwoFactorAuthAPI.as_view(), name="two_factor_auth_api"),
url(r"^user_rank/?$", UserRankAPI.as_view(), name="user_rank_api"),

View File

@ -12,11 +12,10 @@ from django.utils.timezone import now
from django.views.decorators.csrf import ensure_csrf_cookie
from otpauth import OtpAuth
from utils.constants import ContestRuleType
from options.options import SysOptions
from utils.api import APIView, validate_serializer
from utils.cache import default_cache
from utils.captcha import Captcha
from utils.constants import CacheKey
from utils.shortcuts import rand_str, img2base64, timestamp2utcstr
from ..decorators import login_required
from ..models import User, UserProfile
@ -38,7 +37,7 @@ class UserProfileAPI(APIView):
"""
user = request.user
if not user.is_authenticated():
return self.success({})
return self.success()
username = request.GET.get("username")
try:
if username:
@ -47,8 +46,7 @@ class UserProfileAPI(APIView):
user = request.user
except User.DoesNotExist:
return self.error("User does not exist")
profile = UserProfile.objects.select_related("user").get(user=user)
return self.success(UserProfileSerializer(profile).data)
return self.success(UserProfileSerializer(user.userprofile).data)
@validate_serializer(EditUserProfileSerializer)
@login_required
@ -71,8 +69,7 @@ class AvatarUploadAPI(APIView):
avatar = form.cleaned_data["file"]
else:
return self.error("Invalid file content")
# 2097152 = 2 * 1024 * 1024 = 2MB
if avatar.size > 2097152:
if avatar.size > 2 * 1024 * 1024:
return self.error("Picture is too large")
suffix = os.path.splitext(avatar.name)[-1].lower()
if suffix not in [".gif", ".jpg", ".jpeg", ".bmp", ".png"]:
@ -83,46 +80,12 @@ class AvatarUploadAPI(APIView):
for chunk in avatar:
img.write(chunk)
user_profile = request.user.userprofile
_, old_avatar = os.path.split(user_profile.avatar)
if old_avatar != "default.png":
os.remove(os.path.join(settings.IMAGE_UPLOAD_DIR_ABS, old_avatar))
user_profile.avatar = f"/{settings.IMAGE_UPLOAD_DIR}/{name}"
user_profile.avatar = f"{settings.IMAGE_UPLOAD_DIR}/{name}"
user_profile.save()
return self.success("Succeeded")
class SSOAPI(APIView):
@login_required
def get(self, request):
callback = request.GET.get("callback", None)
if not callback:
return self.error("Parameter Error")
token = rand_str()
request.user.auth_token = token
request.user.save()
return self.success({"redirect_url": callback + "?token=" + token,
"callback": callback})
@validate_serializer(SSOSerializer)
def post(self, request):
data = request.data
try:
User.objects.get(open_api_appkey=data["appkey"])
except User.DoesNotExist:
return self.error("Invalid appkey")
try:
user = User.objects.get(auth_token=data["token"])
user.auth_token = None
user.save()
return self.success({"username": user.username,
"id": user.id,
"admin_type": user.admin_type,
"avatar": user.userprofile.avatar})
except User.DoesNotExist:
return self.error("User does not exist")
class TwoFactorAuthAPI(APIView):
@login_required
def get(self, request):
@ -131,7 +94,7 @@ class TwoFactorAuthAPI(APIView):
"""
user = request.user
if user.two_factor_auth:
return self.error("Already open 2FA")
return self.error("2FA is already turned on")
token = rand_str()
user.tfa_token = token
user.save()
@ -161,7 +124,7 @@ class TwoFactorAuthAPI(APIView):
code = request.data["code"]
user = request.user
if not user.two_factor_auth:
return self.error("Other session have disabled TFA")
return self.error("2FA is already turned off")
if OtpAuth(user.tfa_token).valid_totp(code):
user.two_factor_auth = False
user.save()
@ -198,7 +161,7 @@ class UserLoginAPI(APIView):
# None is returned if username or password is wrong
if user:
if user.is_disabled:
return self.error("Your account have been disabled")
return self.error("Your account has been disabled")
if not user.two_factor_auth:
auth.login(request, user)
return self.success("Succeeded")
@ -218,13 +181,13 @@ class UserLoginAPI(APIView):
# todo remove this, only for debug use
def get(self, request):
auth.login(request, auth.authenticate(username=request.GET["username"], password=request.GET["password"]))
return self.success({})
return self.success()
class UserLogoutAPI(APIView):
def get(self, request):
auth.logout(request)
return self.success({})
return self.success()
class UsernameOrEmailCheck(APIView):
@ -240,11 +203,9 @@ class UsernameOrEmailCheck(APIView):
"email": False
}
if data.get("username"):
if User.objects.filter(username=data["username"]).exists():
result["username"] = True
result["username"] = User.objects.filter(username=data["username"]).exists()
if data.get("email"):
if User.objects.filter(email=data["email"]).exists():
result["email"] = True
result["email"] = User.objects.filter(email=data["email"]).exists()
return self.success(result)
@ -254,17 +215,9 @@ class UserRegisterAPI(APIView):
"""
User register api
"""
config = default_cache.get(CacheKey.website_config)
if config:
config = pickle.loads(config)
else:
config = WebsiteConfig.objects.first()
if not config:
config = WebsiteConfig.objects.create()
default_cache.set(CacheKey.website_config, pickle.dumps(config))
if not config.allow_register:
return self.error("Register have been disabled by admin")
if not SysOptions.allow_register:
return self.error("Register function has been disabled by admin")
data = request.data
captcha = Captcha(request)
@ -293,6 +246,7 @@ class UserChangePasswordAPI(APIView):
username = request.user.username
user = auth.authenticate(username=username, password=data["old_password"])
if user:
# TODO: check tfa?
user.set_password(data["new_password"])
user.save()
return self.success("Succeeded")
@ -305,7 +259,6 @@ class ApplyResetPasswordAPI(APIView):
def post(self, request):
data = request.data
captcha = Captcha(request)
config = WebsiteConfig.objects.first()
if not captcha.check(data["captcha"]):
return self.error("Invalid captcha")
try:
@ -320,14 +273,14 @@ class ApplyResetPasswordAPI(APIView):
user.save()
render_data = {
"username": user.username,
"website_name": config.name,
"link": f"{config.base_url}/reset-password/{user.reset_password_token}"
"website_name": SysOptions.website_name,
"link": f"{SysOptions.website_base_url}/reset-password/{user.reset_password_token}"
}
email_html = render_to_string("reset_password_email.html", render_data)
send_email_async.delay(config.name,
send_email_async.delay(SysOptions.website_name,
user.email,
user.username,
config.name + " 登录信息找回邮件",
f"{SysOptions.website_name} 登录信息找回邮件",
email_html)
return self.success("Succeeded")
@ -342,9 +295,9 @@ class ResetPasswordAPI(APIView):
try:
user = User.objects.get(reset_password_token=data["token"])
except User.DoesNotExist:
return self.error("Token dose not exist")
if int((user.reset_password_token_expire_time - now()).total_seconds()) < 0:
return self.error("Token have expired")
return self.error("Token does not exist")
if user.reset_password_token_expire_time < now():
return self.error("Token has expired")
user.reset_password_token = None
user.two_factor_auth = False
user.set_password(data["password"])
@ -356,13 +309,13 @@ class SessionManagementAPI(APIView):
@login_required
def get(self, request):
engine = import_module(settings.SESSION_ENGINE)
SessionStore = engine.SessionStore
session_store = engine.SessionStore
current_session = request.session.session_key
session_keys = request.user.session_keys
result = []
modified = False
for key in session_keys[:]:
session = SessionStore(key)
session = session_store(key)
# session does not exist or is expiry
if not session._session:
session_keys.remove(key)
@ -398,12 +351,12 @@ class SessionManagementAPI(APIView):
class UserRankAPI(APIView):
def get(self, request):
rule_type = request.GET.get("rule")
if rule_type not in ["acm", "oi"]:
rule_type = "acm"
if rule_type not in ContestRuleType.choices():
rule_type = ContestRuleType.ACM
profiles = UserProfile.objects.select_related("user")\
.filter(submission_number__gt=0)\
.exclude(user__is_disabled=True)
if rule_type == "acm":
if rule_type == ContestRuleType.ACM:
profiles = profiles.order_by("-accepted_number", "submission_number")
else:
profiles = profiles.order_by("-total_score")

View File

@ -5,7 +5,7 @@ from utils.models import RichTextField
class Announcement(models.Model):
title = models.CharField(max_length=50)
title = models.CharField(max_length=64)
# HTML
content = RichTextField()
create_time = models.DateTimeField(auto_now_add=True)

View File

@ -5,8 +5,8 @@ from .models import Announcement
class CreateAnnouncementSerializer(serializers.Serializer):
title = serializers.CharField(max_length=50)
content = serializers.CharField(max_length=10000)
title = serializers.CharField(max_length=64)
content = serializers.CharField(max_length=1024 * 1024 * 8)
visible = serializers.BooleanField()
@ -21,6 +21,6 @@ class AnnouncementSerializer(serializers.ModelSerializer):
class EditAnnouncementSerializer(serializers.Serializer):
id = serializers.IntegerField()
title = serializers.CharField(max_length=50)
content = serializers.CharField(max_length=10000)
title = serializers.CharField(max_length=64)
content = serializers.CharField(max_length=1024 * 1024 * 8)
visible = serializers.BooleanField()

View File

@ -3,16 +3,16 @@ from django.utils import timezone
class JudgeServer(models.Model):
hostname = models.CharField(max_length=64)
hostname = models.CharField(max_length=128)
ip = models.CharField(max_length=32, blank=True, null=True)
judger_version = models.CharField(max_length=24)
judger_version = models.CharField(max_length=32)
cpu_core = models.IntegerField()
memory_usage = models.FloatField()
cpu_usage = models.FloatField()
last_heartbeat = models.DateTimeField()
create_time = models.DateTimeField(auto_now_add=True)
task_number = models.IntegerField(default=0)
service_url = models.CharField(max_length=128, blank=True, null=True)
service_url = models.CharField(max_length=256, blank=True, null=True)
@property
def status(self):

View File

@ -21,9 +21,9 @@ class TestSMTPConfigSerializer(serializers.Serializer):
class CreateEditWebsiteConfigSerializer(serializers.Serializer):
website_base_url = serializers.CharField(max_length=128)
website_name = serializers.CharField(max_length=32)
website_name_shortcut = serializers.CharField(max_length=32)
website_footer = serializers.CharField(max_length=1024)
website_name = serializers.CharField(max_length=64)
website_name_shortcut = serializers.CharField(max_length=64)
website_footer = serializers.CharField(max_length=1024 * 1024)
allow_register = serializers.BooleanField()
submission_list_show_all = serializers.BooleanField()
@ -39,10 +39,10 @@ class JudgeServerSerializer(serializers.ModelSerializer):
class JudgeServerHeartbeatSerializer(serializers.Serializer):
hostname = serializers.CharField(max_length=64)
judger_version = serializers.CharField(max_length=24)
hostname = serializers.CharField(max_length=128)
judger_version = serializers.CharField(max_length=32)
cpu_core = serializers.IntegerField(min_value=1)
memory = serializers.FloatField(min_value=0, max_value=100)
cpu = serializers.FloatField(min_value=0, max_value=100)
action = serializers.ChoiceField(choices=("heartbeat", ))
service_url = serializers.CharField(max_length=128, required=False)
service_url = serializers.CharField(max_length=256, required=False)

View File

@ -2,26 +2,11 @@ from django.db import models
from django.utils.timezone import now
from jsonfield import JSONField
from utils.constants import ContestStatus, ContestRuleType, ContestType
from account.models import User, AdminType
from utils.models import RichTextField
class ContestType(object):
PUBLIC_CONTEST = "Public"
PASSWORD_PROTECTED_CONTEST = "Password Protected"
class ContestStatus(object):
CONTEST_NOT_START = "1"
CONTEST_ENDED = "-1"
CONTEST_UNDERWAY = "0"
class ContestRuleType(object):
ACM = "ACM"
OI = "OI"
class Contest(models.Model):
title = models.CharField(max_length=40)
description = RichTextField()

View File

@ -1,13 +1,11 @@
import pickle
from django.utils.timezone import now
from django.core.cache import cache
from utils.api import APIView, validate_serializer
from utils.cache import default_cache
from utils.constants import CacheKey
from account.decorators import login_required, check_contest_permission
from ..models import ContestAnnouncement, Contest, ContestStatus, ContestRuleType
from ..models import OIContestRank, ACMContestRank
from utils.constants import ContestRuleType, ContestType, ContestStatus
from ..models import ContestAnnouncement, Contest, OIContestRank, ACMContestRank
from ..serializers import ContestAnnouncementSerializer
from ..serializers import ContestSerializer, ContestPasswordVerifySerializer
from ..serializers import OIContestRankSerializer, ACMContestRankSerializer

View File

@ -1,3 +1,26 @@
class Choices:
@classmethod
def choices(cls):
d = cls.__dict__
return [d[item] for item in d.keys() if not item.startswith("__")]
class ContestType:
PUBLIC_CONTEST = "Public"
PASSWORD_PROTECTED_CONTEST = "Password Protected"
class ContestStatus:
CONTEST_NOT_START = "1"
CONTEST_ENDED = "-1"
CONTEST_UNDERWAY = "0"
class ContestRuleType(Choices):
ACM = "ACM"
OI = "OI"
class CacheKey:
waiting_queue = "waiting_queue"
contest_rank_cache = "contest_rank_cache_"

View File

@ -26,11 +26,8 @@ Cannot defense xss in browser which is belowed IE7
浏览器版本IE7+ 或其他浏览器无法防御IE6及以下版本浏览器中的XSS
"""
import re
try:
from html.parser import HTMLParser
except:
from HTMLParser import HTMLParser
import copy
from html.parser import HTMLParser
class XssHtml(HTMLParser):
@ -163,7 +160,7 @@ class XssHtml(HTMLParser):
else:
other = []
if attrs:
for (key, value) in attrs.items():
for key, value in copy.deepcopy(attrs).items():
if key not in self.common_attrs + other:
del attrs[key]
return attrs