# Via virusdefender (2) and sxw (1)
* 'debug' of https://git.coding.net/virusdefender/qduoj:
  add oj text logo
  增加富文本的 xss 过滤
  增加 demo 网址
  修补部分没有判断比赛权限的问题;增加比赛调试模式的页面提示
  增加部分注释,修补部分没有判断权限的问题
  增加判断管理员权限的 decorator
  只有管理员才能查看所有人的提交
This commit is contained in:
virusdefender 2015-09-23 21:30:35 +08:00
commit 2957d7f716
18 changed files with 444 additions and 63 deletions

View File

@ -4,11 +4,12 @@
文档https://www.zybuluo.com/virusdefender/note/171932 文档https://www.zybuluo.com/virusdefender/note/171932
demo: https://qduoj.com
TODO TODO
- 完善文档,目前还差很多 - 完善文档,目前还差很多
- 完善测试 - 完善测试
- 搭建 demo 站点
![oj_previewindex.png][1] ![oj_previewindex.png][1]

View File

@ -4,7 +4,7 @@ from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render from django.shortcuts import render
from utils.shortcuts import error_response, error_page from utils.shortcuts import error_response, error_page
from .models import User from .models import User, SUPER_ADMIN
def login_required(func): def login_required(func):
@ -12,7 +12,10 @@ def login_required(func):
def check(*args, **kwargs): def check(*args, **kwargs):
# 在class based views 里面args 有两个元素一个是self, 第二个才是request # 在class based views 里面args 有两个元素一个是self, 第二个才是request
# 在function based views 里面args 只有request 一个参数 # 在function based views 里面args 只有request 一个参数
if len(args) == 2:
request = args[-1] request = args[-1]
else:
request = args[0]
if request.user.is_authenticated(): if request.user.is_authenticated():
return func(*args, **kwargs) return func(*args, **kwargs)
if request.is_ajax(): if request.is_ajax():
@ -25,7 +28,10 @@ def login_required(func):
def admin_required(func): def admin_required(func):
@wraps(func) @wraps(func)
def check(*args, **kwargs): def check(*args, **kwargs):
if len(args) == 2:
request = args[-1] request = args[-1]
else:
request = args[0]
if request.user.is_authenticated() and request.user.admin_type: if request.user.is_authenticated() and request.user.admin_type:
return func(*args, **kwargs) return func(*args, **kwargs)
if request.is_ajax(): if request.is_ajax():
@ -33,3 +39,20 @@ def admin_required(func):
else: else:
return error_page(request, u"需要管理员权限,如果没有登录,请先登录") return error_page(request, u"需要管理员权限,如果没有登录,请先登录")
return check return check
def super_admin_required(func):
@wraps(func)
def check(*args, **kwargs):
if len(args) == 2:
request = args[-1]
else:
request = args[0]
if request.user.is_authenticated() and request.user.admin_type == SUPER_ADMIN:
return func(*args, **kwargs)
if request.is_ajax():
return error_response(u"需要超级管理员权限")
else:
return error_page(request, u"需要超级管理员权限")
return check

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import utils.models
class Migration(migrations.Migration):
dependencies = [
('announcement', '0002_auto_20150818_1445'),
]
operations = [
migrations.AlterField(
model_name='announcement',
name='content',
field=utils.models.RichTextField(),
),
]

View File

@ -3,13 +3,14 @@ from django.db import models
from account.models import User from account.models import User
from group.models import Group from group.models import Group
from utils.models import RichTextField
class Announcement(models.Model): class Announcement(models.Model):
# 标题 # 标题
title = models.CharField(max_length=50) title = models.CharField(max_length=50)
# 公告的内容 HTML 格式 # 公告的内容 HTML 格式
content = models.TextField() content = RichTextField()
# 创建时间 # 创建时间
create_time = models.DateTimeField(auto_now_add=True) create_time = models.DateTimeField(auto_now_add=True)
# 这个公告是谁创建的 # 这个公告是谁创建的

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import utils.models
class Migration(migrations.Migration):
dependencies = [
('contest', '0009_contestsubmission_first_achieved'),
]
operations = [
migrations.AlterField(
model_name='contest',
name='description',
field=utils.models.RichTextField(),
),
migrations.AlterField(
model_name='contestproblem',
name='description',
field=utils.models.RichTextField(),
),
]

View File

@ -5,6 +5,8 @@ from django.utils.timezone import now
from account.models import User from account.models import User
from problem.models import AbstractProblem from problem.models import AbstractProblem
from group.models import Group from group.models import Group
from utils.models import RichTextField
GROUP_CONTEST = 0 GROUP_CONTEST = 0
PUBLIC_CONTEST = 1 PUBLIC_CONTEST = 1
@ -17,7 +19,7 @@ CONTEST_UNDERWAY = 0
class Contest(models.Model): class Contest(models.Model):
title = models.CharField(max_length=40, unique=True) title = models.CharField(max_length=40, unique=True)
description = models.TextField() description = RichTextField()
# 比赛模式0 即为是acm模式1 即为是按照总的 ac 题目数量排名模式 # 比赛模式0 即为是acm模式1 即为是按照总的 ac 题目数量排名模式
mode = models.IntegerField() mode = models.IntegerField()
# 是否显示实时排名结果 # 是否显示实时排名结果

View File

@ -16,7 +16,7 @@ from utils.shortcuts import (serializer_invalid_response, error_response,
from account.models import SUPER_ADMIN, User from account.models import SUPER_ADMIN, User
from account.decorators import login_required from account.decorators import login_required
from group.models import Group from group.models import Group
from .models import Contest, ContestProblem, ContestSubmission from .models import Contest, ContestProblem, ContestSubmission, CONTEST_ENDED, CONTEST_NOT_START, CONTEST_UNDERWAY
from .models import GROUP_CONTEST, PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST from .models import GROUP_CONTEST, PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST
from .decorators import check_user_contest_permission from .decorators import check_user_contest_permission
from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer, from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer,
@ -45,10 +45,10 @@ class ContestAdminAPIView(APIView):
if data["contest_type"] in [PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST]: if data["contest_type"] in [PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST]:
if request.user.admin_type != SUPER_ADMIN: if request.user.admin_type != SUPER_ADMIN:
return error_response(u"只有超级管理员才可创建公开赛") return error_response(u"只有超级管理员才可创建公开赛")
if data["contest_type"] == PASSWORD_PROTECTED_CONTEST: if data["contest_type"] == PASSWORD_PROTECTED_CONTEST:
if not data["password"]: if not data["password"]:
return error_response(u"此比赛为有密码的公开赛,密码不可为空") return error_response(u"此比赛为有密码的公开赛,密码不可为空")
# 没有密码的公开赛 没有密码的小组赛 # 没有密码的公开赛 没有密码的小组赛
elif data["contest_type"] == GROUP_CONTEST: elif data["contest_type"] == GROUP_CONTEST:
if request.user.admin_type == SUPER_ADMIN: if request.user.admin_type == SUPER_ADMIN:
@ -58,7 +58,7 @@ class ContestAdminAPIView(APIView):
if not groups.count(): if not groups.count():
return error_response(u"请至少选择一个小组") return error_response(u"请至少选择一个小组")
if data["start_time"] >= data["end_time"]: if data["start_time"] >= data["end_time"]:
return error_response(u"比赛的开始时间不能晚于或等于比赛结束的时间") return error_response(u"比赛的开始时间必须早于比赛结束的时间")
try: try:
contest = Contest.objects.create(title=data["title"], description=data["description"], contest = Contest.objects.create(title=data["title"], description=data["description"],
mode=data["mode"], contest_type=data["contest_type"], mode=data["mode"], contest_type=data["contest_type"],
@ -86,7 +86,10 @@ class ContestAdminAPIView(APIView):
data = serializer.data data = serializer.data
groups = [] groups = []
try: try:
# 超级管理员可以编辑所有的
contest = Contest.objects.get(id=data["id"]) contest = Contest.objects.get(id=data["id"])
if request.user.admin_type != SUPER_ADMIN:
contest = contest.get(created_by=request.user)
except Contest.DoesNotExist: except Contest.DoesNotExist:
return error_response(u"该比赛不存在!") return error_response(u"该比赛不存在!")
try: try:
@ -109,9 +112,8 @@ class ContestAdminAPIView(APIView):
if not groups.count(): if not groups.count():
return error_response(u"请至少选择一个小组") return error_response(u"请至少选择一个小组")
if data["start_time"] >= data["end_time"]: if data["start_time"] >= data["end_time"]:
return error_response(u"比赛的开始时间不能晚于或等于比赛结束的时间") return error_response(u"比赛的开始时间必须早于比赛结束的时间")
if request.user.admin_type != SUPER_ADMIN and request.user != contest.created_by:
return error_response(u"你无权修改该比赛!")
contest.title = data["title"] contest.title = data["title"]
contest.description = data["description"] contest.description = data["description"]
contest.mode = data["mode"] contest.mode = data["mode"]
@ -163,6 +165,8 @@ class ContestProblemAdminAPIView(APIView):
data = serializer.data data = serializer.data
try: try:
contest = Contest.objects.get(id=data["contest_id"]) contest = Contest.objects.get(id=data["contest_id"])
if request.user.admin_type != SUPER_ADMIN:
contest = contest.get(created_by=request.user)
except Contest.DoesNotExist: except Contest.DoesNotExist:
return error_response(u"比赛不存在") return error_response(u"比赛不存在")
contest_problem = ContestProblem.objects.create(title=data["title"], contest_problem = ContestProblem.objects.create(title=data["title"],
@ -199,7 +203,7 @@ class ContestProblemAdminAPIView(APIView):
return error_response(u"该比赛题目不存在!") return error_response(u"该比赛题目不存在!")
contest = Contest.objects.get(id=contest_problem.contest_id) contest = Contest.objects.get(id=contest_problem.contest_id)
if request.user.admin_type != SUPER_ADMIN and contest.created_by != request.user: if request.user.admin_type != SUPER_ADMIN and contest.created_by != request.user:
return error_response(u"你无权修改该题目!") return error_response(u"比赛不存在")
contest_problem.title = data["title"] contest_problem.title = data["title"]
contest_problem.description = data["description"] contest_problem.description = data["description"]
contest_problem.input_description = data["input_description"] contest_problem.input_description = data["input_description"]
@ -227,29 +231,27 @@ class ContestProblemAdminAPIView(APIView):
if contest_problem_id: if contest_problem_id:
try: try:
contest_problem = ContestProblem.objects.get(id=contest_problem_id) contest_problem = ContestProblem.objects.get(id=contest_problem_id)
if request.user.admin_type != SUPER_ADMIN:
contest_problem = contest_problem.get(created_by=request.user)
return success_response(ContestProblemSerializer(contest_problem).data) return success_response(ContestProblemSerializer(contest_problem).data)
except ContestProblem.DoesNotExist: except ContestProblem.DoesNotExist:
return error_response(u"比赛题目不存在") return error_response(u"比赛题目不存在")
if request.user.admin_type == SUPER_ADMIN:
contest_problem = ContestProblem.objects.all().order_by("sort_index") contest_problems = ContestProblem.objects.all().order_by("sort_index")
else: if request.user.admin_type != SUPER_ADMIN:
contest_problem = ContestProblem.objects.filter(created_by=request.user).order_by("sort_index") contest_problems = contest_problems.filter(created_by=request.user).order_by("sort_index")
visible = request.GET.get("visible", None) visible = request.GET.get("visible", None)
if visible: if visible:
contest_problem = contest_problem.filter(visible=(visible == "true")) contest_problems = contest_problems.filter(visible=(visible == "true"))
keyword = request.GET.get("keyword", None) keyword = request.GET.get("keyword", None)
if keyword: if keyword:
contest_problem = contest_problem.filter(Q(title__contains=keyword) | contest_problems = contest_problems.filter(Q(title__contains=keyword) |
Q(description__contains=keyword)) Q(description__contains=keyword))
contest_id = request.GET.get("contest_id", None) contest_id = request.GET.get("contest_id", None)
if contest_id: if contest_id:
try: contest_problem = contest_problems.filter(contest__id=contest_id).order_by("sort_index")
contest = Contest.objects.get(id=contest_id)
except Contest.DoesNotExist:
return error_response(u"该比赛不存在!")
contest_problem = contest_problem.filter(contest=contest).order_by("sort_index")
return paginate(request, contest_problem, ContestProblemSerializer) return paginate(request, contest_problems, ContestProblemSerializer)
class ContestPasswordVerifyAPIView(APIView): class ContestPasswordVerifyAPIView(APIView):
@ -306,12 +308,23 @@ def contest_problem_page(request, contest_id, contest_problem_id):
pass pass
# 已经结束 # 已经结束
if contest.status == -1: if contest.status == CONTEST_ENDED:
show_warning = True show_warning = True
warning = u"比赛已经结束" warning = u"比赛已经结束"
elif contest.status == CONTEST_NOT_START:
show_warning = True
warning = u"比赛没有开始,您是管理员,可以提交和测试题目,但是目前的提交不会计入排名。"
show_submit_code_area = False
if contest.status == CONTEST_UNDERWAY:
show_submit_code_area = True
if request.user.admin_type == SUPER_ADMIN or request.user == contest.created_by:
show_submit_code_area = True
return render(request, "oj/contest/contest_problem.html", {"contest_problem": contest_problem, "contest": contest, return render(request, "oj/contest/contest_problem.html", {"contest_problem": contest_problem, "contest": contest,
"samples": json.loads(contest_problem.samples), "samples": json.loads(contest_problem.samples),
"show_warning": show_warning, "warning": warning}) "show_warning": show_warning, "warning": warning,
"show_submit_code_area": show_submit_code_area})
@check_user_contest_permission @check_user_contest_permission
@ -319,10 +332,7 @@ def contest_problems_list_page(request, contest_id):
""" """
比赛所有题目的列表页 比赛所有题目的列表页
""" """
try:
contest = Contest.objects.get(id=contest_id) contest = Contest.objects.get(id=contest_id)
except Contest.DoesNotExist:
return error_page(request, u"比赛不存在")
contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index") contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index")
submissions = ContestSubmission.objects.filter(user=request.user, contest=contest) submissions = ContestSubmission.objects.filter(user=request.user, contest=contest)
state = {} state = {}

View File

@ -0,0 +1,9 @@
"""
___ _ _ __ _ _ _
/___\ _ __ | |(_) _ __ ___ \ \ _ _ __| | __ _ ___ | |__ _ _ __ _ __| | _ _
// //| '_ \ | || || '_ \ / _ \ \ \| | | | / _` | / _` | / _ \ | '_ \ | | | | / _` | / _` || | | |
/ \_// | | | || || || | | || __/ /\_/ /| |_| || (_| || (_| || __/ | |_) || |_| | | (_| || (_| || |_| |
\___/ |_| |_||_||_||_| |_| \___| \___/ \__,_| \__,_| \__, | \___| |_.__/ \__, | \__, | \__,_| \__,_|
|___/ |___/ |_|
https://github.com/QingdaoU/OnlineJudge
"""

28
problem/decorators.py Normal file
View File

@ -0,0 +1,28 @@
# coding=utf-8
from functools import wraps
from account.models import SUPER_ADMIN
from utils.shortcuts import error_response
from .models import Problem
def check_user_problem_permission(func):
@wraps(func)
def check(*args, **kwargs):
# 在class based views 里面args 有两个元素一个是self, 第二个才是request
# 在function based views 里面args 只有request 一个参数
if len(args) == 2:
request = args[-1]
else:
request = args[0]
# 这是在后台使用的url middleware 已经确保用户是登录状态的了
if request.user.admin_type == SUPER_ADMIN:
return func(*args, **kwargs)
try:
Problem.objects.get(id=request.data.get("problem_id", -1), created_by=request.user)
except Problem.DoesNotExist:
return error_response(u"问题不存在")
return func(*args, **kwargs)
return check

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import utils.models
class Migration(migrations.Migration):
dependencies = [
('problem', '0007_remove_problem_last_update_time'),
]
operations = [
migrations.AlterField(
model_name='problem',
name='description',
field=utils.models.RichTextField(),
),
]

View File

@ -2,6 +2,7 @@
from django.db import models from django.db import models
from account.models import User from account.models import User
from utils.models import RichTextField
class ProblemTag(models.Model): class ProblemTag(models.Model):
@ -15,7 +16,7 @@ class AbstractProblem(models.Model):
# 标题 # 标题
title = models.CharField(max_length=50) title = models.CharField(max_length=50)
# 问题描述 HTML 格式 # 问题描述 HTML 格式
description = models.TextField() description = RichTextField()
# 输入描述 # 输入描述
input_description = models.CharField(max_length=10000) input_description = models.CharField(max_length=10000)
# 输出描述 # 输出描述

View File

@ -4,6 +4,7 @@ import re
import os import os
import hashlib import hashlib
import json import json
import logging
from django.shortcuts import render from django.shortcuts import render
from django.db.models import Q, Count from django.db.models import Q, Count
@ -13,19 +14,23 @@ from rest_framework.views import APIView
from django.conf import settings from django.conf import settings
from account.models import SUPER_ADMIN
from announcement.models import Announcement from account.decorators import super_admin_required
from utils.shortcuts import (serializer_invalid_response, error_response, from utils.shortcuts import (serializer_invalid_response, error_response,
success_response, paginate, rand_str, error_page) success_response, paginate, rand_str, error_page)
from .serizalizers import (CreateProblemSerializer, EditProblemSerializer, ProblemSerializer, from .serizalizers import (CreateProblemSerializer, EditProblemSerializer, ProblemSerializer,
ProblemTagSerializer, CreateProblemTagSerializer) ProblemTagSerializer, CreateProblemTagSerializer)
from .models import Problem, ProblemTag from .models import Problem, ProblemTag
import logging from .decorators import check_user_problem_permission
logger = logging.getLogger("app_info") logger = logging.getLogger("app_info")
def problem_page(request, problem_id): def problem_page(request, problem_id):
"""
前台题目详情页
"""
try: try:
problem = Problem.objects.get(id=problem_id, visible=True) problem = Problem.objects.get(id=problem_id, visible=True)
except Problem.DoesNotExist: except Problem.DoesNotExist:
@ -34,11 +39,15 @@ def problem_page(request, problem_id):
class ProblemTagAdminAPIView(APIView): class ProblemTagAdminAPIView(APIView):
"""
获取所有标签的列表
"""
def get(self, request): def get(self, request):
return success_response(ProblemTagSerializer(ProblemTag.objects.all(), many=True).data) return success_response(ProblemTagSerializer(ProblemTag.objects.all(), many=True).data)
class ProblemAdminAPIView(APIView): class ProblemAdminAPIView(APIView):
@super_admin_required
def post(self, request): def post(self, request):
""" """
题目发布json api接口 题目发布json api接口
@ -72,6 +81,7 @@ class ProblemAdminAPIView(APIView):
else: else:
return serializer_invalid_response(serializer) return serializer_invalid_response(serializer)
@check_user_problem_permission
def put(self, request): def put(self, request):
""" """
题目编辑json api接口 题目编辑json api接口
@ -82,11 +92,7 @@ class ProblemAdminAPIView(APIView):
serializer = EditProblemSerializer(data=request.data) serializer = EditProblemSerializer(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
data = serializer.data data = serializer.data
try:
problem = Problem.objects.get(id=data["id"]) problem = Problem.objects.get(id=data["id"])
except Problem.DoesNotExist:
return error_response(u"该题目不存在!")
problem.title = data["title"] problem.title = data["title"]
problem.description = data["description"] problem.description = data["description"]
problem.input_description = data["input_description"] problem.input_description = data["input_description"]
@ -123,23 +129,36 @@ class ProblemAdminAPIView(APIView):
problem_id = request.GET.get("problem_id", None) problem_id = request.GET.get("problem_id", None)
if problem_id: if problem_id:
try: try:
# 普通管理员只能获取自己创建的题目
# 超级管理员可以获取全部的题目
problem = Problem.objects.get(id=problem_id) problem = Problem.objects.get(id=problem_id)
if request.user.admin_type != SUPER_ADMIN:
problem = problem.get(created_by=request.user)
return success_response(ProblemSerializer(problem).data) return success_response(ProblemSerializer(problem).data)
except Problem.DoesNotExist: except Problem.DoesNotExist:
return error_response(u"题目不存在") return error_response(u"题目不存在")
problem = Problem.objects.all().order_by("-create_time")
# 获取问题列表
problems = Problem.objects.all().order_by("-create_time")
if request.user.admin_type != SUPER_ADMIN:
problems = problems.filter(created_by=request.user)
visible = request.GET.get("visible", None) visible = request.GET.get("visible", None)
if visible: if visible:
problem = problem.filter(visible=(visible == "true")) problems = problems.filter(visible=(visible == "true"))
keyword = request.GET.get("keyword", None) keyword = request.GET.get("keyword", None)
if keyword: if keyword:
problem = problem.filter(Q(title__contains=keyword) | problems = problems.filter(Q(title__contains=keyword) |
Q(description__contains=keyword)) Q(description__contains=keyword))
return paginate(request, problem, ProblemSerializer) return paginate(request, problems, ProblemSerializer)
class TestCaseUploadAPIView(APIView): class TestCaseUploadAPIView(APIView):
"""
上传题目的测试用例
"""
def _is_legal_test_case_file_name(self, file_name): def _is_legal_test_case_file_name(self, file_name):
# 正整数开头的 .in 或者.out 结尾的 # 正整数开头的 .in 或者.out 结尾的
regex = r"^[1-9]\d*\.(in|out)$" regex = r"^[1-9]\d*\.(in|out)$"
@ -237,6 +256,9 @@ class TestCaseUploadAPIView(APIView):
def problem_list_page(request, page=1): def problem_list_page(request, page=1):
"""
前台的问题列表
"""
# 正常情况 # 正常情况
problems = Problem.objects.filter(visible=True) problems = Problem.objects.filter(visible=True)

View File

@ -218,7 +218,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
description: problem.description, description: problem.description,
time_limit: problem.time_limit, time_limit: problem.time_limit,
memory_limit: problem.memory_limit, memory_limit: problem.memory_limit,
samples: problem.samples, samples: [],
test_case_id: problem.test_case_id, test_case_id: problem.test_case_id,
hint: problem.hint, hint: problem.hint,
source: problem.contest.title, source: problem.contest.title,
@ -228,6 +228,9 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
output_description: problem.output_description, output_description: problem.output_description,
difficulty: 0 difficulty: 0
}; };
for (var i = 0; i < problem.samples.length; i++) {
ajaxData.samples.push({input: problem.samples[i].input, output: problem.samples[i].output})
}
$.ajax({ $.ajax({
beforeSend: csrfTokenHeader, beforeSend: csrfTokenHeader,
url: "/api/admin/problem/", url: "/api/admin/problem/",

View File

@ -77,20 +77,20 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
}); });
if (avalon.vmodels.editProblem) { if (avalon.vmodels.editProblem) {
var vm = avalon.vmodels.editProblem; var vm = avalon.vmodels.editProblem;
title: "", vm.title= "",
description= ""; vm.description= "";
timeLimit= -1; vm.timeLimit= -1;
memoryLimit= -1; vm.memoryLimit= -1;
samples= []; vm.samples= [];
hint= ""; vm.hint= "";
visible= true; vm.visible= true;
difficulty= 0; vm.difficulty= 0;
inputDescription= ""; vm.inputDescription= "";
outputDescription= ""; vm.outputDescription= "";
testCaseIdd= ""; vm.testCaseIdd= "";
uploadSuccess= false; vm.uploadSuccess= false;
source= ""; vm.source= "";
testCaseList= []; vm.testCaseList= [];
} }
else else
var vm = avalon.define({ var vm = avalon.define({

View File

@ -162,7 +162,7 @@ def my_submission_list_page(request, page=1):
我的所有提交的列表页 我的所有提交的列表页
""" """
# 显示所有人的提交 这是管理员的调试功能 # 显示所有人的提交 这是管理员的调试功能
show_all = request.GET.get("show_all", False) == "true" show_all = request.GET.get("show_all", False) == "true" and request.user.admin_type == SUPER_ADMIN
if show_all: if show_all:
submissions = Submission.objects.filter(contest_id__isnull=True) submissions = Submission.objects.filter(contest_id__isnull=True)
else: else:

View File

@ -60,7 +60,7 @@
<div class="problem-detail">{{ contest_problem.hint|safe }}</div> <div class="problem-detail">{{ contest_problem.hint|safe }}</div>
</div> </div>
{% endif %} {% endif %}
{% ifequal contest.status 0 %} {% if show_submit_code_area %}
<div> <div>
<label>选择语言</label> <label>选择语言</label>
<div> <div>
@ -86,7 +86,7 @@
</button> </button>
<img src="/static/img/loading.gif" id="loading-gif"> <img src="/static/img/loading.gif" id="loading-gif">
</div> </div>
{% endifequal %} {% endif %}
{% if show_warning %} {% if show_warning %}
<div class="alert alert-success" role="alert">{{ warning }}</div> <div class="alert alert-success" role="alert">{{ warning }}</div>
{% endif %} {% endif %}

14
utils/models.py Normal file
View File

@ -0,0 +1,14 @@
# coding=utf-8
from django.db import models
from utils.xss_filter import XssHtml
class RichTextField(models.TextField):
__metaclass__ = models.SubfieldBase
def get_prep_value(self, value):
parser = XssHtml()
parser.feed(value)
parser.close()
return parser.getHtml()

202
utils/xss_filter.py Normal file
View File

@ -0,0 +1,202 @@
# -*- coding: utf-8 -*-
"""
Python 富文本XSS过滤类
@package XssHtml
@version 0.1
@link http://phith0n.github.io/python-xss-filter
@since 20150407
@copyright (c) Phithon All Rights Reserved
Based on native Python module HTMLParser purifier of HTML, To Clear all javascript in html
You can use it in all python web framework
Written by Phithon <root@leavesongs.com> in 2015 and placed in the public domain.
phithon <root@leavesongs.com> 编写于20150407
From: XDSEC <www.xdsec.org> & 离别歌 <www.leavesongs.com>
GitHub Pages: https://github.com/phith0n/python-xss-filter
Usage:
parser = XssHtml()
parser.feed('<html code>')
parser.close()
html = parser.getHtml()
print html
Requirements
Python 2.6+ or 3.2+
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
class XssHtml(HTMLParser):
allow_tags = ['a', 'img', 'br', 'strong', 'b', 'code', 'pre',
'p', 'div', 'em', 'span', 'h1', 'h2', 'h3', 'h4',
'h5', 'h6', 'blockquote', 'ul', 'ol', 'tr', 'th', 'td',
'hr', 'li', 'u', 'embed', 's', 'table', 'thead', 'tbody',
'caption', 'small', 'q', 'sup', 'sub']
common_attrs = ["style", "class", "name"]
nonend_tags = ["img", "hr", "br", "embed"]
tags_own_attrs = {
"img": ["src", "width", "height", "alt", "align"],
"a": ["href", "target", "rel", "title"],
"embed": ["src", "width", "height", "type", "allowfullscreen", "loop", "play", "wmode", "menu"],
"table": ["border", "cellpadding", "cellspacing"],
}
def __init__(self, allows=[]):
HTMLParser.__init__(self)
self.allow_tags = allows if allows else self.allow_tags
self.result = []
self.start = []
self.data = []
def getHtml(self):
"""
Get the safe html code
"""
for i in range(0, len(self.result)):
tmp = self.result[i].rstrip('\n')
tmp = tmp.lstrip('\n')
if tmp:
self.data.append(tmp)
return ''.join(self.data)
def handle_startendtag(self, tag, attrs):
self.handle_starttag(tag, attrs)
def handle_starttag(self, tag, attrs):
if tag not in self.allow_tags:
return
end_diagonal = ' /' if tag in self.nonend_tags else ''
if not end_diagonal:
self.start.append(tag)
attdict = {}
for attr in attrs:
attdict[attr[0]] = attr[1]
attdict = self._wash_attr(attdict, tag)
if hasattr(self, "node_%s" % tag):
attdict = getattr(self, "node_%s" % tag)(attdict)
else:
attdict = self.node_default(attdict)
attrs = []
for (key, value) in attdict.items():
attrs.append('%s="%s"' % (key, self._htmlspecialchars(value)))
attrs = (' ' + ' '.join(attrs)) if attrs else ''
self.result.append('<' + tag + attrs + end_diagonal + '>')
def handle_endtag(self, tag):
if self.start and tag == self.start[len(self.start) - 1]:
self.result.append('</' + tag + '>')
self.start.pop()
def handle_data(self, data):
self.result.append(self._htmlspecialchars(data))
def handle_entityref(self, name):
if name.isalpha():
self.result.append("&%s;" % name)
def handle_charref(self, name):
if name.isdigit():
self.result.append("&#%s;" % name)
def node_default(self, attrs):
attrs = self._common_attr(attrs)
return attrs
def node_a(self, attrs):
attrs = self._common_attr(attrs)
attrs = self._get_link(attrs, "href")
attrs = self._set_attr_default(attrs, "target", "_blank")
attrs = self._limit_attr(attrs, {
"target": ["_blank", "_self"]
})
return attrs
def node_embed(self, attrs):
attrs = self._common_attr(attrs)
attrs = self._get_link(attrs, "src")
attrs = self._limit_attr(attrs, {
"type": ["application/x-shockwave-flash"],
"wmode": ["transparent", "window", "opaque"],
"play": ["true", "false"],
"loop": ["true", "false"],
"menu": ["true", "false"],
"allowfullscreen": ["true", "false"]
})
attrs["allowscriptaccess"] = "never"
attrs["allownetworking"] = "none"
return attrs
def _true_url(self, url):
prog = re.compile(r"^(http|https|ftp)://.+", re.I | re.S)
if prog.match(url):
return url
else:
return "http://%s" % url
def _true_style(self, style):
if style:
style = re.sub(r"(\\|&#|/\*|\*/)", "_", style)
style = re.sub(r"e.*x.*p.*r.*e.*s.*s.*i.*o.*n", "_", style)
return style
def _get_style(self, attrs):
if "style" in attrs:
attrs["style"] = self._true_style(attrs.get("style"))
return attrs
def _get_link(self, attrs, name):
if name in attrs:
attrs[name] = self._true_url(attrs[name])
return attrs
def _wash_attr(self, attrs, tag):
if tag in self.tags_own_attrs:
other = self.tags_own_attrs.get(tag)
else:
other = []
if attrs:
for (key, value) in attrs.items():
if key not in self.common_attrs + other:
del attrs[key]
return attrs
def _common_attr(self, attrs):
attrs = self._get_style(attrs)
return attrs
def _set_attr_default(self, attrs, name, default=''):
if name not in attrs:
attrs[name] = default
return attrs
def _limit_attr(self, attrs, limit={}):
for (key, value) in limit.items():
if key in attrs and attrs[key] not in value:
del attrs[key]
return attrs
def _htmlspecialchars(self, html):
return html.replace("<", "&lt;") \
.replace(">", "&gt;") \
.replace('"', "&quot;") \
.replace("'", "&#039;")
if "__main__" == __name__:
parser = XssHtml()
parser.feed("""<p><img src=1 onerror=alert(/xss/)></p><div class="left">
<a href='javascript:prompt(1)'><br />hehe</a></div>
<p id="test" onmouseover="alert(1)">&gt;M<svg>
<a href="https://www.baidu.com" target="self">MM</a></p>
<embed src='javascript:alert(/hehe/)' allowscriptaccess=always />""")
parser.close()
print(parser.getHtml())