mirror of https://github.com/EasyCTF/librectf
python: Run black over the source
This commit is contained in:
parent
4cf0c40fb5
commit
9f3346f158
|
@ -6,8 +6,10 @@
|
|||
pythonPackages = pkgs.python310Packages;
|
||||
in {
|
||||
devShell = pkgs.mkShell {
|
||||
buildInputs = (with pkgs; [ libmysqlclient ])
|
||||
++ (with pythonPackages; [ poetry ]);
|
||||
buildInputs = with pkgs; [
|
||||
libmysqlclient
|
||||
(python310.withPackages (p: with p; [ black poetry ]))
|
||||
];
|
||||
|
||||
SECRET_KEY = "ad88fec19a7641e5de308e45dd4fa1c5";
|
||||
};
|
||||
|
|
31
judge/api.py
31
judge/api.py
|
@ -23,15 +23,34 @@ class API(object):
|
|||
print("text:", repr(r.text))
|
||||
if not r.text:
|
||||
return None
|
||||
required_fields = ["id", "language", "source", "pid", "test_cases", "time_limit", "memory_limit", "generator_code", "grader_code", "source_verifier_code"]
|
||||
required_fields = [
|
||||
"id",
|
||||
"language",
|
||||
"source",
|
||||
"pid",
|
||||
"test_cases",
|
||||
"time_limit",
|
||||
"memory_limit",
|
||||
"generator_code",
|
||||
"grader_code",
|
||||
"source_verifier_code",
|
||||
]
|
||||
# create job object
|
||||
obj = r.json()
|
||||
if not all(field in obj for field in required_fields):
|
||||
return None
|
||||
problem = Problem(obj["pid"], obj["test_cases"], obj["time_limit"], obj["memory_limit"],
|
||||
obj["generator_code"], Python3,
|
||||
obj["grader_code"], Python3,
|
||||
obj["source_verifier_code"], Python3)
|
||||
problem = Problem(
|
||||
obj["pid"],
|
||||
obj["test_cases"],
|
||||
obj["time_limit"],
|
||||
obj["memory_limit"],
|
||||
obj["generator_code"],
|
||||
Python3,
|
||||
obj["grader_code"],
|
||||
Python3,
|
||||
obj["source_verifier_code"],
|
||||
Python3,
|
||||
)
|
||||
language = languages.get(obj["language"])
|
||||
if not language:
|
||||
return None # TODO: should definitely not do this
|
||||
|
@ -44,7 +63,7 @@ class API(object):
|
|||
verdict=result.verdict.value if verdict else "JE",
|
||||
last_ran_case=result.last_ran_case,
|
||||
execution_time=result.execution_time,
|
||||
execution_memory=result.execution_memory
|
||||
execution_memory=result.execution_memory,
|
||||
)
|
||||
r = self.api_call(self.base_url + "/jobs", method="POST", data=data)
|
||||
return r.status_code // 100 == 2
|
||||
|
|
|
@ -4,7 +4,7 @@ from typing import Dict
|
|||
|
||||
|
||||
APP_ROOT = pathlib.Path(os.path.dirname(os.path.abspath(__file__)))
|
||||
CONFINE_PATH = APP_ROOT / 'confine'
|
||||
CONFINE_PATH = APP_ROOT / "confine"
|
||||
|
||||
COMPILATION_TIME_LIMIT = 10
|
||||
GRADER_TIME_LIMIT = 10
|
||||
|
|
|
@ -14,23 +14,29 @@ from models import ExecutionResult, Job, JobVerdict, Problem
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO)
|
||||
logging.info('Starting up')
|
||||
logging.info("Starting up")
|
||||
|
||||
verdict_map = {
|
||||
'InternalError': JobVerdict.judge_error,
|
||||
'RuntimeError': JobVerdict.runtime_error,
|
||||
'TimeLimitExceeded': JobVerdict.time_limit_exceeded,
|
||||
'MemoryLimitExceeded': JobVerdict.memory_limit_exceeded,
|
||||
'IllegalSyscall': JobVerdict.illegal_syscall,
|
||||
'IllegalOpen': JobVerdict.illegal_syscall,
|
||||
'IllegalWrite': JobVerdict.illegal_syscall,
|
||||
"InternalError": JobVerdict.judge_error,
|
||||
"RuntimeError": JobVerdict.runtime_error,
|
||||
"TimeLimitExceeded": JobVerdict.time_limit_exceeded,
|
||||
"MemoryLimitExceeded": JobVerdict.memory_limit_exceeded,
|
||||
"IllegalSyscall": JobVerdict.illegal_syscall,
|
||||
"IllegalOpen": JobVerdict.illegal_syscall,
|
||||
"IllegalWrite": JobVerdict.illegal_syscall,
|
||||
}
|
||||
|
||||
|
||||
class ExecutionReport:
|
||||
def __init__(self, execution_ok: bool, execution_error_code: JobVerdict, exitcode: int, realtime: float,
|
||||
cputime: float,
|
||||
memory: int):
|
||||
def __init__(
|
||||
self,
|
||||
execution_ok: bool,
|
||||
execution_error_code: JobVerdict,
|
||||
exitcode: int,
|
||||
realtime: float,
|
||||
cputime: float,
|
||||
memory: int,
|
||||
):
|
||||
self.execution_ok = execution_ok
|
||||
self.execution_error_code = execution_error_code
|
||||
self.exitcode = exitcode
|
||||
|
@ -54,21 +60,32 @@ class ExecutionReport:
|
|||
try:
|
||||
obj = json.loads(json_string)
|
||||
return cls(
|
||||
execution_ok=obj['execution_ok'],
|
||||
execution_error_code=verdict_map[obj['execution_error_code']['code']] if obj['execution_error_code'] else None,
|
||||
exitcode=obj['exitcode'],
|
||||
realtime=obj['realtime'],
|
||||
cputime=obj['cputime'],
|
||||
memory=obj['memory'],
|
||||
execution_ok=obj["execution_ok"],
|
||||
execution_error_code=verdict_map[obj["execution_error_code"]["code"]]
|
||||
if obj["execution_error_code"]
|
||||
else None,
|
||||
exitcode=obj["exitcode"],
|
||||
realtime=obj["realtime"],
|
||||
cputime=obj["cputime"],
|
||||
memory=obj["memory"],
|
||||
)
|
||||
except (json.JSONDecodeError, KeyError):
|
||||
logger.error('Failed to load execution report from json!')
|
||||
logger.error("Failed to load execution report from json!")
|
||||
return cls.error_report()
|
||||
|
||||
|
||||
class ExecutionProfile:
|
||||
def __init__(self, confine_path: str, problem: Problem, language: Language, workdir: str,
|
||||
input_file='input', output_file='output', error_file='error', report_file='report'):
|
||||
def __init__(
|
||||
self,
|
||||
confine_path: str,
|
||||
problem: Problem,
|
||||
language: Language,
|
||||
workdir: str,
|
||||
input_file="input",
|
||||
output_file="output",
|
||||
error_file="error",
|
||||
report_file="report",
|
||||
):
|
||||
self.confine_path = confine_path
|
||||
self.language = language
|
||||
self.workdir = workdir
|
||||
|
@ -80,16 +97,22 @@ class ExecutionProfile:
|
|||
self.report_file = os.path.join(workdir, report_file)
|
||||
|
||||
def as_json(self, executable_name: str):
|
||||
return json.dumps({
|
||||
'cputime_limit': self.time_limit,
|
||||
'realtime_limit': self.time_limit * 1000,
|
||||
'allowed_files': self.language.get_allowed_files(self.workdir, executable_name),
|
||||
'allowed_prefixes': self.language.get_allowed_file_prefixes(self.workdir, executable_name),
|
||||
'stdin_file': self.input_file,
|
||||
'stdout_file': self.output_file,
|
||||
'stderr_file': self.error_file,
|
||||
'json_report_file': self.report_file,
|
||||
})
|
||||
return json.dumps(
|
||||
{
|
||||
"cputime_limit": self.time_limit,
|
||||
"realtime_limit": self.time_limit * 1000,
|
||||
"allowed_files": self.language.get_allowed_files(
|
||||
self.workdir, executable_name
|
||||
),
|
||||
"allowed_prefixes": self.language.get_allowed_file_prefixes(
|
||||
self.workdir, executable_name
|
||||
),
|
||||
"stdin_file": self.input_file,
|
||||
"stdout_file": self.output_file,
|
||||
"stderr_file": self.error_file,
|
||||
"json_report_file": self.report_file,
|
||||
}
|
||||
)
|
||||
|
||||
def execute(self, executable_name: str) -> ExecutionReport:
|
||||
return Executor(self).execute(executable_name)
|
||||
|
@ -100,19 +123,25 @@ class Executor:
|
|||
self.profile = profile
|
||||
|
||||
def execute(self, executable_name: str) -> ExecutionReport:
|
||||
command = self.profile.language.get_command(self.profile.workdir, executable_name)
|
||||
command = self.profile.language.get_command(
|
||||
self.profile.workdir, executable_name
|
||||
)
|
||||
|
||||
config_file_path = os.path.join(self.profile.workdir, 'confine.json')
|
||||
with open(config_file_path, 'w') as config_file:
|
||||
config_file_path = os.path.join(self.profile.workdir, "confine.json")
|
||||
with open(config_file_path, "w") as config_file:
|
||||
config_file.write(self.profile.as_json(executable_name))
|
||||
|
||||
try:
|
||||
subprocess.check_call([self.profile.confine_path, '-c', config_file_path, '--', *command],
|
||||
timeout=self.profile.time_limit * 2)
|
||||
subprocess.check_call(
|
||||
[self.profile.confine_path, "-c", config_file_path, "--", *command],
|
||||
timeout=self.profile.time_limit * 2,
|
||||
)
|
||||
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
||||
return ExecutionReport.error_report()
|
||||
|
||||
with open(os.path.join(self.profile.workdir, self.profile.report_file)) as report_file:
|
||||
with open(
|
||||
os.path.join(self.profile.workdir, self.profile.report_file)
|
||||
) as report_file:
|
||||
execution_report = ExecutionReport.from_json(report_file.read())
|
||||
return execution_report
|
||||
|
||||
|
@ -121,7 +150,7 @@ def use_tempdir(func):
|
|||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
before_dir = os.getcwd()
|
||||
tempdir = tempfile.mkdtemp(prefix='jury-')
|
||||
tempdir = tempfile.mkdtemp(prefix="jury-")
|
||||
os.chdir(tempdir)
|
||||
|
||||
result = func(*args, tempdir=tempdir, **kwargs)
|
||||
|
@ -144,24 +173,27 @@ def run_job(job: Job, tempdir: str) -> Iterator[ExecutionResult]:
|
|||
)
|
||||
|
||||
if job.problem.source_verifier_code and job.problem.source_verifier_language:
|
||||
source_verifier_executable = job.problem.source_verifier_language.compile(job.problem.source_verifier_code, tempdir,
|
||||
'source_verifier')
|
||||
source_verifier_executable = job.problem.source_verifier_language.compile(
|
||||
job.problem.source_verifier_code, tempdir, "source_verifier"
|
||||
)
|
||||
if not source_verifier_executable:
|
||||
logger.error('Source verifier failed to compile for problem %d' % job.problem.id)
|
||||
logger.error(
|
||||
"Source verifier failed to compile for problem %d" % job.problem.id
|
||||
)
|
||||
result.verdict = JobVerdict.judge_error
|
||||
yield result
|
||||
return
|
||||
|
||||
with open(os.path.join(tempdir, 'source'), 'wb') as source_file:
|
||||
source_file.write(job.code.encode('utf-8'))
|
||||
with open(os.path.join(tempdir, "source"), "wb") as source_file:
|
||||
source_file.write(job.code.encode("utf-8"))
|
||||
|
||||
execution_profile = ExecutionProfile(
|
||||
confine_path=str(config.CONFINE_PATH),
|
||||
problem=job.problem,
|
||||
language=job.problem.source_verifier_language,
|
||||
workdir=tempdir,
|
||||
input_file='source',
|
||||
output_file='source_verifier_result',
|
||||
input_file="source",
|
||||
output_file="source_verifier_result",
|
||||
)
|
||||
|
||||
execution_result = execution_profile.execute(source_verifier_executable)
|
||||
|
@ -171,28 +203,34 @@ def run_job(job: Job, tempdir: str) -> Iterator[ExecutionResult]:
|
|||
yield result
|
||||
return
|
||||
|
||||
with open(os.path.join(tempdir, 'source_verifier_result')) as source_verifier_result_file:
|
||||
if source_verifier_result_file.read().strip() != 'OK':
|
||||
with open(
|
||||
os.path.join(tempdir, "source_verifier_result")
|
||||
) as source_verifier_result_file:
|
||||
if source_verifier_result_file.read().strip() != "OK":
|
||||
result.verdict = JobVerdict.invalid_source
|
||||
yield result
|
||||
return
|
||||
|
||||
program_executable = job.language.compile(job.code, tempdir, 'program')
|
||||
program_executable = job.language.compile(job.code, tempdir, "program")
|
||||
if not program_executable:
|
||||
result.verdict = JobVerdict.compilation_error
|
||||
yield result
|
||||
return
|
||||
|
||||
generator_executable = job.problem.generator_language.compile(job.problem.generator_code, tempdir, 'generator')
|
||||
generator_executable = job.problem.generator_language.compile(
|
||||
job.problem.generator_code, tempdir, "generator"
|
||||
)
|
||||
if not generator_executable:
|
||||
logger.error('Generator failed to compile for problem %d' % job.problem.id)
|
||||
logger.error("Generator failed to compile for problem %d" % job.problem.id)
|
||||
result.verdict = JobVerdict.judge_error
|
||||
yield result
|
||||
return
|
||||
|
||||
grader_executable = job.problem.grader_language.compile(job.problem.grader_code, tempdir, 'grader')
|
||||
grader_executable = job.problem.grader_language.compile(
|
||||
job.problem.grader_code, tempdir, "grader"
|
||||
)
|
||||
if not grader_executable:
|
||||
logger.error('Grader failed to compile for problem %d' % job.problem.id)
|
||||
logger.error("Grader failed to compile for problem %d" % job.problem.id)
|
||||
result.verdict = JobVerdict.judge_error
|
||||
yield result
|
||||
return
|
||||
|
@ -202,18 +240,29 @@ def run_job(job: Job, tempdir: str) -> Iterator[ExecutionResult]:
|
|||
last_submitted_case = 0
|
||||
for case_number in range(1, job.problem.test_cases + 1):
|
||||
result.last_ran_case = case_number
|
||||
case_result = run_test_case(job, case_number, tempdir, program_executable, generator_executable, grader_executable)
|
||||
case_result = run_test_case(
|
||||
job,
|
||||
case_number,
|
||||
tempdir,
|
||||
program_executable,
|
||||
generator_executable,
|
||||
grader_executable,
|
||||
)
|
||||
if case_result.verdict != JobVerdict.accepted:
|
||||
result = case_result
|
||||
break
|
||||
result.execution_time = max(result.execution_time, case_result.execution_time)
|
||||
result.execution_memory = max(result.execution_memory, case_result.execution_memory)
|
||||
result.execution_memory = max(
|
||||
result.execution_memory, case_result.execution_memory
|
||||
)
|
||||
|
||||
# Yield result if over threshold and is not last case
|
||||
# If verdict calculation takes time, result should be changed to yield even if is last case.
|
||||
if (time.time() - last_submitted_time > config.PARTIAL_JOB_SUBMIT_TIME_THRESHOLD or
|
||||
case_number - last_submitted_case > config.PARTIAL_JOB_SUBMIT_CASES_THRESHOLD) and \
|
||||
case_number != job.problem.test_cases + 1:
|
||||
if (
|
||||
time.time() - last_submitted_time > config.PARTIAL_JOB_SUBMIT_TIME_THRESHOLD
|
||||
or case_number - last_submitted_case
|
||||
> config.PARTIAL_JOB_SUBMIT_CASES_THRESHOLD
|
||||
) and case_number != job.problem.test_cases + 1:
|
||||
yield result
|
||||
|
||||
# We want to let the programs run for `threshold` time before another potential pause
|
||||
|
@ -226,8 +275,14 @@ def run_job(job: Job, tempdir: str) -> Iterator[ExecutionResult]:
|
|||
yield result
|
||||
|
||||
|
||||
def run_test_case(job: Job, case_number: int, workdir: str, program_executable: str, generator_executable: str,
|
||||
grader_executable: str) -> ExecutionResult:
|
||||
def run_test_case(
|
||||
job: Job,
|
||||
case_number: int,
|
||||
workdir: str,
|
||||
program_executable: str,
|
||||
generator_executable: str,
|
||||
grader_executable: str,
|
||||
) -> ExecutionResult:
|
||||
result = ExecutionResult(
|
||||
job=job,
|
||||
verdict=JobVerdict.judge_error,
|
||||
|
@ -236,22 +291,24 @@ def run_test_case(job: Job, case_number: int, workdir: str, program_executable:
|
|||
execution_memory=0,
|
||||
)
|
||||
|
||||
with open(os.path.join(workdir, 'case_number'), 'wb') as case_number_file:
|
||||
case_number_file.write(str(case_number).encode('utf-8'))
|
||||
with open(os.path.join(workdir, "case_number"), "wb") as case_number_file:
|
||||
case_number_file.write(str(case_number).encode("utf-8"))
|
||||
|
||||
generator_execution_profile = ExecutionProfile(
|
||||
confine_path=str(config.CONFINE_PATH),
|
||||
problem=job.problem,
|
||||
language=job.problem.generator_language,
|
||||
workdir=workdir,
|
||||
input_file='case_number',
|
||||
output_file='input',
|
||||
input_file="case_number",
|
||||
output_file="input",
|
||||
)
|
||||
generator_result = generator_execution_profile.execute(generator_executable)
|
||||
|
||||
if not generator_result.execution_ok:
|
||||
logger.error('Generator failed for test case %d of problem %d with error %s' %
|
||||
(case_number, job.problem.id, generator_result.execution_error_code))
|
||||
logger.error(
|
||||
"Generator failed for test case %d of problem %d with error %s"
|
||||
% (case_number, job.problem.id, generator_result.execution_error_code)
|
||||
)
|
||||
return result
|
||||
|
||||
program_execution_profile = ExecutionProfile(
|
||||
|
@ -259,9 +316,9 @@ def run_test_case(job: Job, case_number: int, workdir: str, program_executable:
|
|||
problem=job.problem,
|
||||
language=job.language,
|
||||
workdir=workdir,
|
||||
input_file='input',
|
||||
output_file='program_output',
|
||||
error_file='program_error',
|
||||
input_file="input",
|
||||
output_file="program_output",
|
||||
error_file="program_error",
|
||||
)
|
||||
execution_result = program_execution_profile.execute(program_executable)
|
||||
|
||||
|
@ -276,20 +333,23 @@ def run_test_case(job: Job, case_number: int, workdir: str, program_executable:
|
|||
problem=job.problem,
|
||||
language=job.problem.grader_language,
|
||||
workdir=workdir,
|
||||
input_file='input',
|
||||
output_file='grader_output',
|
||||
error_file='grader_error',
|
||||
input_file="input",
|
||||
output_file="grader_output",
|
||||
error_file="grader_error",
|
||||
)
|
||||
grader_result = grader_execution_profile.execute(grader_executable)
|
||||
|
||||
if not grader_result.execution_ok:
|
||||
logger.error('Grader failed for test case %d of problem %d with error %s' %
|
||||
(case_number, job.problem.id, grader_result.execution_error_code))
|
||||
logger.error(
|
||||
"Grader failed for test case %d of problem %d with error %s"
|
||||
% (case_number, job.problem.id, grader_result.execution_error_code)
|
||||
)
|
||||
result.verdict = JobVerdict.judge_error
|
||||
return result
|
||||
|
||||
with open(os.path.join(workdir, 'program_output'), 'rb') as program_output, \
|
||||
open(os.path.join(workdir, 'grader_output'), 'rb') as grader_output:
|
||||
with open(os.path.join(workdir, "program_output"), "rb") as program_output, open(
|
||||
os.path.join(workdir, "grader_output"), "rb"
|
||||
) as grader_output:
|
||||
if program_output.read().strip() == grader_output.read().strip():
|
||||
final_verdict = JobVerdict.accepted
|
||||
else:
|
||||
|
|
|
@ -13,7 +13,7 @@ from models import Job
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO)
|
||||
logging.info('Starting up')
|
||||
logging.info("Starting up")
|
||||
|
||||
|
||||
api = None
|
||||
|
@ -26,27 +26,34 @@ def loop():
|
|||
job = api.claim()
|
||||
current_job = job
|
||||
if not job:
|
||||
logger.debug('No jobs available.')
|
||||
logger.debug("No jobs available.")
|
||||
return False
|
||||
logger.info('Got job %d.', job.id)
|
||||
logger.info("Got job %d.", job.id)
|
||||
|
||||
tempdir = tempfile.mkdtemp(prefix='jury-')
|
||||
tempdir = tempfile.mkdtemp(prefix="jury-")
|
||||
try:
|
||||
for execution_result in executor.run_job(job, tempdir):
|
||||
# execution_result is partial here
|
||||
|
||||
logger.info('Job %d partially judged; case: %d, time: %.2f, memory: %d',
|
||||
job.id, execution_result.last_ran_case, execution_result.execution_time,
|
||||
execution_result.execution_memory)
|
||||
logger.info(
|
||||
"Job %d partially judged; case: %d, time: %.2f, memory: %d",
|
||||
job.id,
|
||||
execution_result.last_ran_case,
|
||||
execution_result.execution_time,
|
||||
execution_result.execution_memory,
|
||||
)
|
||||
|
||||
if execution_result.verdict:
|
||||
# This should be the last value returned by run_job
|
||||
logger.info('Job %d finished with verdict %s.' % (job.id, execution_result.verdict.value))
|
||||
logger.info(
|
||||
"Job %d finished with verdict %s."
|
||||
% (job.id, execution_result.verdict.value)
|
||||
)
|
||||
|
||||
if api.submit(execution_result):
|
||||
logger.info('Job %d successfully partially submitted.' % job.id)
|
||||
logger.info("Job %d successfully partially submitted." % job.id)
|
||||
else:
|
||||
logger.info('Job %d failed to partially submit.' % job.id)
|
||||
logger.info("Job %d failed to partially submit." % job.id)
|
||||
except:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
shutil.rmtree(tempdir, ignore_errors=True)
|
||||
|
@ -56,7 +63,7 @@ def loop():
|
|||
return True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
api_key = os.getenv("API_KEY")
|
||||
if not api_key:
|
||||
print("no api key", file=sys.stderr)
|
||||
|
|
|
@ -9,12 +9,18 @@ from models import JobVerdict
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO)
|
||||
logging.info('Starting up')
|
||||
logging.info("Starting up")
|
||||
|
||||
|
||||
class Language(metaclass=ABCMeta):
|
||||
@classmethod
|
||||
def compile(cls, source_code: str, workdir: str, executable_name: str, time_limit: float = config.COMPILATION_TIME_LIMIT) -> str:
|
||||
def compile(
|
||||
cls,
|
||||
source_code: str,
|
||||
workdir: str,
|
||||
executable_name: str,
|
||||
time_limit: float = config.COMPILATION_TIME_LIMIT,
|
||||
) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
|
@ -32,15 +38,23 @@ class Language(metaclass=ABCMeta):
|
|||
|
||||
class CXX(Language):
|
||||
@classmethod
|
||||
def compile(cls, source_code: str, workdir: str, executable_name: str, time_limit: float = config.COMPILATION_TIME_LIMIT) -> str:
|
||||
source_file_path = os.path.join(workdir, 'source.cpp')
|
||||
with open(source_file_path, 'wb') as source_file:
|
||||
source_file.write(source_code.encode('utf-8'))
|
||||
def compile(
|
||||
cls,
|
||||
source_code: str,
|
||||
workdir: str,
|
||||
executable_name: str,
|
||||
time_limit: float = config.COMPILATION_TIME_LIMIT,
|
||||
) -> str:
|
||||
source_file_path = os.path.join(workdir, "source.cpp")
|
||||
with open(source_file_path, "wb") as source_file:
|
||||
source_file.write(source_code.encode("utf-8"))
|
||||
|
||||
executable_file_path = os.path.join(workdir, executable_name)
|
||||
try:
|
||||
subprocess.check_call(['g++', '--std=c++1y', '-o', executable_file_path, source_file_path],
|
||||
timeout=config.COMPILATION_TIME_LIMIT)
|
||||
subprocess.check_call(
|
||||
["g++", "--std=c++1y", "-o", executable_file_path, source_file_path],
|
||||
timeout=config.COMPILATION_TIME_LIMIT,
|
||||
)
|
||||
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
||||
return None
|
||||
|
||||
|
@ -60,15 +74,21 @@ class CXX(Language):
|
|||
|
||||
|
||||
class Python(Language):
|
||||
language_name = 'python'
|
||||
interpreter_name = 'python'
|
||||
|
||||
language_name = "python"
|
||||
interpreter_name = "python"
|
||||
|
||||
@classmethod
|
||||
def compile(cls, source_code: str, workdir: str, executable_name: str, time_limit: float = config.COMPILATION_TIME_LIMIT) -> str:
|
||||
executable_name += '.py'
|
||||
def compile(
|
||||
cls,
|
||||
source_code: str,
|
||||
workdir: str,
|
||||
executable_name: str,
|
||||
time_limit: float = config.COMPILATION_TIME_LIMIT,
|
||||
) -> str:
|
||||
executable_name += ".py"
|
||||
executable_path = os.path.join(workdir, executable_name)
|
||||
with open(executable_path, 'wb') as executable_file:
|
||||
executable_file.write(source_code.encode('utf-8'))
|
||||
with open(executable_path, "wb") as executable_file:
|
||||
executable_file.write(source_code.encode("utf-8"))
|
||||
|
||||
"""try:
|
||||
subprocess.check_call([cls.interpreter_name, '-m', 'py_compile', executable_name],
|
||||
|
@ -80,16 +100,21 @@ class Python(Language):
|
|||
|
||||
@classmethod
|
||||
def get_command(cls, workdir: str, executable_name: str) -> List[str]:
|
||||
return [os.path.join('/usr/bin', cls.interpreter_name), '-s', '-S', os.path.join(workdir, executable_name)]
|
||||
return [
|
||||
os.path.join("/usr/bin", cls.interpreter_name),
|
||||
"-s",
|
||||
"-S",
|
||||
os.path.join(workdir, executable_name),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_allowed_files(cls, workdir: str, executable_name: str):
|
||||
return [
|
||||
'/etc/nsswitch.conf',
|
||||
'/etc/passwd',
|
||||
'/dev/urandom', # TODO: come up with random policy
|
||||
'/tmp',
|
||||
'/bin/Modules/Setup',
|
||||
"/etc/nsswitch.conf",
|
||||
"/etc/passwd",
|
||||
"/dev/urandom", # TODO: come up with random policy
|
||||
"/tmp",
|
||||
"/bin/Modules/Setup",
|
||||
workdir,
|
||||
os.path.join(workdir, executable_name),
|
||||
]
|
||||
|
@ -101,57 +126,73 @@ class Python(Language):
|
|||
|
||||
class Java(Language):
|
||||
@classmethod
|
||||
def compile(cls, source_code: str, workdir: str, executable_name: str, time_limit: float = config.COMPILATION_TIME_LIMIT) -> str:
|
||||
source_file_path = os.path.join(workdir, 'Main.java')
|
||||
with open(source_file_path, 'wb') as source_file:
|
||||
source_file.write(source_code.encode('utf-8'))
|
||||
def compile(
|
||||
cls,
|
||||
source_code: str,
|
||||
workdir: str,
|
||||
executable_name: str,
|
||||
time_limit: float = config.COMPILATION_TIME_LIMIT,
|
||||
) -> str:
|
||||
source_file_path = os.path.join(workdir, "Main.java")
|
||||
with open(source_file_path, "wb") as source_file:
|
||||
source_file.write(source_code.encode("utf-8"))
|
||||
|
||||
executable_file_path = os.path.join(workdir, 'Main')
|
||||
executable_file_path = os.path.join(workdir, "Main")
|
||||
try:
|
||||
subprocess.check_call(['javac', '-d', workdir, source_file_path],
|
||||
timeout=config.COMPILATION_TIME_LIMIT)
|
||||
subprocess.check_call(
|
||||
["javac", "-d", workdir, source_file_path],
|
||||
timeout=config.COMPILATION_TIME_LIMIT,
|
||||
)
|
||||
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
||||
return None
|
||||
|
||||
return 'Main'
|
||||
return "Main"
|
||||
|
||||
@classmethod
|
||||
def get_command(cls, workdir: str, executable_name: str) -> List[str]:
|
||||
return ['/usr/bin/java', '-XX:-UsePerfData', '-XX:+DisableAttachMechanism', '-Xmx256m', '-Xrs', '-cp',
|
||||
workdir, executable_name]
|
||||
return [
|
||||
"/usr/bin/java",
|
||||
"-XX:-UsePerfData",
|
||||
"-XX:+DisableAttachMechanism",
|
||||
"-Xmx256m",
|
||||
"-Xrs",
|
||||
"-cp",
|
||||
workdir,
|
||||
executable_name,
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_allowed_files(cls, workdir: str, executable_name: str):
|
||||
return [
|
||||
'/etc/nsswitch.conf',
|
||||
'/etc/passwd',
|
||||
'/tmp',
|
||||
"/etc/nsswitch.conf",
|
||||
"/etc/passwd",
|
||||
"/tmp",
|
||||
workdir,
|
||||
os.path.join(workdir, executable_name + '.class'),
|
||||
os.path.join(workdir, executable_name + ".class"),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_allowed_file_prefixes(cls, workdir: str, executable_name: str):
|
||||
return [
|
||||
'/etc/java-7-openjdk/',
|
||||
'/tmp/.java_pid',
|
||||
'/tmp/',
|
||||
"/etc/java-7-openjdk/",
|
||||
"/tmp/.java_pid",
|
||||
"/tmp/",
|
||||
]
|
||||
|
||||
|
||||
class Python2(Python):
|
||||
language_name = 'python2'
|
||||
interpreter_name = 'python2.7'
|
||||
language_name = "python2"
|
||||
interpreter_name = "python2.7"
|
||||
|
||||
|
||||
class Python3(Python):
|
||||
language_name = 'python3'
|
||||
interpreter_name = 'python3.5'
|
||||
language_name = "python3"
|
||||
interpreter_name = "python3.5"
|
||||
|
||||
|
||||
languages = {
|
||||
'cxx': CXX,
|
||||
'python2': Python2,
|
||||
'python3': Python3,
|
||||
'java': Java,
|
||||
"cxx": CXX,
|
||||
"python2": Python2,
|
||||
"python3": Python3,
|
||||
"java": Java,
|
||||
} # type: Dict[str, Language]
|
||||
|
|
|
@ -2,9 +2,19 @@ import enum
|
|||
|
||||
|
||||
class Problem:
|
||||
def __init__(self, id: int, test_cases: int, time_limit: float, memory_limit: int,
|
||||
generator_code: str, generator_language, grader_code: str, grader_language,
|
||||
source_verifier_code: str=None, source_verifier_language=None):
|
||||
def __init__(
|
||||
self,
|
||||
id: int,
|
||||
test_cases: int,
|
||||
time_limit: float,
|
||||
memory_limit: int,
|
||||
generator_code: str,
|
||||
generator_language,
|
||||
grader_code: str,
|
||||
grader_language,
|
||||
source_verifier_code: str = None,
|
||||
source_verifier_language=None,
|
||||
):
|
||||
self.id = id
|
||||
self.test_cases = test_cases
|
||||
self.time_limit = time_limit
|
||||
|
@ -26,20 +36,27 @@ class Job:
|
|||
|
||||
|
||||
class JobVerdict(enum.Enum):
|
||||
accepted = 'AC'
|
||||
ran = 'RAN'
|
||||
invalid_source = 'IS'
|
||||
wrong_answer = 'WA'
|
||||
time_limit_exceeded = 'TLE'
|
||||
memory_limit_exceeded = 'MLE'
|
||||
runtime_error = 'RTE'
|
||||
illegal_syscall = 'ISC'
|
||||
compilation_error = 'CE'
|
||||
judge_error = 'JE'
|
||||
accepted = "AC"
|
||||
ran = "RAN"
|
||||
invalid_source = "IS"
|
||||
wrong_answer = "WA"
|
||||
time_limit_exceeded = "TLE"
|
||||
memory_limit_exceeded = "MLE"
|
||||
runtime_error = "RTE"
|
||||
illegal_syscall = "ISC"
|
||||
compilation_error = "CE"
|
||||
judge_error = "JE"
|
||||
|
||||
|
||||
class ExecutionResult:
|
||||
def __init__(self, job: Job, verdict: JobVerdict, last_ran_case: int, execution_time: float, execution_memory: int):
|
||||
def __init__(
|
||||
self,
|
||||
job: Job,
|
||||
verdict: JobVerdict,
|
||||
last_ran_case: int,
|
||||
execution_time: float,
|
||||
execution_memory: int,
|
||||
):
|
||||
self.job = job
|
||||
self.verdict = verdict
|
||||
self.last_ran_case = last_ran_case
|
||||
|
|
|
@ -13,11 +13,13 @@ def create_app(config=None):
|
|||
|
||||
if not config:
|
||||
from easyctf.config import Config
|
||||
|
||||
config = Config()
|
||||
app.config.from_object(config)
|
||||
|
||||
from easyctf.objects import cache, db, login_manager, sentry, migrate, s3
|
||||
import easyctf.models
|
||||
|
||||
cache.init_app(app)
|
||||
db.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
|
@ -27,6 +29,7 @@ def create_app(config=None):
|
|||
sentry.init_app(app, logging=True, level=logging.WARNING)
|
||||
|
||||
from easyctf.utils import filestore, to_place_str, to_timestamp
|
||||
|
||||
app.jinja_env.globals.update(filestore=filestore)
|
||||
app.jinja_env.filters["to_timestamp"] = to_timestamp
|
||||
app.jinja_env.filters["to_place_str"] = to_place_str
|
||||
|
@ -56,7 +59,11 @@ def create_app(config=None):
|
|||
# TODO: actually finish this
|
||||
@app.context_processor
|
||||
def inject_config():
|
||||
competition_start, competition_end, competition_running = get_competition_running()
|
||||
(
|
||||
competition_start,
|
||||
competition_end,
|
||||
competition_running,
|
||||
) = get_competition_running()
|
||||
easter_egg_enabled = False
|
||||
if competition_running and current_user.is_authenticated:
|
||||
try:
|
||||
|
@ -71,11 +78,12 @@ def create_app(config=None):
|
|||
competition_end=competition_end,
|
||||
ctf_name=Config.get("ctf_name", "OpenCTF"),
|
||||
easter_egg_enabled=easter_egg_enabled,
|
||||
environment=app.config.get("ENVIRONMENT", "production")
|
||||
environment=app.config.get("ENVIRONMENT", "production"),
|
||||
)
|
||||
return config
|
||||
|
||||
from easyctf.views import admin, base, classroom, chals, game, judge, teams, users
|
||||
|
||||
app.register_blueprint(admin.blueprint, url_prefix="/admin")
|
||||
app.register_blueprint(base.blueprint)
|
||||
app.register_blueprint(classroom.blueprint, url_prefix="/classroom")
|
||||
|
|
|
@ -11,8 +11,8 @@ class CTFCache(RedisCache):
|
|||
def dump_object(self, value):
|
||||
value_type = type(value)
|
||||
if value_type in (int, int):
|
||||
return str(value).encode('ascii')
|
||||
return b'!' + pickle.dumps(value, -1)
|
||||
return str(value).encode("ascii")
|
||||
return b"!" + pickle.dumps(value, -1)
|
||||
|
||||
|
||||
def cache(app, config, args, kwargs):
|
||||
|
@ -23,8 +23,7 @@ def cache(app, config, args, kwargs):
|
|||
class Config(object):
|
||||
def __init__(self, app_root=None, testing=False):
|
||||
if app_root is None:
|
||||
self.app_root = pathlib.Path(
|
||||
os.path.dirname(os.path.abspath(__file__)))
|
||||
self.app_root = pathlib.Path(os.path.dirname(os.path.abspath(__file__)))
|
||||
else:
|
||||
self.app_root = pathlib.Path(app_root)
|
||||
|
||||
|
@ -44,7 +43,8 @@ class Config(object):
|
|||
|
||||
self.S3_RESOURCE = os.getenv("S3_RESOURCE", "")
|
||||
self.FILESTORE_SAVE_ENDPOINT = os.getenv(
|
||||
"FILESTORE_SAVE_ENDPOINT", "http://filestore:5001/save")
|
||||
"FILESTORE_SAVE_ENDPOINT", "http://filestore:5001/save"
|
||||
)
|
||||
self.FILESTORE_STATIC = os.getenv("FILESTORE_STATIC", "/static")
|
||||
|
||||
self.JUDGE_URL = os.getenv("JUDGE_URL", "http://127.0.0.1/")
|
||||
|
@ -80,4 +80,7 @@ class Config(object):
|
|||
url = os.getenv("DATABASE_URL")
|
||||
if url:
|
||||
return url
|
||||
return "mysql://root:%s@db/%s" % (os.getenv("MYSQL_ROOT_PASSWORD"), os.getenv("MYSQL_DATABASE"))
|
||||
return "mysql://root:%s@db/%s" % (
|
||||
os.getenv("MYSQL_ROOT_PASSWORD"),
|
||||
os.getenv("MYSQL_DATABASE"),
|
||||
)
|
||||
|
|
|
@ -11,4 +11,4 @@ SUPPORTED_LANGUAGES = {
|
|||
"python2": "Python 2",
|
||||
"python3": "Python 3",
|
||||
"java": "Java",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ def email_verification_required(func):
|
|||
flash("You need to verify your email first.", "warning")
|
||||
return redirect(url_for("users.settings"))
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
|
@ -25,6 +26,7 @@ def admin_required(func):
|
|||
if not (current_user.is_authenticated and current_user.admin):
|
||||
abort(403)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
|
@ -34,6 +36,7 @@ def teacher_required(func):
|
|||
if not (current_user.is_authenticated and current_user.level == 3):
|
||||
abort(403)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
|
@ -41,9 +44,17 @@ def block_before_competition(func):
|
|||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
start_time = Config.get("start_time")
|
||||
if not current_user.is_authenticated or not (current_user.admin or (start_time and current_user.is_authenticated and datetime.utcnow() >= datetime.fromtimestamp(int(start_time)))):
|
||||
if not current_user.is_authenticated or not (
|
||||
current_user.admin
|
||||
or (
|
||||
start_time
|
||||
and current_user.is_authenticated
|
||||
and datetime.utcnow() >= datetime.fromtimestamp(int(start_time))
|
||||
)
|
||||
):
|
||||
abort(403)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
|
@ -51,9 +62,17 @@ def block_after_competition(func):
|
|||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
end_time = Config.get("end_time")
|
||||
if not current_user.is_authenticated or not (current_user.admin or (end_time and current_user.is_authenticated and datetime.utcnow() <= datetime.fromtimestamp(int(end_time)))):
|
||||
if not current_user.is_authenticated or not (
|
||||
current_user.admin
|
||||
or (
|
||||
end_time
|
||||
and current_user.is_authenticated
|
||||
and datetime.utcnow() <= datetime.fromtimestamp(int(end_time))
|
||||
)
|
||||
):
|
||||
abort(403)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
|
@ -72,19 +91,27 @@ def team_required(func):
|
|||
def is_team_captain(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
if not(current_user.is_authenticated and current_user.tid and current_user.team.owner == current_user.uid):
|
||||
if not (
|
||||
current_user.is_authenticated
|
||||
and current_user.tid
|
||||
and current_user.team.owner == current_user.uid
|
||||
):
|
||||
return abort(403)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def no_cache(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
response = make_response(func(*args, **kwargs))
|
||||
response.headers['Last-Modified'] = datetime.now()
|
||||
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0'
|
||||
response.headers['Pragma'] = 'no-cache'
|
||||
response.headers['Expires'] = '-1'
|
||||
response.headers["Last-Modified"] = datetime.now()
|
||||
response.headers[
|
||||
"Cache-Control"
|
||||
] = "no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0"
|
||||
response.headers["Pragma"] = "no-cache"
|
||||
response.headers["Expires"] = "-1"
|
||||
return response
|
||||
|
||||
return update_wrapper(wrapper, func)
|
||||
|
|
|
@ -5,9 +5,16 @@ from datetime import datetime
|
|||
from flask_wtf import FlaskForm
|
||||
from sqlalchemy import and_
|
||||
from wtforms import ValidationError
|
||||
from wtforms.fields import (BooleanField, FloatField, HiddenField,
|
||||
IntegerField, StringField, SubmitField,
|
||||
TextAreaField, DateTimeLocalField)
|
||||
from wtforms.fields import (
|
||||
BooleanField,
|
||||
FloatField,
|
||||
HiddenField,
|
||||
IntegerField,
|
||||
StringField,
|
||||
SubmitField,
|
||||
TextAreaField,
|
||||
DateTimeLocalField,
|
||||
)
|
||||
from wtforms.validators import InputRequired, NumberRange, Optional
|
||||
|
||||
from easyctf.models import Problem
|
||||
|
@ -27,16 +34,30 @@ class DateTimeField(DateTimeLocalField):
|
|||
|
||||
|
||||
class ProblemForm(FlaskForm):
|
||||
author = StringField("Problem Author", validators=[InputRequired("Please enter the author.")])
|
||||
title = StringField("Problem Title", validators=[InputRequired("Please enter a problem title.")])
|
||||
name = StringField("Problem Name (slug)", validators=[InputRequired("Please enter a problem name.")])
|
||||
category = StringField("Problem Category", validators=[InputRequired("Please enter a problem category.")])
|
||||
description = TextAreaField("Description", validators=[InputRequired("Please enter a description.")])
|
||||
author = StringField(
|
||||
"Problem Author", validators=[InputRequired("Please enter the author.")]
|
||||
)
|
||||
title = StringField(
|
||||
"Problem Title", validators=[InputRequired("Please enter a problem title.")]
|
||||
)
|
||||
name = StringField(
|
||||
"Problem Name (slug)",
|
||||
validators=[InputRequired("Please enter a problem name.")],
|
||||
)
|
||||
category = StringField(
|
||||
"Problem Category",
|
||||
validators=[InputRequired("Please enter a problem category.")],
|
||||
)
|
||||
description = TextAreaField(
|
||||
"Description", validators=[InputRequired("Please enter a description.")]
|
||||
)
|
||||
value = IntegerField("Value", validators=[InputRequired("Please enter a value.")])
|
||||
programming = BooleanField(default=False, validators=[Optional()])
|
||||
|
||||
autogen = BooleanField("Autogen", validators=[Optional()])
|
||||
grader = TextAreaField("Grader", validators=[InputRequired("Please enter a grader.")])
|
||||
grader = TextAreaField(
|
||||
"Grader", validators=[InputRequired("Please enter a grader.")]
|
||||
)
|
||||
generator = TextAreaField("Generator", validators=[Optional()])
|
||||
source_verifier = TextAreaField("Source Verifier", validators=[Optional()])
|
||||
|
||||
|
@ -48,7 +69,9 @@ class ProblemForm(FlaskForm):
|
|||
|
||||
def validate_name(self, field):
|
||||
if not VALID_PROBLEM_NAME.match(field.data):
|
||||
raise ValidationError("Problem name must be an all-lowercase, slug-style string.")
|
||||
raise ValidationError(
|
||||
"Problem name must be an all-lowercase, slug-style string."
|
||||
)
|
||||
# if Problem.query.filter(Problem.name == field.data).count():
|
||||
# raise ValidationError("That problem name already exists.")
|
||||
|
||||
|
@ -60,18 +83,22 @@ class ProblemForm(FlaskForm):
|
|||
else:
|
||||
try:
|
||||
exec(field.data, grader.__dict__)
|
||||
assert hasattr(grader, "grade"), \
|
||||
"Grader is missing a 'grade' function."
|
||||
assert hasattr(grader, "grade"), "Grader is missing a 'grade' function."
|
||||
if self.autogen.data:
|
||||
assert hasattr(grader, "generate"), "Grader is missing a 'generate' function."
|
||||
assert hasattr(
|
||||
grader, "generate"
|
||||
), "Grader is missing a 'generate' function."
|
||||
seed1 = generate_string()
|
||||
import random
|
||||
|
||||
random.seed(seed1)
|
||||
data = grader.generate(random)
|
||||
assert type(data) is dict, "'generate' must return dict"
|
||||
else:
|
||||
result = grader.grade(None, "")
|
||||
assert type(result) is tuple, "'grade' must return (correct, message)"
|
||||
assert (
|
||||
type(result) is tuple
|
||||
), "'grade' must return (correct, message)"
|
||||
correct, message = result
|
||||
assert type(correct) is bool, "'correct' must be a boolean."
|
||||
assert type(message) is str, "'message' must be a string."
|
||||
|
@ -80,14 +107,27 @@ class ProblemForm(FlaskForm):
|
|||
|
||||
|
||||
class SettingsForm(FlaskForm):
|
||||
team_size = IntegerField("Team Size", default=5, validators=[NumberRange(min=1), InputRequired("Please enter a max team size.")])
|
||||
ctf_name = StringField("CTF Name", default="OpenCTF", validators=[InputRequired("Please enter a CTF name.")])
|
||||
start_time = DateTimeField("Start Time", validators=[InputRequired("Please enter a CTF start time.")])
|
||||
end_time = DateTimeField("End Time", validators=[InputRequired("Please enter a CTF end time.")])
|
||||
team_size = IntegerField(
|
||||
"Team Size",
|
||||
default=5,
|
||||
validators=[NumberRange(min=1), InputRequired("Please enter a max team size.")],
|
||||
)
|
||||
ctf_name = StringField(
|
||||
"CTF Name",
|
||||
default="OpenCTF",
|
||||
validators=[InputRequired("Please enter a CTF name.")],
|
||||
)
|
||||
start_time = DateTimeField(
|
||||
"Start Time", validators=[InputRequired("Please enter a CTF start time.")]
|
||||
)
|
||||
end_time = DateTimeField(
|
||||
"End Time", validators=[InputRequired("Please enter a CTF end time.")]
|
||||
)
|
||||
judge_api_key = StringField("Judge API Key", validators=[Optional()])
|
||||
|
||||
submit = SubmitField("Save Settings")
|
||||
|
||||
def validate_start_time(self, field):
|
||||
import logging
|
||||
|
||||
logging.error("lol {}".format(field.data))
|
||||
|
|
|
@ -8,14 +8,12 @@ from easyctf.constants import SUPPORTED_LANGUAGES
|
|||
|
||||
class ProblemSubmitForm(FlaskForm):
|
||||
pid = HiddenField("Problem ID")
|
||||
flag = StringField("Flag",
|
||||
validators=[InputRequired("Please enter a flag.")])
|
||||
flag = StringField("Flag", validators=[InputRequired("Please enter a flag.")])
|
||||
|
||||
|
||||
class ProgrammingSubmitForm(FlaskForm):
|
||||
pid = HiddenField()
|
||||
code = TextAreaField("Code",
|
||||
validators=[InputRequired("Please enter code.")])
|
||||
code = TextAreaField("Code", validators=[InputRequired("Please enter code.")])
|
||||
language = HiddenField()
|
||||
|
||||
def validate_language(self, field):
|
||||
|
|
|
@ -17,5 +17,7 @@ class AddTeamForm(FlaskForm):
|
|||
submit = SubmitField("Add Team")
|
||||
|
||||
def validate_name(self, field):
|
||||
if not Team.query.filter(func.lower(Team.teamname) == field.data.lower()).count():
|
||||
if not Team.query.filter(
|
||||
func.lower(Team.teamname) == field.data.lower()
|
||||
).count():
|
||||
raise ValidationError("Team does not exist!")
|
||||
|
|
|
@ -13,4 +13,4 @@ class GameStateUpdateForm(FlaskForm):
|
|||
try:
|
||||
json.loads(field.data)
|
||||
except:
|
||||
raise ValidationError('invalid json!')
|
||||
raise ValidationError("invalid json!")
|
||||
|
|
|
@ -10,13 +10,20 @@ from easyctf.models import Config, Team, User
|
|||
|
||||
|
||||
class AddMemberForm(FlaskForm):
|
||||
username = StringField("Username", validators=[InputRequired(
|
||||
"Please enter the username of the person you would like to add.")])
|
||||
username = StringField(
|
||||
"Username",
|
||||
validators=[
|
||||
InputRequired(
|
||||
"Please enter the username of the person you would like to add."
|
||||
)
|
||||
],
|
||||
)
|
||||
submit = SubmitField("Add")
|
||||
|
||||
def get_user(self):
|
||||
query = User.query.filter(
|
||||
func.lower(User.username) == self.username.data.lower())
|
||||
func.lower(User.username) == self.username.data.lower()
|
||||
)
|
||||
return query.first()
|
||||
|
||||
def validate_username(self, field):
|
||||
|
@ -25,9 +32,12 @@ class AddMemberForm(FlaskForm):
|
|||
if current_user.team.owner != current_user.uid:
|
||||
raise ValidationError("Only the team captain can invite new members.")
|
||||
if len(current_user.team.outgoing_invitations) >= Config.get_team_size():
|
||||
raise ValidationError("You've already sent the maximum number of invitations.")
|
||||
raise ValidationError(
|
||||
"You've already sent the maximum number of invitations."
|
||||
)
|
||||
user = User.query.filter(
|
||||
func.lower(User.username) == field.data.lower()).first()
|
||||
func.lower(User.username) == field.data.lower()
|
||||
).first()
|
||||
if user is None:
|
||||
raise ValidationError("This user doesn't exist.")
|
||||
if user.tid is not None:
|
||||
|
@ -37,8 +47,21 @@ class AddMemberForm(FlaskForm):
|
|||
|
||||
|
||||
class CreateTeamForm(FlaskForm):
|
||||
teamname = StringField("Team Name", validators=[InputRequired("Please create a team name."), TeamLengthValidator])
|
||||
school = StringField("School", validators=[InputRequired("Please enter your school."), Length(3, 36, "Your school name must be between 3 and 36 characters long. Use abbreviations if necessary.")])
|
||||
teamname = StringField(
|
||||
"Team Name",
|
||||
validators=[InputRequired("Please create a team name."), TeamLengthValidator],
|
||||
)
|
||||
school = StringField(
|
||||
"School",
|
||||
validators=[
|
||||
InputRequired("Please enter your school."),
|
||||
Length(
|
||||
3,
|
||||
36,
|
||||
"Your school name must be between 3 and 36 characters long. Use abbreviations if necessary.",
|
||||
),
|
||||
],
|
||||
)
|
||||
submit = SubmitField("Create Team")
|
||||
|
||||
def validate_teamname(self, field):
|
||||
|
@ -62,8 +85,21 @@ class DisbandTeamForm(FlaskForm):
|
|||
|
||||
|
||||
class ManageTeamForm(FlaskForm):
|
||||
teamname = StringField("Team Name", validators=[InputRequired("Please create a team name."), TeamLengthValidator])
|
||||
school = StringField("School", validators=[InputRequired("Please enter your school."), Length(3, 36, "Your school name must be between 3 and 36 characters long. Use abbreviations if necessary.")])
|
||||
teamname = StringField(
|
||||
"Team Name",
|
||||
validators=[InputRequired("Please create a team name."), TeamLengthValidator],
|
||||
)
|
||||
school = StringField(
|
||||
"School",
|
||||
validators=[
|
||||
InputRequired("Please enter your school."),
|
||||
Length(
|
||||
3,
|
||||
36,
|
||||
"Your school name must be between 3 and 36 characters long. Use abbreviations if necessary.",
|
||||
),
|
||||
],
|
||||
)
|
||||
submit = SubmitField("Update")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -71,13 +107,28 @@ class ManageTeamForm(FlaskForm):
|
|||
self.tid = kwargs.get("tid", None)
|
||||
|
||||
def validate_teamname(self, field):
|
||||
if Team.query.filter(and_(func.lower(Team.teamname) == field.data.lower(), Team.tid != self.tid)).count():
|
||||
if Team.query.filter(
|
||||
and_(func.lower(Team.teamname) == field.data.lower(), Team.tid != self.tid)
|
||||
).count():
|
||||
raise ValidationError("Team name is taken.")
|
||||
|
||||
|
||||
class ProfileEditForm(FlaskForm):
|
||||
teamname = StringField("Team Name", validators=[InputRequired("Please enter a team name."), TeamLengthValidator])
|
||||
school = StringField("School", validators=[InputRequired("Please enter your school."), Length(3, 36, "Your school name must be between 3 and 36 characters long. Use abbreviations if necessary.")])
|
||||
teamname = StringField(
|
||||
"Team Name",
|
||||
validators=[InputRequired("Please enter a team name."), TeamLengthValidator],
|
||||
)
|
||||
school = StringField(
|
||||
"School",
|
||||
validators=[
|
||||
InputRequired("Please enter your school."),
|
||||
Length(
|
||||
3,
|
||||
36,
|
||||
"Your school name must be between 3 and 36 characters long. Use abbreviations if necessary.",
|
||||
),
|
||||
],
|
||||
)
|
||||
avatar = FileField("Avatar")
|
||||
remove_avatar = BooleanField("Remove Avatar")
|
||||
submit = SubmitField("Update Profile")
|
||||
|
|
|
@ -2,9 +2,15 @@ from flask_login import current_user
|
|||
from flask_wtf import FlaskForm
|
||||
from sqlalchemy import func
|
||||
from wtforms import ValidationError
|
||||
from wtforms.fields import (BooleanField, FileField, IntegerField,
|
||||
PasswordField, RadioField, StringField,
|
||||
SubmitField)
|
||||
from wtforms.fields import (
|
||||
BooleanField,
|
||||
FileField,
|
||||
IntegerField,
|
||||
PasswordField,
|
||||
RadioField,
|
||||
StringField,
|
||||
SubmitField,
|
||||
)
|
||||
from wtforms.validators import Email, EqualTo, InputRequired, Length, Optional
|
||||
from wtforms.widgets import NumberInput
|
||||
|
||||
|
@ -14,10 +20,18 @@ from easyctf.utils import VALID_USERNAME
|
|||
|
||||
|
||||
class ChangeLoginForm(FlaskForm):
|
||||
email = StringField("Email", validators=[InputRequired("Please enter your email."), Email()])
|
||||
old_password = PasswordField("Current Password", validators=[InputRequired("Please enter your current password.")])
|
||||
email = StringField(
|
||||
"Email", validators=[InputRequired("Please enter your email."), Email()]
|
||||
)
|
||||
old_password = PasswordField(
|
||||
"Current Password",
|
||||
validators=[InputRequired("Please enter your current password.")],
|
||||
)
|
||||
password = PasswordField("Password", validators=[Optional()])
|
||||
confirm_password = PasswordField("Confirm Password", validators=[Optional(), EqualTo("password", "Please enter the same password.")])
|
||||
confirm_password = PasswordField(
|
||||
"Confirm Password",
|
||||
validators=[Optional(), EqualTo("password", "Please enter the same password.")],
|
||||
)
|
||||
submit = SubmitField("Update Login Information")
|
||||
|
||||
def validate_old_password(self, field):
|
||||
|
@ -26,14 +40,24 @@ class ChangeLoginForm(FlaskForm):
|
|||
|
||||
|
||||
class LoginForm(FlaskForm):
|
||||
username = StringField("Username", validators=[InputRequired("Please enter your username."), UsernameLengthValidator])
|
||||
password = PasswordField("Password", validators=[InputRequired("Please enter your password.")])
|
||||
username = StringField(
|
||||
"Username",
|
||||
validators=[
|
||||
InputRequired("Please enter your username."),
|
||||
UsernameLengthValidator,
|
||||
],
|
||||
)
|
||||
password = PasswordField(
|
||||
"Password", validators=[InputRequired("Please enter your password.")]
|
||||
)
|
||||
code = IntegerField("Two-Factor Token", validators=[Optional()])
|
||||
remember = BooleanField("Remember Me")
|
||||
submit = SubmitField("Login")
|
||||
|
||||
def get_user(self):
|
||||
query = User.query.filter(func.lower(User.username) == self.username.data.lower())
|
||||
query = User.query.filter(
|
||||
func.lower(User.username) == self.username.data.lower()
|
||||
)
|
||||
return query.first()
|
||||
|
||||
def validate_username(self, field):
|
||||
|
@ -56,7 +80,13 @@ class ProfileEditForm(FlaskForm):
|
|||
|
||||
|
||||
class PasswordForgotForm(FlaskForm):
|
||||
email = StringField("Email", validators=[InputRequired("Please enter your email."), Email("Please enter a valid email.")])
|
||||
email = StringField(
|
||||
"Email",
|
||||
validators=[
|
||||
InputRequired("Please enter your email."),
|
||||
Email("Please enter a valid email."),
|
||||
],
|
||||
)
|
||||
submit = SubmitField("Send Recovery Email")
|
||||
|
||||
def __init__(self):
|
||||
|
@ -68,29 +98,59 @@ class PasswordForgotForm(FlaskForm):
|
|||
def user(self):
|
||||
if not self._user_cached:
|
||||
self._user = User.query.filter(
|
||||
func.lower(User.email) == self.email.data.lower()).first()
|
||||
func.lower(User.email) == self.email.data.lower()
|
||||
).first()
|
||||
self._user_cached = True
|
||||
return self._user
|
||||
|
||||
|
||||
class PasswordResetForm(FlaskForm):
|
||||
password = PasswordField("Password", validators=[InputRequired("Please enter a password.")])
|
||||
confirm_password = PasswordField("Confirm Password", validators=[InputRequired("Please confirm your password."), EqualTo("password", "Please enter the same password.")])
|
||||
password = PasswordField(
|
||||
"Password", validators=[InputRequired("Please enter a password.")]
|
||||
)
|
||||
confirm_password = PasswordField(
|
||||
"Confirm Password",
|
||||
validators=[
|
||||
InputRequired("Please confirm your password."),
|
||||
EqualTo("password", "Please enter the same password."),
|
||||
],
|
||||
)
|
||||
submit = SubmitField("Change Password")
|
||||
|
||||
|
||||
class RegisterForm(FlaskForm):
|
||||
name = StringField("Name", validators=[InputRequired("Please enter a name.")])
|
||||
username = StringField("Username", validators=[InputRequired("Please enter a username."), UsernameLengthValidator])
|
||||
email = StringField("Email", validators=[InputRequired("Please enter an email."), Email("Please enter a valid email.")])
|
||||
password = PasswordField("Password", validators=[InputRequired("Please enter a password.")])
|
||||
confirm_password = PasswordField("Confirm Password", validators=[InputRequired("Please confirm your password."), EqualTo("password", "Please enter the same password.")])
|
||||
level = RadioField("Who are you?", choices=[("1", "Student"), ("2", "Observer"), ("3", "Teacher")])
|
||||
username = StringField(
|
||||
"Username",
|
||||
validators=[InputRequired("Please enter a username."), UsernameLengthValidator],
|
||||
)
|
||||
email = StringField(
|
||||
"Email",
|
||||
validators=[
|
||||
InputRequired("Please enter an email."),
|
||||
Email("Please enter a valid email."),
|
||||
],
|
||||
)
|
||||
password = PasswordField(
|
||||
"Password", validators=[InputRequired("Please enter a password.")]
|
||||
)
|
||||
confirm_password = PasswordField(
|
||||
"Confirm Password",
|
||||
validators=[
|
||||
InputRequired("Please confirm your password."),
|
||||
EqualTo("password", "Please enter the same password."),
|
||||
],
|
||||
)
|
||||
level = RadioField(
|
||||
"Who are you?", choices=[("1", "Student"), ("2", "Observer"), ("3", "Teacher")]
|
||||
)
|
||||
submit = SubmitField("Register")
|
||||
|
||||
def validate_username(self, field):
|
||||
if not VALID_USERNAME.match(field.data):
|
||||
raise ValidationError("Username must be contain letters, numbers, or _, and not start with a number.")
|
||||
raise ValidationError(
|
||||
"Username must be contain letters, numbers, or _, and not start with a number."
|
||||
)
|
||||
if User.query.filter(func.lower(User.username) == field.data.lower()).count():
|
||||
raise ValidationError("Username is taken.")
|
||||
|
||||
|
@ -100,9 +160,14 @@ class RegisterForm(FlaskForm):
|
|||
|
||||
|
||||
class TwoFactorAuthSetupForm(FlaskForm):
|
||||
code = IntegerField("Code", validators=[InputRequired("Please enter the code.")], widget=NumberInput())
|
||||
password = PasswordField("Password", validators=[
|
||||
InputRequired("Please enter your password.")])
|
||||
code = IntegerField(
|
||||
"Code",
|
||||
validators=[InputRequired("Please enter the code.")],
|
||||
widget=NumberInput(),
|
||||
)
|
||||
password = PasswordField(
|
||||
"Password", validators=[InputRequired("Please enter your password.")]
|
||||
)
|
||||
submit = SubmitField("Confirm")
|
||||
|
||||
def validate_code(self, field):
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
from wtforms.validators import Length
|
||||
|
||||
UsernameLengthValidator = Length(3, 16, message="Usernames must be between 3 to 16 characters long.")
|
||||
TeamLengthValidator = Length(3, 32, message="Usernames must be between 3 to 32 characters long.")
|
||||
UsernameLengthValidator = Length(
|
||||
3, 16, message="Usernames must be between 3 to 16 characters long."
|
||||
)
|
||||
TeamLengthValidator = Length(
|
||||
3, 32, message="Usernames must be between 3 to 32 characters long."
|
||||
)
|
||||
|
|
|
@ -29,8 +29,12 @@ from sqlalchemy.sql.expression import union_all
|
|||
from easyctf.config import Config as AppConfig
|
||||
from easyctf.constants import USER_REGULAR
|
||||
from easyctf.objects import cache, db, login_manager
|
||||
from easyctf.utils import (generate_identicon, generate_short_string,
|
||||
generate_string, save_file)
|
||||
from easyctf.utils import (
|
||||
generate_identicon,
|
||||
generate_short_string,
|
||||
generate_string,
|
||||
save_file,
|
||||
)
|
||||
|
||||
config = AppConfig()
|
||||
SEED = "OPENCTF_PROBLEM_SEED_PREFIX_%s" % config.SECRET_KEY
|
||||
|
@ -44,28 +48,34 @@ def filename_filter(name):
|
|||
return re.sub("[^a-zA-Z0-9]+", "_", name)
|
||||
|
||||
|
||||
team_classroom = db.Table("team_classroom",
|
||||
db.Column("team_id", db.Integer, db.ForeignKey(
|
||||
"teams.tid"), nullable=False),
|
||||
db.Column("classroom_id", db.Integer, db.ForeignKey(
|
||||
"classrooms.id"), nullable=False),
|
||||
db.PrimaryKeyConstraint("team_id", "classroom_id"))
|
||||
classroom_invitation = db.Table("classroom_invitation",
|
||||
db.Column("team_id", db.Integer, db.ForeignKey(
|
||||
"teams.tid"), nullable=False),
|
||||
db.Column("classroom_id", db.Integer, db.ForeignKey(
|
||||
"classrooms.id"), nullable=False),
|
||||
db.PrimaryKeyConstraint("team_id", "classroom_id"))
|
||||
team_classroom = db.Table(
|
||||
"team_classroom",
|
||||
db.Column("team_id", db.Integer, db.ForeignKey("teams.tid"), nullable=False),
|
||||
db.Column(
|
||||
"classroom_id", db.Integer, db.ForeignKey("classrooms.id"), nullable=False
|
||||
),
|
||||
db.PrimaryKeyConstraint("team_id", "classroom_id"),
|
||||
)
|
||||
classroom_invitation = db.Table(
|
||||
"classroom_invitation",
|
||||
db.Column("team_id", db.Integer, db.ForeignKey("teams.tid"), nullable=False),
|
||||
db.Column(
|
||||
"classroom_id", db.Integer, db.ForeignKey("classrooms.id"), nullable=False
|
||||
),
|
||||
db.PrimaryKeyConstraint("team_id", "classroom_id"),
|
||||
)
|
||||
|
||||
team_player_invitation = db.Table("team_player_invitation",
|
||||
db.Column("team_id", db.Integer, db.ForeignKey(
|
||||
"teams.tid", primary_key=True)),
|
||||
db.Column("user_id", db.Integer, db.ForeignKey("users.uid", primary_key=True)))
|
||||
team_player_invitation = db.Table(
|
||||
"team_player_invitation",
|
||||
db.Column("team_id", db.Integer, db.ForeignKey("teams.tid", primary_key=True)),
|
||||
db.Column("user_id", db.Integer, db.ForeignKey("users.uid", primary_key=True)),
|
||||
)
|
||||
|
||||
player_team_invitation = db.Table("player_team_invitation",
|
||||
db.Column("user_id", db.Integer, db.ForeignKey(
|
||||
"users.uid", primary_key=True)),
|
||||
db.Column("team_id", db.Integer, db.ForeignKey("teams.tid", primary_key=True)))
|
||||
player_team_invitation = db.Table(
|
||||
"player_team_invitation",
|
||||
db.Column("user_id", db.Integer, db.ForeignKey("users.uid", primary_key=True)),
|
||||
db.Column("team_id", db.Integer, db.ForeignKey("teams.tid", primary_key=True)),
|
||||
)
|
||||
|
||||
|
||||
class Config(db.Model):
|
||||
|
@ -125,10 +135,12 @@ class Config(db.Model):
|
|||
key = RSA.generate(2048)
|
||||
private_key = key.exportKey("PEM")
|
||||
public_key = key.publickey().exportKey("OpenSSH")
|
||||
cls.set_many({
|
||||
"private_key": str(private_key, "utf-8"),
|
||||
"public_key": str(public_key, "utf-8")
|
||||
})
|
||||
cls.set_many(
|
||||
{
|
||||
"private_key": str(private_key, "utf-8"),
|
||||
"public_key": str(public_key, "utf-8"),
|
||||
}
|
||||
)
|
||||
return private_key, public_key
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -158,7 +170,12 @@ class User(db.Model):
|
|||
jobs = db.relationship("Job", backref="user", lazy=True)
|
||||
_avatar = db.Column("avatar", db.String(128))
|
||||
|
||||
outgoing_invitations = db.relationship("Team", secondary=player_team_invitation, lazy="subquery", backref=db.backref("incoming_invitations", lazy=True))
|
||||
outgoing_invitations = db.relationship(
|
||||
"Team",
|
||||
secondary=player_team_invitation,
|
||||
lazy="subquery",
|
||||
backref=db.backref("incoming_invitations", lazy=True),
|
||||
)
|
||||
|
||||
@property
|
||||
def avatar(self):
|
||||
|
@ -167,8 +184,7 @@ class User(db.Model):
|
|||
avatar = generate_identicon("user%s" % self.uid)
|
||||
avatar.save(avatar_file, format="PNG")
|
||||
avatar_file.seek(0)
|
||||
response = save_file(
|
||||
avatar_file, prefix="team_avatar_", suffix=".png")
|
||||
response = save_file(avatar_file, prefix="team_avatar_", suffix=".png")
|
||||
if response.status_code == 200:
|
||||
self._avatar = response.text
|
||||
db.session.add(self)
|
||||
|
@ -231,7 +247,12 @@ class User(db.Model):
|
|||
db.session.add(self)
|
||||
db.session.commit()
|
||||
service_name = Config.get("ctf_name")
|
||||
return "otpauth://totp/%s:%s?secret=%s&issuer=%s" % (service_name, self.username, self.otp_secret, service_name)
|
||||
return "otpauth://totp/%s:%s?secret=%s&issuer=%s" % (
|
||||
service_name,
|
||||
self.username,
|
||||
self.otp_secret,
|
||||
service_name,
|
||||
)
|
||||
|
||||
def verify_totp(self, token):
|
||||
return onetimepass.valid_totp(token, self.otp_secret)
|
||||
|
@ -272,8 +293,7 @@ class Problem(db.Model):
|
|||
path = db.Column(db.String(128)) # path to problem source code
|
||||
|
||||
files = db.relationship("File", backref="problem", lazy=True)
|
||||
autogen_files = db.relationship(
|
||||
"AutogenFile", backref="problem", lazy=True)
|
||||
autogen_files = db.relationship("AutogenFile", backref="problem", lazy=True)
|
||||
|
||||
@staticmethod
|
||||
def validate_problem(path, name):
|
||||
|
@ -295,7 +315,11 @@ class Problem(db.Model):
|
|||
|
||||
for required_key in ["test_cases", "time_limit", "memory_limit"]:
|
||||
if required_key not in metadata:
|
||||
print("\t* Expected required key {} in 'problem.yml'".format(required_key))
|
||||
print(
|
||||
"\t* Expected required key {} in 'problem.yml'".format(
|
||||
required_key
|
||||
)
|
||||
)
|
||||
valid = False
|
||||
|
||||
return valid
|
||||
|
@ -359,7 +383,9 @@ class Problem(db.Model):
|
|||
|
||||
@staticmethod
|
||||
def import_repository(path):
|
||||
if not (os.path.realpath(path) and os.path.exists(path) and os.path.isdir(path)):
|
||||
if not (
|
||||
os.path.realpath(path) and os.path.exists(path) and os.path.isdir(path)
|
||||
):
|
||||
print("this isn't a path")
|
||||
sys.exit(1)
|
||||
path = os.path.realpath(path)
|
||||
|
@ -375,7 +401,9 @@ class Problem(db.Model):
|
|||
|
||||
@classmethod
|
||||
def categories(cls):
|
||||
def f(c): return c[0]
|
||||
def f(c):
|
||||
return c[0]
|
||||
|
||||
categories = map(f, db.session.query(Problem.category).distinct().all())
|
||||
return list(categories)
|
||||
|
||||
|
@ -435,7 +463,9 @@ class Problem(db.Model):
|
|||
solved = Solve.query.filter_by(tid=current_user.tid, pid=self.pid).first()
|
||||
if solved:
|
||||
return "error", "You've already solved this problem"
|
||||
already_tried = WrongFlag.query.filter_by(tid=current_user.tid, pid=self.pid, flag=flag).count()
|
||||
already_tried = WrongFlag.query.filter_by(
|
||||
tid=current_user.tid, pid=self.pid, flag=flag
|
||||
).count()
|
||||
if already_tried:
|
||||
return "error", "You've already tried this flag"
|
||||
random = None
|
||||
|
@ -445,13 +475,16 @@ class Problem(db.Model):
|
|||
grader = self.get_grader()
|
||||
correct, message = grader.grade(random, flag)
|
||||
if correct:
|
||||
submission = Solve(pid=self.pid, tid=current_user.tid, uid=current_user.uid, flag=flag)
|
||||
submission = Solve(
|
||||
pid=self.pid, tid=current_user.tid, uid=current_user.uid, flag=flag
|
||||
)
|
||||
db.session.add(submission)
|
||||
db.session.commit()
|
||||
else:
|
||||
if len(flag) < 256:
|
||||
submission = WrongFlag(pid=self.pid, tid=current_user.tid, uid=current_user.uid,
|
||||
flag=flag)
|
||||
submission = WrongFlag(
|
||||
pid=self.pid, tid=current_user.tid, uid=current_user.uid, flag=flag
|
||||
)
|
||||
db.session.add(submission)
|
||||
db.session.commit()
|
||||
else:
|
||||
|
@ -466,9 +499,21 @@ class Problem(db.Model):
|
|||
return "success" if correct else "failure", message
|
||||
|
||||
def api_summary(self):
|
||||
summary = {field: getattr(self, field) for field in ['pid', 'author', 'name', 'title', 'hint',
|
||||
'category', 'value', 'solved', 'programming']}
|
||||
summary['description'] = self.render_description(current_user.tid)
|
||||
summary = {
|
||||
field: getattr(self, field)
|
||||
for field in [
|
||||
"pid",
|
||||
"author",
|
||||
"name",
|
||||
"title",
|
||||
"hint",
|
||||
"category",
|
||||
"value",
|
||||
"solved",
|
||||
"programming",
|
||||
]
|
||||
}
|
||||
summary["description"] = self.render_description(current_user.tid)
|
||||
return summary
|
||||
|
||||
|
||||
|
@ -536,7 +581,7 @@ class PasswordResetToken(db.Model):
|
|||
|
||||
class Solve(db.Model):
|
||||
__tablename__ = "solves"
|
||||
__table_args__ = (db.UniqueConstraint('pid', 'tid'),)
|
||||
__table_args__ = (db.UniqueConstraint("pid", "tid"),)
|
||||
id = db.Column(db.Integer, index=True, primary_key=True)
|
||||
pid = db.Column(db.Integer, db.ForeignKey("problems.pid"), index=True)
|
||||
tid = db.Column(db.Integer, db.ForeignKey("teams.tid"), index=True)
|
||||
|
@ -577,8 +622,12 @@ class Team(db.Model):
|
|||
teamname = db.Column(db.Unicode(32), unique=True)
|
||||
school = db.Column(db.Unicode(64))
|
||||
owner = db.Column(db.Integer)
|
||||
classrooms = db.relationship("Classroom", secondary=team_classroom, backref="classrooms")
|
||||
classroom_invites = db.relationship("Classroom", secondary=classroom_invitation, backref="classroom_invites")
|
||||
classrooms = db.relationship(
|
||||
"Classroom", secondary=team_classroom, backref="classrooms"
|
||||
)
|
||||
classroom_invites = db.relationship(
|
||||
"Classroom", secondary=classroom_invitation, backref="classroom_invites"
|
||||
)
|
||||
members = db.relationship("User", back_populates="team")
|
||||
admin = db.Column(db.Boolean, default=False)
|
||||
shell_user = db.Column(db.String(16), unique=True)
|
||||
|
@ -588,7 +637,12 @@ class Team(db.Model):
|
|||
jobs = db.relationship("Job", backref="team", lazy=True)
|
||||
_avatar = db.Column("avatar", db.String(128))
|
||||
|
||||
outgoing_invitations = db.relationship("User", secondary=team_player_invitation, lazy="subquery", backref=db.backref("incoming_invitations", lazy=True))
|
||||
outgoing_invitations = db.relationship(
|
||||
"User",
|
||||
secondary=team_player_invitation,
|
||||
lazy="subquery",
|
||||
backref=db.backref("incoming_invitations", lazy=True),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s_%s" % (self.__class__.__name__, self.tid)
|
||||
|
@ -603,8 +657,7 @@ class Team(db.Model):
|
|||
avatar = generate_identicon("team%s" % self.tid)
|
||||
avatar.save(avatar_file, format="PNG")
|
||||
avatar_file.seek(0)
|
||||
response = save_file(
|
||||
avatar_file, prefix="user_avatar_", suffix=".png")
|
||||
response = save_file(avatar_file, prefix="user_avatar_", suffix=".png")
|
||||
if response.status_code == 200:
|
||||
self._avatar = response.text
|
||||
db.session.add(self)
|
||||
|
@ -623,7 +676,9 @@ class Team(db.Model):
|
|||
# @hybrid_property
|
||||
@cache.memoize(timeout=120)
|
||||
def observer(self):
|
||||
return User.query.filter(and_(User.tid == self.tid, User.level != USER_REGULAR)).count()
|
||||
return User.query.filter(
|
||||
and_(User.tid == self.tid, User.level != USER_REGULAR)
|
||||
).count()
|
||||
|
||||
# @observer.expression
|
||||
# @cache.memoize(timeout=120)
|
||||
|
@ -632,14 +687,23 @@ class Team(db.Model):
|
|||
|
||||
@hybrid_property
|
||||
def prop_points(self):
|
||||
return sum(problem.value
|
||||
for problem, solve in
|
||||
db.session.query(Problem, Solve).filter(Solve.tid == self.tid).filter(Problem.pid == Solve.tid).all())
|
||||
return sum(
|
||||
problem.value
|
||||
for problem, solve in db.session.query(Problem, Solve)
|
||||
.filter(Solve.tid == self.tid)
|
||||
.filter(Problem.pid == Solve.tid)
|
||||
.all()
|
||||
)
|
||||
|
||||
@prop_points.expression
|
||||
def prop_points(self):
|
||||
return db.session.query(Problem, Solve).filter(Solve.tid == self.tid).filter(Problem.pid == Solve.tid)\
|
||||
.with_entities(func.sum(Problem.value)).scalar()
|
||||
return (
|
||||
db.session.query(Problem, Solve)
|
||||
.filter(Solve.tid == self.tid)
|
||||
.filter(Problem.pid == Solve.tid)
|
||||
.with_entities(func.sum(Problem.value))
|
||||
.scalar()
|
||||
)
|
||||
|
||||
@cache.memoize(timeout=120)
|
||||
def points(self):
|
||||
|
@ -665,8 +729,7 @@ class Team(db.Model):
|
|||
|
||||
@hybrid_property
|
||||
def prop_last_solved(self):
|
||||
solve = Solve.query.filter_by(
|
||||
tid=self.tid).order_by(Solve.date).first()
|
||||
solve = Solve.query.filter_by(tid=self.tid).order_by(Solve.date).first()
|
||||
if not solve:
|
||||
return 0
|
||||
return solve.date
|
||||
|
@ -685,7 +748,8 @@ class Team(db.Model):
|
|||
if not problem.weightmap:
|
||||
return True
|
||||
current = sum(
|
||||
[problem.weightmap.get(solve.problem.name, 0) for solve in solves])
|
||||
[problem.weightmap.get(solve.problem.name, 0) for solve in solves]
|
||||
)
|
||||
return current >= problem.threshold
|
||||
|
||||
def get_unlocked_problems(self, admin=False, programming=None):
|
||||
|
@ -700,13 +764,17 @@ class Team(db.Model):
|
|||
def unlocked(problem):
|
||||
if not problem.weightmap:
|
||||
return True
|
||||
current = sum([problem.weightmap.get(solve.problem.name, 0) for solve in solves])
|
||||
current = sum(
|
||||
[problem.weightmap.get(solve.problem.name, 0) for solve in solves]
|
||||
)
|
||||
return current >= problem.threshold
|
||||
|
||||
return list(filter(unlocked, problems))
|
||||
|
||||
def get_jobs(self):
|
||||
return Job.query.filter_by(tid=self.tid).order_by(
|
||||
Job.completion_time.desc()).all()
|
||||
return (
|
||||
Job.query.filter_by(tid=self.tid).order_by(Job.completion_time.desc()).all()
|
||||
)
|
||||
|
||||
def has_solved(self, pid):
|
||||
return Solve.query.filter_by(tid=self.tid, pid=pid).count() > 0
|
||||
|
@ -715,31 +783,46 @@ class Team(db.Model):
|
|||
@cache.memoize(timeout=60)
|
||||
def scoreboard(cls):
|
||||
# credit: https://github.com/CTFd/CTFd/blob/master/CTFd/scoreboard.py
|
||||
uniq = db.session\
|
||||
.query(Solve.tid.label("tid"), Solve.pid.label("pid"))\
|
||||
.distinct()\
|
||||
uniq = (
|
||||
db.session.query(Solve.tid.label("tid"), Solve.pid.label("pid"))
|
||||
.distinct()
|
||||
.subquery()
|
||||
)
|
||||
# flash("uniq: " + str(uniq).replace("\n", ""), "info")
|
||||
scores = db.session\
|
||||
.query(
|
||||
scores = (
|
||||
db.session.query(
|
||||
# uniq.columns.tid.label("tid"),
|
||||
Solve.tid.label("tid"),
|
||||
db.func.max(Solve.pid).label("pid"),
|
||||
db.func.sum(Problem.value).label("score"),
|
||||
db.func.max(Solve.date).label("date"))\
|
||||
.join(Problem)\
|
||||
db.func.max(Solve.date).label("date"),
|
||||
)
|
||||
.join(Problem)
|
||||
.group_by(Solve.tid)
|
||||
)
|
||||
# flash("scores: " + str(scores).replace("\n", ""), "info")
|
||||
results = union_all(scores).alias("results")
|
||||
sumscores = db.session\
|
||||
.query(results.columns.tid, db.func.sum(results.columns.score).label("score"), db.func.max(results.columns.pid), db.func.max(results.columns.date).label("date"))\
|
||||
.group_by(results.columns.tid)\
|
||||
sumscores = (
|
||||
db.session.query(
|
||||
results.columns.tid,
|
||||
db.func.sum(results.columns.score).label("score"),
|
||||
db.func.max(results.columns.pid),
|
||||
db.func.max(results.columns.date).label("date"),
|
||||
)
|
||||
.group_by(results.columns.tid)
|
||||
.subquery()
|
||||
query = db.session\
|
||||
.query(Team, Team.tid.label("tid"), sumscores.columns.score, sumscores.columns.date)\
|
||||
.filter(Team.banned == False)\
|
||||
.join(sumscores, Team.tid == sumscores.columns.tid)\
|
||||
)
|
||||
query = (
|
||||
db.session.query(
|
||||
Team,
|
||||
Team.tid.label("tid"),
|
||||
sumscores.columns.score,
|
||||
sumscores.columns.date,
|
||||
)
|
||||
.filter(Team.banned == False)
|
||||
.join(sumscores, Team.tid == sumscores.columns.tid)
|
||||
.order_by(sumscores.columns.score.desc(), sumscores.columns.date)
|
||||
)
|
||||
# flash("full query: " + str(query).replace("\n", ""), "info")
|
||||
return query.all()
|
||||
|
||||
|
@ -780,7 +863,9 @@ class Team(db.Model):
|
|||
if not self.shell_user or not self.shell_pass:
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
client.connect(host, username="ctfadmin", pkey=private_key, look_for_keys=False)
|
||||
client.connect(
|
||||
host, username="ctfadmin", pkey=private_key, look_for_keys=False
|
||||
)
|
||||
stdin, stdout, stderr = client.exec_command("\n")
|
||||
data = stdout.read().decode("utf-8").split("\n")
|
||||
for line in data:
|
||||
|
@ -802,8 +887,12 @@ class Classroom(db.Model):
|
|||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.Unicode(64), nullable=False)
|
||||
owner = db.Column(db.Integer)
|
||||
teams = db.relationship("Team", passive_deletes=True, secondary=team_classroom, backref="teams")
|
||||
invites = db.relationship("Team", passive_deletes=True, secondary=classroom_invitation, backref="invites")
|
||||
teams = db.relationship(
|
||||
"Team", passive_deletes=True, secondary=team_classroom, backref="teams"
|
||||
)
|
||||
invites = db.relationship(
|
||||
"Team", passive_deletes=True, secondary=classroom_invitation, backref="invites"
|
||||
)
|
||||
|
||||
def __contains__(self, obj):
|
||||
if isinstance(obj, Team):
|
||||
|
@ -820,7 +909,11 @@ class Classroom(db.Model):
|
|||
|
||||
@property
|
||||
def scoreboard(self):
|
||||
return sorted(self.teams, key=lambda team: (team.points(), -team.get_last_solved()), reverse=True)
|
||||
return sorted(
|
||||
self.teams,
|
||||
key=lambda team: (team.points(), -team.get_last_solved()),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
|
||||
class Egg(db.Model):
|
||||
|
@ -848,6 +941,7 @@ class WrongEgg(db.Model):
|
|||
date = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
submission = db.Column(db.Unicode(64))
|
||||
|
||||
|
||||
# judge stuff
|
||||
|
||||
|
||||
|
@ -890,6 +984,11 @@ class GameState(db.Model):
|
|||
__tablename__ = "game_states"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
uid = db.Column(db.Integer, db.ForeignKey("users.uid"), unique=True)
|
||||
last_updated = db.Column(db.DateTime, server_default=func.now(), onupdate=func.current_timestamp(), unique=True)
|
||||
last_updated = db.Column(
|
||||
db.DateTime,
|
||||
server_default=func.now(),
|
||||
onupdate=func.current_timestamp(),
|
||||
unique=True,
|
||||
)
|
||||
|
||||
state = db.Column(db.UnicodeText, nullable=False, default="{}")
|
||||
|
|
|
@ -7,6 +7,7 @@ from flask_login import LoginManager
|
|||
from flask_sqlalchemy import SQLAlchemy
|
||||
from raven.contrib.flask import Sentry
|
||||
|
||||
|
||||
class S3Wrapper:
|
||||
def __init__(self):
|
||||
self.client = None
|
||||
|
@ -14,10 +15,11 @@ class S3Wrapper:
|
|||
def init_app(self, app):
|
||||
s3_resource = app.config.get("S3_RESOURCE")
|
||||
self.client = boto3.resource(
|
||||
's3',
|
||||
endpoint_url = s3_resource,
|
||||
"s3",
|
||||
endpoint_url=s3_resource,
|
||||
)
|
||||
|
||||
|
||||
random = SystemRandom()
|
||||
cache = Cache()
|
||||
login_manager = LoginManager()
|
||||
|
|
|
@ -25,11 +25,7 @@ def generate_short_string():
|
|||
|
||||
|
||||
def send_mail(recipient, subject, body):
|
||||
data = {
|
||||
"from": current_app.config["ADMIN_EMAIL"],
|
||||
"subject": subject,
|
||||
"html": body
|
||||
}
|
||||
data = {"from": current_app.config["ADMIN_EMAIL"], "subject": subject, "html": body}
|
||||
data["bcc" if type(recipient) == list else "to"] = recipient
|
||||
auth = ("api", current_app.config["MAILGUN_API_KEY"])
|
||||
url = "{}/messages".format(current_app.config["MAILGUN_URL"])
|
||||
|
@ -43,7 +39,8 @@ def filestore(name):
|
|||
|
||||
def save_file(file, **params):
|
||||
url = current_app.config.get(
|
||||
"FILESTORE_SAVE_ENDPOINT", "http://filestore:5001/save")
|
||||
"FILESTORE_SAVE_ENDPOINT", "http://filestore:5001/save"
|
||||
)
|
||||
return requests.post(url, data=params, files=dict(file=file))
|
||||
|
||||
|
||||
|
@ -55,14 +52,13 @@ def to_timestamp(date):
|
|||
|
||||
def to_place_str(n):
|
||||
k = n % 10
|
||||
return "%d%s" % (n, "tsnrhtdd"[(n / 10 % 10 != 1) * (k < 4) * k::4])
|
||||
return "%d%s" % (n, "tsnrhtdd"[(n / 10 % 10 != 1) * (k < 4) * k :: 4])
|
||||
|
||||
|
||||
def is_safe_url(target):
|
||||
ref_url = urlparse(request.host_url)
|
||||
test_url = urlparse(urljoin(request.host_url, target))
|
||||
return test_url.scheme in ("http", "https") and \
|
||||
ref_url.netloc == test_url.netloc
|
||||
return test_url.scheme in ("http", "https") and ref_url.netloc == test_url.netloc
|
||||
|
||||
|
||||
def get_redirect_target():
|
||||
|
@ -118,11 +114,9 @@ def generate_identicon(seed):
|
|||
s1.append(b + h % 1 * s)
|
||||
s1.append(b + s)
|
||||
|
||||
return [
|
||||
s1[~~h % 6], s1[(h | 16) % 6], s1[(h | 8) % 6]
|
||||
]
|
||||
return [s1[~~h % 6], s1[(h | 16) % 6], s1[(h | 8) % 6]]
|
||||
|
||||
rgb = hsl2rgb(int(h[-7:], 16) & 0xfffffff, 0.5, 0.7)
|
||||
rgb = hsl2rgb(int(h[-7:], 16) & 0xFFFFFFF, 0.5, 0.7)
|
||||
bg = (255, 255, 255)
|
||||
fg = (int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255))
|
||||
draw.rectangle([(0, 0), (size, size)], fill=bg)
|
||||
|
@ -130,22 +124,42 @@ def generate_identicon(seed):
|
|||
for i in range(15):
|
||||
c = bg if int(h[i], 16) % 2 == 1 else fg
|
||||
if i < 5:
|
||||
draw.rectangle([(2 * cell + margin, i * cell + margin),
|
||||
(3 * cell + margin, (i + 1) * cell + margin)],
|
||||
fill=c)
|
||||
draw.rectangle(
|
||||
[
|
||||
(2 * cell + margin, i * cell + margin),
|
||||
(3 * cell + margin, (i + 1) * cell + margin),
|
||||
],
|
||||
fill=c,
|
||||
)
|
||||
elif i < 10:
|
||||
draw.rectangle([(1 * cell + margin, (i - 5) * cell + margin),
|
||||
(2 * cell + margin, (i - 4) * cell + margin)],
|
||||
fill=c)
|
||||
draw.rectangle([(3 * cell + margin, (i - 5) * cell + margin),
|
||||
(4 * cell + margin, (i - 4) * cell + margin)],
|
||||
fill=c)
|
||||
draw.rectangle(
|
||||
[
|
||||
(1 * cell + margin, (i - 5) * cell + margin),
|
||||
(2 * cell + margin, (i - 4) * cell + margin),
|
||||
],
|
||||
fill=c,
|
||||
)
|
||||
draw.rectangle(
|
||||
[
|
||||
(3 * cell + margin, (i - 5) * cell + margin),
|
||||
(4 * cell + margin, (i - 4) * cell + margin),
|
||||
],
|
||||
fill=c,
|
||||
)
|
||||
elif i < 15:
|
||||
draw.rectangle(
|
||||
[(0 * cell + margin, (i - 10) * cell + margin),
|
||||
(1 * cell + margin, (i - 9) * cell + margin)], fill=c)
|
||||
[
|
||||
(0 * cell + margin, (i - 10) * cell + margin),
|
||||
(1 * cell + margin, (i - 9) * cell + margin),
|
||||
],
|
||||
fill=c,
|
||||
)
|
||||
draw.rectangle(
|
||||
[(4 * cell + margin, (i - 10) * cell + margin),
|
||||
(5 * cell + margin, (i - 9) * cell + margin)], fill=c)
|
||||
[
|
||||
(4 * cell + margin, (i - 10) * cell + margin),
|
||||
(5 * cell + margin, (i - 9) * cell + margin),
|
||||
],
|
||||
fill=c,
|
||||
)
|
||||
|
||||
return image
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from flask import (Blueprint, abort, flash, redirect, render_template, request,
|
||||
url_for)
|
||||
from flask import Blueprint, abort, flash, redirect, render_template, request, url_for
|
||||
from wtforms_components import read_only
|
||||
|
||||
from easyctf.decorators import admin_required
|
||||
|
@ -72,7 +71,12 @@ def problems(pid=None):
|
|||
# problem_form.generator.data = judge_problem.data['generator_code']
|
||||
else:
|
||||
problem_form.grader.data = DEFAULT_GRADER
|
||||
return render_template("admin/problems.html", current_problem=problem, problems=problems, problem_form=problem_form)
|
||||
return render_template(
|
||||
"admin/problems.html",
|
||||
current_problem=problem,
|
||||
problems=problems,
|
||||
problem_form=problem_form,
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/settings/judge/key")
|
||||
|
|
|
@ -54,7 +54,9 @@ def scoreboard():
|
|||
|
||||
@blueprint.route("/shibboleet", methods=["GET", "POST"])
|
||||
def easter():
|
||||
if not (current_user.is_authenticated and (current_user.admin or current_user.team)):
|
||||
if not (
|
||||
current_user.is_authenticated and (current_user.admin or current_user.team)
|
||||
):
|
||||
return abort(404)
|
||||
eggs = []
|
||||
if request.method == "POST":
|
||||
|
@ -69,20 +71,28 @@ def easter():
|
|||
cand = request.form.get("egg")
|
||||
egg = Egg.query.filter_by(flag=cand).first()
|
||||
if egg:
|
||||
solve = EggSolve.query.filter_by(eid=egg.eid, tid=current_user.tid).first()
|
||||
solve = EggSolve.query.filter_by(
|
||||
eid=egg.eid, tid=current_user.tid
|
||||
).first()
|
||||
if solve:
|
||||
flash("You already got this one", "info")
|
||||
else:
|
||||
solve = EggSolve(eid=egg.eid, tid=current_user.tid, uid=current_user.uid)
|
||||
solve = EggSolve(
|
||||
eid=egg.eid, tid=current_user.tid, uid=current_user.uid
|
||||
)
|
||||
db.session.add(solve)
|
||||
db.session.commit()
|
||||
flash("Congrats!", "success")
|
||||
else:
|
||||
submission = WrongEgg.query.filter_by(tid=current_user.tid, submission=cand).first()
|
||||
submission = WrongEgg.query.filter_by(
|
||||
tid=current_user.tid, submission=cand
|
||||
).first()
|
||||
if submission:
|
||||
flash("You've already tried that egg", "info")
|
||||
else:
|
||||
submission = WrongEgg(tid=current_user.tid, uid=current_user.uid, submission=cand)
|
||||
submission = WrongEgg(
|
||||
tid=current_user.tid, uid=current_user.uid, submission=cand
|
||||
)
|
||||
db.session.add(submission)
|
||||
db.session.commit()
|
||||
flash("Nope, sorry", "danger")
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
from flask import Blueprint, abort, current_app, flash, redirect, render_template, url_for
|
||||
from flask import (
|
||||
Blueprint,
|
||||
abort,
|
||||
current_app,
|
||||
flash,
|
||||
redirect,
|
||||
render_template,
|
||||
url_for,
|
||||
)
|
||||
from flask_login import current_user, login_required
|
||||
|
||||
from easyctf.decorators import block_before_competition, team_required, no_cache
|
||||
|
@ -38,7 +46,12 @@ def list():
|
|||
problems = Problem.query.filter(Problem.value > 0).order_by(Problem.value).all()
|
||||
else:
|
||||
problems = current_user.team.get_unlocked_problems()
|
||||
return render_template("chals/list.html", categories=categories, problems=problems, problem_submit_form=problem_submit_form)
|
||||
return render_template(
|
||||
"chals/list.html",
|
||||
categories=categories,
|
||||
problems=problems,
|
||||
problem_submit_form=problem_submit_form,
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/solves/<int:pid>")
|
||||
|
@ -94,15 +107,24 @@ def programming(pid=None):
|
|||
if programming_submit_form.validate_on_submit():
|
||||
if not problem.programming:
|
||||
return redirect(url_for("chals.list"))
|
||||
job = Job(uid=current_user.uid, tid=current_user.tid, pid=pid,
|
||||
language=programming_submit_form.language.data, contents=programming_submit_form.code.data)
|
||||
job = Job(
|
||||
uid=current_user.uid,
|
||||
tid=current_user.tid,
|
||||
pid=pid,
|
||||
language=programming_submit_form.language.data,
|
||||
contents=programming_submit_form.code.data,
|
||||
)
|
||||
db.session.add(job)
|
||||
db.session.commit()
|
||||
flash("Code was sent! Refresh the page for updates.", "success")
|
||||
return redirect(url_for("chals.submission", id=job.id))
|
||||
|
||||
return render_template("chals/programming.html", problem=problem,
|
||||
problems=problems, programming_submit_form=programming_submit_form)
|
||||
return render_template(
|
||||
"chals/programming.html",
|
||||
problem=problem,
|
||||
problems=problems,
|
||||
programming_submit_form=programming_submit_form,
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/programming/status")
|
||||
|
@ -110,7 +132,9 @@ def programming(pid=None):
|
|||
@team_required
|
||||
@block_before_competition
|
||||
def status():
|
||||
jobs = Job.query.filter_by(tid=current_user.tid).order_by(Job.submitted.desc()).all()
|
||||
jobs = (
|
||||
Job.query.filter_by(tid=current_user.tid).order_by(Job.submitted.desc()).all()
|
||||
)
|
||||
return render_template("chals/status.html", jobs=jobs)
|
||||
|
||||
|
||||
|
@ -124,7 +148,9 @@ def submission(id):
|
|||
return abort(404)
|
||||
if not current_user.admin and job.tid != current_user.tid:
|
||||
return abort(403)
|
||||
return render_template("chals/submission.html", problem=job.problem, job=job, user=job.user)
|
||||
return render_template(
|
||||
"chals/submission.html", problem=job.problem, job=job, user=job.user
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/autogen/<int:pid>/<filename>")
|
||||
|
@ -139,9 +165,13 @@ def autogen(pid, filename):
|
|||
|
||||
tid = current_user.tid
|
||||
# If autogen file exists in db, redirect to filestore
|
||||
autogen_file = AutogenFile.query.filter_by(pid=pid, tid=tid, filename=filename).first()
|
||||
autogen_file = AutogenFile.query.filter_by(
|
||||
pid=pid, tid=tid, filename=filename
|
||||
).first()
|
||||
if autogen_file:
|
||||
return redirect("{}/{}".format(current_app.config["FILESTORE_STATIC"], autogen_file.url))
|
||||
return redirect(
|
||||
"{}/{}".format(current_app.config["FILESTORE_STATIC"], autogen_file.url)
|
||||
)
|
||||
|
||||
current_path = os.getcwd()
|
||||
if problem.path:
|
||||
|
@ -156,6 +186,8 @@ def autogen(pid, filename):
|
|||
autogen_file = AutogenFile(pid=pid, tid=tid, filename=filename, data=data)
|
||||
db.session.add(autogen_file)
|
||||
db.session.commit()
|
||||
return redirect("{}/{}".format(current_app.config["FILESTORE_STATIC"], autogen_file.url))
|
||||
return redirect(
|
||||
"{}/{}".format(current_app.config["FILESTORE_STATIC"], autogen_file.url)
|
||||
)
|
||||
os.chdir(current_path)
|
||||
return abort(404)
|
||||
|
|
|
@ -4,8 +4,7 @@ from sqlalchemy import func
|
|||
|
||||
from easyctf.decorators import teacher_required, team_required
|
||||
from easyctf.forms.classroom import AddTeamForm, NewClassroomForm
|
||||
from easyctf.models import (Classroom, Team, classroom_invitation,
|
||||
team_classroom)
|
||||
from easyctf.models import Classroom, Team, classroom_invitation, team_classroom
|
||||
from easyctf.objects import db
|
||||
|
||||
blueprint = Blueprint("classroom", __name__)
|
||||
|
@ -24,8 +23,7 @@ def index():
|
|||
else:
|
||||
classes = current_user.team.classrooms
|
||||
invites = current_user.team.classroom_invites
|
||||
return render_template("classroom/index.html", classes=classes,
|
||||
invites=invites)
|
||||
return render_template("classroom/index.html", classes=classes, invites=invites)
|
||||
|
||||
|
||||
@blueprint.route("/new", methods=["GET", "POST"])
|
||||
|
@ -34,14 +32,12 @@ def index():
|
|||
def new():
|
||||
new_classroom_form = NewClassroomForm()
|
||||
if new_classroom_form.validate_on_submit():
|
||||
classroom = Classroom(name=new_classroom_form.name.data,
|
||||
owner=current_user.uid)
|
||||
classroom = Classroom(name=new_classroom_form.name.data, owner=current_user.uid)
|
||||
db.session.add(classroom)
|
||||
db.session.commit()
|
||||
flash("Created classroom.", "success")
|
||||
return redirect(url_for("classroom.view", id=classroom.id))
|
||||
return render_template("classroom/new.html",
|
||||
new_classroom_form=new_classroom_form)
|
||||
return render_template("classroom/new.html", new_classroom_form=new_classroom_form)
|
||||
|
||||
|
||||
@blueprint.route("/delete/<int:id>")
|
||||
|
@ -62,7 +58,8 @@ def delete(id):
|
|||
@login_required
|
||||
def accept(id):
|
||||
invitation = db.session.query(classroom_invitation).filter_by(
|
||||
team_id=current_user.tid, classroom_id=id)
|
||||
team_id=current_user.tid, classroom_id=id
|
||||
)
|
||||
if not invitation:
|
||||
abort(404)
|
||||
classroom = Classroom.query.filter_by(id=id).first()
|
||||
|
@ -101,20 +98,28 @@ def view(id):
|
|||
classroom = Classroom.query.filter_by(id=id).first()
|
||||
if not classroom:
|
||||
return redirect("classroom.index")
|
||||
if not (current_user.uid == classroom.owner or db.session.query(
|
||||
team_classroom).filter_by(team_id=current_user.tid,
|
||||
classroom_id=classroom.id).count()):
|
||||
if not (
|
||||
current_user.uid == classroom.owner
|
||||
or db.session.query(team_classroom)
|
||||
.filter_by(team_id=current_user.tid, classroom_id=classroom.id)
|
||||
.count()
|
||||
):
|
||||
abort(403)
|
||||
add_team_form = AddTeamForm(prefix="addteam")
|
||||
if add_team_form.validate_on_submit():
|
||||
if current_user.uid != classroom.owner:
|
||||
abort(403)
|
||||
team = Team.query.filter(func.lower(
|
||||
Team.teamname) == add_team_form.name.data.lower()).first()
|
||||
team = Team.query.filter(
|
||||
func.lower(Team.teamname) == add_team_form.name.data.lower()
|
||||
).first()
|
||||
classroom.invites.append(team)
|
||||
flash("Team invited.", "success")
|
||||
db.session.commit()
|
||||
return redirect(url_for("classroom.view", id=id))
|
||||
users = [user for _team in classroom.teams for user in _team.members]
|
||||
return render_template("classroom/view.html", classroom=classroom,
|
||||
users=users, add_team_form=add_team_form)
|
||||
return render_template(
|
||||
"classroom/view.html",
|
||||
classroom=classroom,
|
||||
users=users,
|
||||
add_team_form=add_team_form,
|
||||
)
|
||||
|
|
|
@ -2,7 +2,16 @@ import json
|
|||
import os
|
||||
from functools import wraps
|
||||
|
||||
from flask import Blueprint, abort, current_app, flash, make_response, render_template, request, url_for
|
||||
from flask import (
|
||||
Blueprint,
|
||||
abort,
|
||||
current_app,
|
||||
flash,
|
||||
make_response,
|
||||
render_template,
|
||||
request,
|
||||
url_for,
|
||||
)
|
||||
from flask_login import current_user, login_required
|
||||
|
||||
from easyctf.decorators import block_before_competition, team_required
|
||||
|
@ -18,7 +27,12 @@ def api_view(f):
|
|||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
status, result = f(*args, **kwargs)
|
||||
return make_response(json.dumps(result or dict()), status, {"Content-Type": "application/json; charset=utf-8"})
|
||||
return make_response(
|
||||
json.dumps(result or dict()),
|
||||
status,
|
||||
{"Content-Type": "application/json; charset=utf-8"},
|
||||
)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
|
@ -75,7 +89,9 @@ def game_state_get():
|
|||
# TODO: proper upserting
|
||||
db.session.add(game_state)
|
||||
db.session.commit()
|
||||
return make_response(game_state.state, 200, {"Content-Type": "application/json; charset=utf-8"})
|
||||
return make_response(
|
||||
game_state.state, 200, {"Content-Type": "application/json; charset=utf-8"}
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/state/update", methods=["POST"])
|
||||
|
|
|
@ -23,7 +23,12 @@ def api_view(f):
|
|||
if not key:
|
||||
return abort(403)
|
||||
status, result = f(*args, **kwargs)
|
||||
return make_response(json.dumps(result or dict()), status, {"Content-Type": "application/json; charset=utf-8"})
|
||||
return make_response(
|
||||
json.dumps(result or dict()),
|
||||
status,
|
||||
{"Content-Type": "application/json; charset=utf-8"},
|
||||
)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
|
@ -32,7 +37,19 @@ def api_view(f):
|
|||
def jobs():
|
||||
if request.method == "GET":
|
||||
# implement language preference later
|
||||
available = Job.query.filter(or_(Job.status == 0, and_(Job.status == 1, Job.claimed < datetime.utcnow() - timedelta(minutes=5)))).order_by(Job.submitted).first()
|
||||
available = (
|
||||
Job.query.filter(
|
||||
or_(
|
||||
Job.status == 0,
|
||||
and_(
|
||||
Job.status == 1,
|
||||
Job.claimed < datetime.utcnow() - timedelta(minutes=5),
|
||||
),
|
||||
)
|
||||
)
|
||||
.order_by(Job.submitted)
|
||||
.first()
|
||||
)
|
||||
if not available:
|
||||
return 204, []
|
||||
# assign job to current judge
|
||||
|
@ -75,7 +92,9 @@ def jobs():
|
|||
if job.verdict == "AC":
|
||||
solve = Solve.query.filter_by(pid=job.pid, tid=job.tid).first()
|
||||
if not solve:
|
||||
solve = Solve(pid=job.pid, uid=job.uid, tid=job.tid, _date=job.completed)
|
||||
solve = Solve(
|
||||
pid=job.pid, uid=job.uid, tid=job.tid, _date=job.completed
|
||||
)
|
||||
db.session.add(solve)
|
||||
db.session.commit()
|
||||
return 202, None
|
||||
|
|
|
@ -29,11 +29,15 @@ def cancel(id):
|
|||
target_user = User.get_by_id(id)
|
||||
try:
|
||||
assert target_user != None, "User not found."
|
||||
assert target_user in current_team.outgoing_invitations, "No invitation for this user found."
|
||||
assert (
|
||||
target_user in current_team.outgoing_invitations
|
||||
), "No invitation for this user found."
|
||||
current_team.outgoing_invitations.remove(target_user)
|
||||
db.session.add(current_team)
|
||||
db.session.commit()
|
||||
flash("Invitation to %s successfully withdrawn." % target_user.username, "success")
|
||||
flash(
|
||||
"Invitation to %s successfully withdrawn." % target_user.username, "success"
|
||||
)
|
||||
except AssertionError as e:
|
||||
flash(str(e), "danger")
|
||||
return redirect(url_for("teams.settings"))
|
||||
|
@ -113,7 +117,9 @@ def settings():
|
|||
f = BytesIO(field.data.read())
|
||||
new_avatar = sanitize_avatar(f)
|
||||
if new_avatar:
|
||||
response = save_file(new_avatar, prefix="team_avatar", suffix=".png")
|
||||
response = save_file(
|
||||
new_avatar, prefix="team_avatar", suffix=".png"
|
||||
)
|
||||
if response.status_code == 200:
|
||||
current_team._avatar = response.text
|
||||
continue
|
||||
|
@ -129,7 +135,12 @@ def settings():
|
|||
for field in profile_edit_form:
|
||||
if hasattr(current_team, field.short_name):
|
||||
field.data = getattr(current_team, field.short_name, "")
|
||||
return render_template("teams/settings.html", team=current_team, profile_edit_form=profile_edit_form, add_member_form=add_member_form)
|
||||
return render_template(
|
||||
"teams/settings.html",
|
||||
team=current_team,
|
||||
profile_edit_form=profile_edit_form,
|
||||
add_member_form=add_member_form,
|
||||
)
|
||||
|
||||
|
||||
def create_team(form):
|
||||
|
|
|
@ -4,21 +4,34 @@ from io import BytesIO
|
|||
from string import Template
|
||||
|
||||
import pyqrcode
|
||||
from flask import (Blueprint, abort, flash, redirect, render_template, request,
|
||||
url_for)
|
||||
from flask import Blueprint, abort, flash, redirect, render_template, request, url_for
|
||||
from flask_login import current_user, login_required, login_user, logout_user
|
||||
from sqlalchemy import func
|
||||
|
||||
from easyctf.constants import (FORGOT_EMAIL_TEMPLATE,
|
||||
REGISTRATION_EMAIL_TEMPLATE, USER_LEVELS)
|
||||
from easyctf.forms.users import (ChangeLoginForm, LoginForm,
|
||||
PasswordForgotForm, PasswordResetForm,
|
||||
ProfileEditForm, RegisterForm,
|
||||
TwoFactorAuthSetupForm)
|
||||
from easyctf.constants import (
|
||||
FORGOT_EMAIL_TEMPLATE,
|
||||
REGISTRATION_EMAIL_TEMPLATE,
|
||||
USER_LEVELS,
|
||||
)
|
||||
from easyctf.forms.users import (
|
||||
ChangeLoginForm,
|
||||
LoginForm,
|
||||
PasswordForgotForm,
|
||||
PasswordResetForm,
|
||||
ProfileEditForm,
|
||||
RegisterForm,
|
||||
TwoFactorAuthSetupForm,
|
||||
)
|
||||
from easyctf.models import Config, PasswordResetToken, Team, User
|
||||
from easyctf.objects import db, sentry
|
||||
from easyctf.utils import (generate_string, get_redirect_target, redirect_back,
|
||||
sanitize_avatar, save_file, send_mail)
|
||||
from easyctf.utils import (
|
||||
generate_string,
|
||||
get_redirect_target,
|
||||
redirect_back,
|
||||
sanitize_avatar,
|
||||
save_file,
|
||||
send_mail,
|
||||
)
|
||||
|
||||
blueprint = Blueprint("users", __name__, template_folder="templates")
|
||||
|
||||
|
@ -30,9 +43,13 @@ def accept(id):
|
|||
max_size = Config.get_team_size()
|
||||
try:
|
||||
assert not current_user.tid, "You're already in a team!"
|
||||
assert current_user in target_team.outgoing_invitations, "There is no invitation for you!"
|
||||
assert (
|
||||
current_user in target_team.outgoing_invitations
|
||||
), "There is no invitation for you!"
|
||||
if not target_team.admin:
|
||||
assert target_team.size < max_size, "This team has already reached the maximum member limit!"
|
||||
assert (
|
||||
target_team.size < max_size
|
||||
), "This team has already reached the maximum member limit!"
|
||||
target_team.outgoing_invitations.remove(current_user)
|
||||
target_team.members.append(current_user)
|
||||
db.session.add(current_user)
|
||||
|
@ -50,12 +67,21 @@ def forgot():
|
|||
forgot_form = PasswordForgotForm()
|
||||
if forgot_form.validate_on_submit():
|
||||
if forgot_form.user is not None:
|
||||
token = PasswordResetToken(active=True, uid=forgot_form.user.uid, email=forgot_form.email.data, expire=datetime.utcnow() + timedelta(days=1))
|
||||
token = PasswordResetToken(
|
||||
active=True,
|
||||
uid=forgot_form.user.uid,
|
||||
email=forgot_form.email.data,
|
||||
expire=datetime.utcnow() + timedelta(days=1),
|
||||
)
|
||||
db.session.add(token)
|
||||
db.session.commit()
|
||||
url = url_for("users.reset", code=token.token, _external=True)
|
||||
# TODO: stick this into the template
|
||||
send_mail(forgot_form.email.data, "%s Password Reset" % Config.get("ctf_name"), "Click here to reset your password: %s" % url)
|
||||
send_mail(
|
||||
forgot_form.email.data,
|
||||
"%s Password Reset" % Config.get("ctf_name"),
|
||||
"Click here to reset your password: %s" % url,
|
||||
)
|
||||
flash("Sent! Check your email.", "success")
|
||||
return redirect(url_for("users.forgot"))
|
||||
return render_template("users/forgot.html", forgot_form=forgot_form)
|
||||
|
@ -87,14 +113,22 @@ def login():
|
|||
next = get_redirect_target()
|
||||
if login_form.validate_on_submit():
|
||||
target_user = login_form.get_user()
|
||||
if target_user.otp_confirmed and not target_user.verify_totp(login_form.code.data):
|
||||
if target_user.otp_confirmed and not target_user.verify_totp(
|
||||
login_form.code.data
|
||||
):
|
||||
flash("Invalid code.", "danger")
|
||||
return render_template("users/login.html", login_form=login_form, next=next)
|
||||
|
||||
login_user(target_user, remember=login_form.remember.data)
|
||||
flash("Successfully logged in as %s!" % target_user.username, "success")
|
||||
if sentry.client:
|
||||
sentry.client.capture_breadcrumb(message="login", category="user:login", level="info", data=dict(uid=target_user.uid, username=target_user.username), timestamp=datetime.now())
|
||||
sentry.client.capture_breadcrumb(
|
||||
message="login",
|
||||
category="user:login",
|
||||
level="info",
|
||||
data=dict(uid=target_user.uid, username=target_user.username),
|
||||
timestamp=datetime.now(),
|
||||
)
|
||||
return redirect_back("users.profile")
|
||||
return render_template("users/login.html", login_form=login_form, next=next)
|
||||
|
||||
|
@ -126,11 +160,14 @@ def register():
|
|||
return redirect(url_for("users.profile", uid=current_user.uid))
|
||||
register_form = RegisterForm(prefix="register")
|
||||
if register_form.validate_on_submit():
|
||||
new_user = register_user(register_form.name.data,
|
||||
register_form.email.data,
|
||||
register_form.username.data,
|
||||
register_form.password.data,
|
||||
int(register_form.level.data), admin=False)
|
||||
new_user = register_user(
|
||||
register_form.name.data,
|
||||
register_form.email.data,
|
||||
register_form.username.data,
|
||||
register_form.password.data,
|
||||
int(register_form.level.data),
|
||||
admin=False,
|
||||
)
|
||||
login_user(new_user)
|
||||
return redirect(url_for("users.profile"))
|
||||
return render_template("users/register.html", register_form=register_form)
|
||||
|
@ -164,7 +201,9 @@ def settings():
|
|||
f = BytesIO(field.data.read())
|
||||
new_avatar = sanitize_avatar(f)
|
||||
if new_avatar:
|
||||
response = save_file(new_avatar, prefix="user_avatar", suffix=".png")
|
||||
response = save_file(
|
||||
new_avatar, prefix="user_avatar", suffix=".png"
|
||||
)
|
||||
if response.status_code == 200:
|
||||
current_user._avatar = response.text
|
||||
continue
|
||||
|
@ -181,7 +220,11 @@ def settings():
|
|||
for field in profile_edit_form:
|
||||
if hasattr(current_user, field.short_name):
|
||||
field.data = getattr(current_user, field.short_name, "")
|
||||
return render_template("users/settings.html", change_login_form=change_login_form, profile_edit_form=profile_edit_form)
|
||||
return render_template(
|
||||
"users/settings.html",
|
||||
change_login_form=change_login_form,
|
||||
profile_edit_form=profile_edit_form,
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/two_factor/required")
|
||||
|
@ -204,7 +247,9 @@ def two_factor_setup():
|
|||
db.session.commit()
|
||||
flash("Two-factor authentication setup is complete.", "success")
|
||||
return redirect(url_for("users.settings"))
|
||||
return render_template("users/two_factor/setup.html", two_factor_form=two_factor_form)
|
||||
return render_template(
|
||||
"users/two_factor/setup.html", two_factor_form=two_factor_form
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/two_factor/qr")
|
||||
|
@ -213,13 +258,17 @@ def two_factor_qr():
|
|||
url = pyqrcode.create(current_user.get_totp_uri())
|
||||
stream = BytesIO()
|
||||
url.svg(stream, scale=6)
|
||||
return stream.getvalue(), 200, {
|
||||
"Content-Type": "image/svg+xml",
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
"Pragma": "no-cache",
|
||||
"Expires": 0,
|
||||
"Secret": current_user.otp_secret
|
||||
}
|
||||
return (
|
||||
stream.getvalue(),
|
||||
200,
|
||||
{
|
||||
"Content-Type": "image/svg+xml",
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
"Pragma": "no-cache",
|
||||
"Expires": 0,
|
||||
"Secret": current_user.otp_secret,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/two_factor/disable")
|
||||
|
@ -258,7 +307,9 @@ def verify_email():
|
|||
db.session.commit()
|
||||
try:
|
||||
link = url_for("users.verify", code=code, _external=True)
|
||||
response = send_verification_email(current_user.username, current_user.email, link)
|
||||
response = send_verification_email(
|
||||
current_user.username, current_user.email, link
|
||||
)
|
||||
if response.status_code // 100 != 2:
|
||||
return "failed"
|
||||
return "success"
|
||||
|
@ -268,15 +319,21 @@ def verify_email():
|
|||
|
||||
def send_verification_email(username, email, verification_link):
|
||||
subject = "[ACTION REQUIRED] EasyCTF Email Verification"
|
||||
body = Template(REGISTRATION_EMAIL_TEMPLATE).substitute({
|
||||
"link": verification_link,
|
||||
"username": username
|
||||
})
|
||||
body = Template(REGISTRATION_EMAIL_TEMPLATE).substitute(
|
||||
{"link": verification_link, "username": username}
|
||||
)
|
||||
return send_mail(email, subject, body)
|
||||
|
||||
|
||||
def register_user(name, email, username, password, level, admin=False, **kwargs):
|
||||
new_user = User(name=name, username=username, password=password, email=email, level=level, admin=admin)
|
||||
new_user = User(
|
||||
name=name,
|
||||
username=username,
|
||||
password=password,
|
||||
email=email,
|
||||
level=level,
|
||||
admin=admin,
|
||||
)
|
||||
for key, value in list(kwargs.items()):
|
||||
setattr(new_user, key, value)
|
||||
code = generate_string()
|
||||
|
|
|
@ -11,16 +11,18 @@ config = context.config
|
|||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
fileConfig(config.config_file_name)
|
||||
logger = logging.getLogger('alembic.env')
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
from flask import current_app
|
||||
config.set_main_option('sqlalchemy.url',
|
||||
current_app.config.get('SQLALCHEMY_DATABASE_URI'))
|
||||
target_metadata = current_app.extensions['migrate'].db.metadata
|
||||
|
||||
config.set_main_option(
|
||||
"sqlalchemy.url", current_app.config.get("SQLALCHEMY_DATABASE_URI")
|
||||
)
|
||||
target_metadata = current_app.extensions["migrate"].db.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
|
@ -59,21 +61,25 @@ def run_migrations_online():
|
|||
# when there are no changes to the schema
|
||||
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||
def process_revision_directives(context, revision, directives):
|
||||
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||
if getattr(config.cmd_opts, "autogenerate", False):
|
||||
script = directives[0]
|
||||
if script.upgrade_ops.is_empty():
|
||||
directives[:] = []
|
||||
logger.info('No changes in schema detected.')
|
||||
logger.info("No changes in schema detected.")
|
||||
|
||||
engine = engine_from_config(config.get_section(config.config_ini_section),
|
||||
prefix='sqlalchemy.',
|
||||
poolclass=pool.NullPool)
|
||||
engine = engine_from_config(
|
||||
config.get_section(config.config_ini_section),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
connection = engine.connect()
|
||||
context.configure(connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
process_revision_directives=process_revision_directives,
|
||||
**current_app.extensions['migrate'].configure_args)
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
process_revision_directives=process_revision_directives,
|
||||
**current_app.extensions["migrate"].configure_args
|
||||
)
|
||||
|
||||
try:
|
||||
with context.begin_transaction():
|
||||
|
@ -81,6 +87,7 @@ def run_migrations_online():
|
|||
finally:
|
||||
connection.close()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
|
|
|
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
|||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '59f8fa2f0c98'
|
||||
revision = "59f8fa2f0c98"
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
@ -18,310 +18,434 @@ depends_on = None
|
|||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('classrooms',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.Unicode(length=64), nullable=False),
|
||||
sa.Column('owner', sa.Integer(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
op.create_table(
|
||||
"classrooms",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.Unicode(length=64), nullable=False),
|
||||
sa.Column("owner", sa.Integer(), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table('config',
|
||||
sa.Column('cid', sa.Integer(), nullable=False),
|
||||
sa.Column('key', sa.Unicode(length=32), nullable=True),
|
||||
sa.Column('value', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('cid')
|
||||
op.create_table(
|
||||
"config",
|
||||
sa.Column("cid", sa.Integer(), nullable=False),
|
||||
sa.Column("key", sa.Unicode(length=32), nullable=True),
|
||||
sa.Column("value", sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint("cid"),
|
||||
)
|
||||
op.create_index(op.f('ix_config_key'), 'config', ['key'], unique=False)
|
||||
op.create_table('eggs',
|
||||
sa.Column('eid', sa.Integer(), nullable=False),
|
||||
sa.Column('flag', sa.Unicode(length=64), nullable=False),
|
||||
sa.PrimaryKeyConstraint('eid')
|
||||
op.create_index(op.f("ix_config_key"), "config", ["key"], unique=False)
|
||||
op.create_table(
|
||||
"eggs",
|
||||
sa.Column("eid", sa.Integer(), nullable=False),
|
||||
sa.Column("flag", sa.Unicode(length=64), nullable=False),
|
||||
sa.PrimaryKeyConstraint("eid"),
|
||||
)
|
||||
op.create_index(op.f('ix_eggs_flag'), 'eggs', ['flag'], unique=True)
|
||||
op.create_table('judge_api_keys',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('key', sa.String(length=64), nullable=True),
|
||||
sa.Column('ip', sa.Integer(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
op.create_index(op.f("ix_eggs_flag"), "eggs", ["flag"], unique=True)
|
||||
op.create_table(
|
||||
"judge_api_keys",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("key", sa.String(length=64), nullable=True),
|
||||
sa.Column("ip", sa.Integer(), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f('ix_judge_api_keys_key'), 'judge_api_keys', ['key'], unique=False)
|
||||
op.create_table('problems',
|
||||
sa.Column('pid', sa.Integer(), nullable=False),
|
||||
sa.Column('author', sa.Unicode(length=32), nullable=True),
|
||||
sa.Column('name', sa.String(length=32), nullable=True),
|
||||
sa.Column('title', sa.Unicode(length=64), nullable=True),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('hint', sa.Text(), nullable=True),
|
||||
sa.Column('category', sa.Unicode(length=64), nullable=True),
|
||||
sa.Column('value', sa.Integer(), nullable=True),
|
||||
sa.Column('grader', sa.UnicodeText(), nullable=True),
|
||||
sa.Column('autogen', sa.Boolean(), nullable=True),
|
||||
sa.Column('programming', sa.Boolean(), nullable=True),
|
||||
sa.Column('threshold', sa.Integer(), nullable=True),
|
||||
sa.Column('weightmap', sa.PickleType(), nullable=True),
|
||||
sa.Column('test_cases', sa.Integer(), nullable=True),
|
||||
sa.Column('time_limit', sa.Integer(), nullable=True),
|
||||
sa.Column('memory_limit', sa.Integer(), nullable=True),
|
||||
sa.Column('generator', sa.Text(), nullable=True),
|
||||
sa.Column('source_verifier', sa.Text(), nullable=True),
|
||||
sa.Column('path', sa.String(length=128), nullable=True),
|
||||
sa.PrimaryKeyConstraint('pid'),
|
||||
sa.UniqueConstraint('name')
|
||||
op.create_index(
|
||||
op.f("ix_judge_api_keys_key"), "judge_api_keys", ["key"], unique=False
|
||||
)
|
||||
op.create_index(op.f('ix_problems_pid'), 'problems', ['pid'], unique=False)
|
||||
op.create_table('teams',
|
||||
sa.Column('tid', sa.Integer(), nullable=False),
|
||||
sa.Column('teamname', sa.Unicode(length=32), nullable=True),
|
||||
sa.Column('school', sa.Unicode(length=64), nullable=True),
|
||||
sa.Column('owner', sa.Integer(), nullable=True),
|
||||
sa.Column('admin', sa.Boolean(), nullable=True),
|
||||
sa.Column('shell_user', sa.String(length=16), nullable=True),
|
||||
sa.Column('shell_pass', sa.String(length=32), nullable=True),
|
||||
sa.Column('banned', sa.Boolean(), nullable=True),
|
||||
sa.Column('avatar', sa.String(length=128), nullable=True),
|
||||
sa.PrimaryKeyConstraint('tid'),
|
||||
sa.UniqueConstraint('shell_user'),
|
||||
sa.UniqueConstraint('teamname')
|
||||
op.create_table(
|
||||
"problems",
|
||||
sa.Column("pid", sa.Integer(), nullable=False),
|
||||
sa.Column("author", sa.Unicode(length=32), nullable=True),
|
||||
sa.Column("name", sa.String(length=32), nullable=True),
|
||||
sa.Column("title", sa.Unicode(length=64), nullable=True),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("hint", sa.Text(), nullable=True),
|
||||
sa.Column("category", sa.Unicode(length=64), nullable=True),
|
||||
sa.Column("value", sa.Integer(), nullable=True),
|
||||
sa.Column("grader", sa.UnicodeText(), nullable=True),
|
||||
sa.Column("autogen", sa.Boolean(), nullable=True),
|
||||
sa.Column("programming", sa.Boolean(), nullable=True),
|
||||
sa.Column("threshold", sa.Integer(), nullable=True),
|
||||
sa.Column("weightmap", sa.PickleType(), nullable=True),
|
||||
sa.Column("test_cases", sa.Integer(), nullable=True),
|
||||
sa.Column("time_limit", sa.Integer(), nullable=True),
|
||||
sa.Column("memory_limit", sa.Integer(), nullable=True),
|
||||
sa.Column("generator", sa.Text(), nullable=True),
|
||||
sa.Column("source_verifier", sa.Text(), nullable=True),
|
||||
sa.Column("path", sa.String(length=128), nullable=True),
|
||||
sa.PrimaryKeyConstraint("pid"),
|
||||
sa.UniqueConstraint("name"),
|
||||
)
|
||||
op.create_index(op.f('ix_teams_tid'), 'teams', ['tid'], unique=False)
|
||||
op.create_table('autogen_files',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pid', sa.Integer(), nullable=True),
|
||||
sa.Column('tid', sa.Integer(), nullable=True),
|
||||
sa.Column('filename', sa.Unicode(length=64), nullable=True),
|
||||
sa.Column('url', sa.String(length=128), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pid'], ['problems.pid'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
op.create_index(op.f("ix_problems_pid"), "problems", ["pid"], unique=False)
|
||||
op.create_table(
|
||||
"teams",
|
||||
sa.Column("tid", sa.Integer(), nullable=False),
|
||||
sa.Column("teamname", sa.Unicode(length=32), nullable=True),
|
||||
sa.Column("school", sa.Unicode(length=64), nullable=True),
|
||||
sa.Column("owner", sa.Integer(), nullable=True),
|
||||
sa.Column("admin", sa.Boolean(), nullable=True),
|
||||
sa.Column("shell_user", sa.String(length=16), nullable=True),
|
||||
sa.Column("shell_pass", sa.String(length=32), nullable=True),
|
||||
sa.Column("banned", sa.Boolean(), nullable=True),
|
||||
sa.Column("avatar", sa.String(length=128), nullable=True),
|
||||
sa.PrimaryKeyConstraint("tid"),
|
||||
sa.UniqueConstraint("shell_user"),
|
||||
sa.UniqueConstraint("teamname"),
|
||||
)
|
||||
op.create_index(op.f('ix_autogen_files_filename'), 'autogen_files', ['filename'], unique=False)
|
||||
op.create_index(op.f('ix_autogen_files_id'), 'autogen_files', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_autogen_files_pid'), 'autogen_files', ['pid'], unique=False)
|
||||
op.create_index(op.f('ix_autogen_files_tid'), 'autogen_files', ['tid'], unique=False)
|
||||
op.create_table('classroom_invitation',
|
||||
sa.Column('team_id', sa.Integer(), nullable=False),
|
||||
sa.Column('classroom_id', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['classroom_id'], ['classrooms.id'], ),
|
||||
sa.ForeignKeyConstraint(['team_id'], ['teams.tid'], ),
|
||||
sa.PrimaryKeyConstraint('team_id', 'classroom_id')
|
||||
op.create_index(op.f("ix_teams_tid"), "teams", ["tid"], unique=False)
|
||||
op.create_table(
|
||||
"autogen_files",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("pid", sa.Integer(), nullable=True),
|
||||
sa.Column("tid", sa.Integer(), nullable=True),
|
||||
sa.Column("filename", sa.Unicode(length=64), nullable=True),
|
||||
sa.Column("url", sa.String(length=128), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["pid"],
|
||||
["problems.pid"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table('files',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pid', sa.Integer(), nullable=True),
|
||||
sa.Column('filename', sa.Unicode(length=64), nullable=True),
|
||||
sa.Column('url', sa.String(length=128), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pid'], ['problems.pid'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
op.create_index(
|
||||
op.f("ix_autogen_files_filename"), "autogen_files", ["filename"], unique=False
|
||||
)
|
||||
op.create_index(op.f('ix_files_id'), 'files', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_files_pid'), 'files', ['pid'], unique=False)
|
||||
op.create_table('team_classroom',
|
||||
sa.Column('team_id', sa.Integer(), nullable=False),
|
||||
sa.Column('classroom_id', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['classroom_id'], ['classrooms.id'], ),
|
||||
sa.ForeignKeyConstraint(['team_id'], ['teams.tid'], ),
|
||||
sa.PrimaryKeyConstraint('team_id', 'classroom_id')
|
||||
op.create_index(op.f("ix_autogen_files_id"), "autogen_files", ["id"], unique=False)
|
||||
op.create_index(
|
||||
op.f("ix_autogen_files_pid"), "autogen_files", ["pid"], unique=False
|
||||
)
|
||||
op.create_table('users',
|
||||
sa.Column('uid', sa.Integer(), nullable=False),
|
||||
sa.Column('tid', sa.Integer(), nullable=True),
|
||||
sa.Column('name', sa.Unicode(length=32), nullable=True),
|
||||
sa.Column('easyctf', sa.Boolean(), nullable=True),
|
||||
sa.Column('username', sa.String(length=16), nullable=True),
|
||||
sa.Column('email', sa.String(length=128), nullable=True),
|
||||
sa.Column('password', sa.String(length=128), nullable=True),
|
||||
sa.Column('admin', sa.Boolean(), nullable=True),
|
||||
sa.Column('level', sa.Integer(), nullable=True),
|
||||
sa.Column('register_time', sa.DateTime(), nullable=True),
|
||||
sa.Column('reset_token', sa.String(length=32), nullable=True),
|
||||
sa.Column('otp_secret', sa.String(length=16), nullable=True),
|
||||
sa.Column('otp_confirmed', sa.Boolean(), nullable=True),
|
||||
sa.Column('email_token', sa.String(length=32), nullable=True),
|
||||
sa.Column('email_verified', sa.Boolean(), nullable=True),
|
||||
sa.Column('avatar', sa.String(length=128), nullable=True),
|
||||
sa.ForeignKeyConstraint(['tid'], ['teams.tid'], ),
|
||||
sa.PrimaryKeyConstraint('uid'),
|
||||
sa.UniqueConstraint('email')
|
||||
op.create_index(
|
||||
op.f("ix_autogen_files_tid"), "autogen_files", ["tid"], unique=False
|
||||
)
|
||||
op.create_index(op.f('ix_users_easyctf'), 'users', ['easyctf'], unique=False)
|
||||
op.create_index(op.f('ix_users_uid'), 'users', ['uid'], unique=False)
|
||||
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
|
||||
op.create_table('egg_solves',
|
||||
sa.Column('sid', sa.Integer(), nullable=False),
|
||||
sa.Column('eid', sa.Integer(), nullable=True),
|
||||
sa.Column('tid', sa.Integer(), nullable=True),
|
||||
sa.Column('uid', sa.Integer(), nullable=True),
|
||||
sa.Column('date', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['eid'], ['eggs.eid'], ),
|
||||
sa.ForeignKeyConstraint(['tid'], ['teams.tid'], ),
|
||||
sa.ForeignKeyConstraint(['uid'], ['users.uid'], ),
|
||||
sa.PrimaryKeyConstraint('sid')
|
||||
op.create_table(
|
||||
"classroom_invitation",
|
||||
sa.Column("team_id", sa.Integer(), nullable=False),
|
||||
sa.Column("classroom_id", sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["classroom_id"],
|
||||
["classrooms.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["team_id"],
|
||||
["teams.tid"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("team_id", "classroom_id"),
|
||||
)
|
||||
op.create_index(op.f('ix_egg_solves_eid'), 'egg_solves', ['eid'], unique=False)
|
||||
op.create_index(op.f('ix_egg_solves_tid'), 'egg_solves', ['tid'], unique=False)
|
||||
op.create_index(op.f('ix_egg_solves_uid'), 'egg_solves', ['uid'], unique=False)
|
||||
op.create_table('game_states',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uid', sa.Integer(), nullable=True),
|
||||
sa.Column('last_updated', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
|
||||
sa.Column('state', sa.UnicodeText(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['uid'], ['users.uid'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('last_updated'),
|
||||
sa.UniqueConstraint('uid')
|
||||
op.create_table(
|
||||
"files",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("pid", sa.Integer(), nullable=True),
|
||||
sa.Column("filename", sa.Unicode(length=64), nullable=True),
|
||||
sa.Column("url", sa.String(length=128), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["pid"],
|
||||
["problems.pid"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table('jobs',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pid', sa.Integer(), nullable=True),
|
||||
sa.Column('tid', sa.Integer(), nullable=True),
|
||||
sa.Column('uid', sa.Integer(), nullable=True),
|
||||
sa.Column('submitted', sa.DateTime(), nullable=True),
|
||||
sa.Column('claimed', sa.DateTime(), nullable=True),
|
||||
sa.Column('completed', sa.DateTime(), nullable=True),
|
||||
sa.Column('execution_time', sa.Float(), nullable=True),
|
||||
sa.Column('execution_memory', sa.Float(), nullable=True),
|
||||
sa.Column('language', sa.String(length=16), nullable=False),
|
||||
sa.Column('contents', sa.Text(), nullable=False),
|
||||
sa.Column('feedback', sa.Text(), nullable=True),
|
||||
sa.Column('status', sa.Integer(), nullable=False),
|
||||
sa.Column('verdict', sa.String(length=8), nullable=True),
|
||||
sa.Column('last_ran_case', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pid'], ['problems.pid'], ),
|
||||
sa.ForeignKeyConstraint(['tid'], ['teams.tid'], ),
|
||||
sa.ForeignKeyConstraint(['uid'], ['users.uid'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
op.create_index(op.f("ix_files_id"), "files", ["id"], unique=False)
|
||||
op.create_index(op.f("ix_files_pid"), "files", ["pid"], unique=False)
|
||||
op.create_table(
|
||||
"team_classroom",
|
||||
sa.Column("team_id", sa.Integer(), nullable=False),
|
||||
sa.Column("classroom_id", sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["classroom_id"],
|
||||
["classrooms.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["team_id"],
|
||||
["teams.tid"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("team_id", "classroom_id"),
|
||||
)
|
||||
op.create_index(op.f('ix_jobs_pid'), 'jobs', ['pid'], unique=False)
|
||||
op.create_index(op.f('ix_jobs_status'), 'jobs', ['status'], unique=False)
|
||||
op.create_index(op.f('ix_jobs_tid'), 'jobs', ['tid'], unique=False)
|
||||
op.create_index(op.f('ix_jobs_uid'), 'jobs', ['uid'], unique=False)
|
||||
op.create_table('password_reset_tokens',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uid', sa.Integer(), nullable=True),
|
||||
sa.Column('active', sa.Boolean(), nullable=True),
|
||||
sa.Column('token', sa.String(length=32), nullable=True),
|
||||
sa.Column('email', sa.Unicode(length=128), nullable=True),
|
||||
sa.Column('expire', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['uid'], ['users.uid'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
op.create_table(
|
||||
"users",
|
||||
sa.Column("uid", sa.Integer(), nullable=False),
|
||||
sa.Column("tid", sa.Integer(), nullable=True),
|
||||
sa.Column("name", sa.Unicode(length=32), nullable=True),
|
||||
sa.Column("easyctf", sa.Boolean(), nullable=True),
|
||||
sa.Column("username", sa.String(length=16), nullable=True),
|
||||
sa.Column("email", sa.String(length=128), nullable=True),
|
||||
sa.Column("password", sa.String(length=128), nullable=True),
|
||||
sa.Column("admin", sa.Boolean(), nullable=True),
|
||||
sa.Column("level", sa.Integer(), nullable=True),
|
||||
sa.Column("register_time", sa.DateTime(), nullable=True),
|
||||
sa.Column("reset_token", sa.String(length=32), nullable=True),
|
||||
sa.Column("otp_secret", sa.String(length=16), nullable=True),
|
||||
sa.Column("otp_confirmed", sa.Boolean(), nullable=True),
|
||||
sa.Column("email_token", sa.String(length=32), nullable=True),
|
||||
sa.Column("email_verified", sa.Boolean(), nullable=True),
|
||||
sa.Column("avatar", sa.String(length=128), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["tid"],
|
||||
["teams.tid"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("uid"),
|
||||
sa.UniqueConstraint("email"),
|
||||
)
|
||||
op.create_index(op.f('ix_password_reset_tokens_uid'), 'password_reset_tokens', ['uid'], unique=False)
|
||||
op.create_table('player_team_invitation',
|
||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('team_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['team_id'], ['teams.tid'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.uid'], )
|
||||
op.create_index(op.f("ix_users_easyctf"), "users", ["easyctf"], unique=False)
|
||||
op.create_index(op.f("ix_users_uid"), "users", ["uid"], unique=False)
|
||||
op.create_index(op.f("ix_users_username"), "users", ["username"], unique=True)
|
||||
op.create_table(
|
||||
"egg_solves",
|
||||
sa.Column("sid", sa.Integer(), nullable=False),
|
||||
sa.Column("eid", sa.Integer(), nullable=True),
|
||||
sa.Column("tid", sa.Integer(), nullable=True),
|
||||
sa.Column("uid", sa.Integer(), nullable=True),
|
||||
sa.Column("date", sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["eid"],
|
||||
["eggs.eid"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["tid"],
|
||||
["teams.tid"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["uid"],
|
||||
["users.uid"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("sid"),
|
||||
)
|
||||
op.create_table('solves',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pid', sa.Integer(), nullable=True),
|
||||
sa.Column('tid', sa.Integer(), nullable=True),
|
||||
sa.Column('uid', sa.Integer(), nullable=True),
|
||||
sa.Column('date', sa.DateTime(), nullable=True),
|
||||
sa.Column('flag', sa.Unicode(length=256), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pid'], ['problems.pid'], ),
|
||||
sa.ForeignKeyConstraint(['tid'], ['teams.tid'], ),
|
||||
sa.ForeignKeyConstraint(['uid'], ['users.uid'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('pid', 'tid')
|
||||
op.create_index(op.f("ix_egg_solves_eid"), "egg_solves", ["eid"], unique=False)
|
||||
op.create_index(op.f("ix_egg_solves_tid"), "egg_solves", ["tid"], unique=False)
|
||||
op.create_index(op.f("ix_egg_solves_uid"), "egg_solves", ["uid"], unique=False)
|
||||
op.create_table(
|
||||
"game_states",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("uid", sa.Integer(), nullable=True),
|
||||
sa.Column(
|
||||
"last_updated",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column("state", sa.UnicodeText(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["uid"],
|
||||
["users.uid"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("last_updated"),
|
||||
sa.UniqueConstraint("uid"),
|
||||
)
|
||||
op.create_index(op.f('ix_solves_id'), 'solves', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_solves_pid'), 'solves', ['pid'], unique=False)
|
||||
op.create_index(op.f('ix_solves_tid'), 'solves', ['tid'], unique=False)
|
||||
op.create_index(op.f('ix_solves_uid'), 'solves', ['uid'], unique=False)
|
||||
op.create_table('team_player_invitation',
|
||||
sa.Column('team_id', sa.Integer(), nullable=True),
|
||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['team_id'], ['teams.tid'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.uid'], )
|
||||
op.create_table(
|
||||
"jobs",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("pid", sa.Integer(), nullable=True),
|
||||
sa.Column("tid", sa.Integer(), nullable=True),
|
||||
sa.Column("uid", sa.Integer(), nullable=True),
|
||||
sa.Column("submitted", sa.DateTime(), nullable=True),
|
||||
sa.Column("claimed", sa.DateTime(), nullable=True),
|
||||
sa.Column("completed", sa.DateTime(), nullable=True),
|
||||
sa.Column("execution_time", sa.Float(), nullable=True),
|
||||
sa.Column("execution_memory", sa.Float(), nullable=True),
|
||||
sa.Column("language", sa.String(length=16), nullable=False),
|
||||
sa.Column("contents", sa.Text(), nullable=False),
|
||||
sa.Column("feedback", sa.Text(), nullable=True),
|
||||
sa.Column("status", sa.Integer(), nullable=False),
|
||||
sa.Column("verdict", sa.String(length=8), nullable=True),
|
||||
sa.Column("last_ran_case", sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["pid"],
|
||||
["problems.pid"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["tid"],
|
||||
["teams.tid"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["uid"],
|
||||
["users.uid"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table('wrong_egg',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('eid', sa.Integer(), nullable=True),
|
||||
sa.Column('tid', sa.Integer(), nullable=True),
|
||||
sa.Column('uid', sa.Integer(), nullable=True),
|
||||
sa.Column('date', sa.DateTime(), nullable=True),
|
||||
sa.Column('submission', sa.Unicode(length=64), nullable=True),
|
||||
sa.ForeignKeyConstraint(['eid'], ['eggs.eid'], ),
|
||||
sa.ForeignKeyConstraint(['tid'], ['teams.tid'], ),
|
||||
sa.ForeignKeyConstraint(['uid'], ['users.uid'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
op.create_index(op.f("ix_jobs_pid"), "jobs", ["pid"], unique=False)
|
||||
op.create_index(op.f("ix_jobs_status"), "jobs", ["status"], unique=False)
|
||||
op.create_index(op.f("ix_jobs_tid"), "jobs", ["tid"], unique=False)
|
||||
op.create_index(op.f("ix_jobs_uid"), "jobs", ["uid"], unique=False)
|
||||
op.create_table(
|
||||
"password_reset_tokens",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("uid", sa.Integer(), nullable=True),
|
||||
sa.Column("active", sa.Boolean(), nullable=True),
|
||||
sa.Column("token", sa.String(length=32), nullable=True),
|
||||
sa.Column("email", sa.Unicode(length=128), nullable=True),
|
||||
sa.Column("expire", sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["uid"],
|
||||
["users.uid"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f('ix_wrong_egg_eid'), 'wrong_egg', ['eid'], unique=False)
|
||||
op.create_index(op.f('ix_wrong_egg_tid'), 'wrong_egg', ['tid'], unique=False)
|
||||
op.create_index(op.f('ix_wrong_egg_uid'), 'wrong_egg', ['uid'], unique=False)
|
||||
op.create_table('wrong_flags',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('pid', sa.Integer(), nullable=True),
|
||||
sa.Column('tid', sa.Integer(), nullable=True),
|
||||
sa.Column('uid', sa.Integer(), nullable=True),
|
||||
sa.Column('date', sa.DateTime(), nullable=True),
|
||||
sa.Column('flag', sa.Unicode(length=256), nullable=True),
|
||||
sa.ForeignKeyConstraint(['pid'], ['problems.pid'], ),
|
||||
sa.ForeignKeyConstraint(['tid'], ['teams.tid'], ),
|
||||
sa.ForeignKeyConstraint(['uid'], ['users.uid'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
op.create_index(
|
||||
op.f("ix_password_reset_tokens_uid"),
|
||||
"password_reset_tokens",
|
||||
["uid"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(op.f('ix_wrong_flags_flag'), 'wrong_flags', ['flag'], unique=False)
|
||||
op.create_index(op.f('ix_wrong_flags_id'), 'wrong_flags', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_wrong_flags_pid'), 'wrong_flags', ['pid'], unique=False)
|
||||
op.create_index(op.f('ix_wrong_flags_tid'), 'wrong_flags', ['tid'], unique=False)
|
||||
op.create_index(op.f('ix_wrong_flags_uid'), 'wrong_flags', ['uid'], unique=False)
|
||||
op.create_table(
|
||||
"player_team_invitation",
|
||||
sa.Column("user_id", sa.Integer(), nullable=True),
|
||||
sa.Column("team_id", sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["team_id"],
|
||||
["teams.tid"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["user_id"],
|
||||
["users.uid"],
|
||||
),
|
||||
)
|
||||
op.create_table(
|
||||
"solves",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("pid", sa.Integer(), nullable=True),
|
||||
sa.Column("tid", sa.Integer(), nullable=True),
|
||||
sa.Column("uid", sa.Integer(), nullable=True),
|
||||
sa.Column("date", sa.DateTime(), nullable=True),
|
||||
sa.Column("flag", sa.Unicode(length=256), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["pid"],
|
||||
["problems.pid"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["tid"],
|
||||
["teams.tid"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["uid"],
|
||||
["users.uid"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("pid", "tid"),
|
||||
)
|
||||
op.create_index(op.f("ix_solves_id"), "solves", ["id"], unique=False)
|
||||
op.create_index(op.f("ix_solves_pid"), "solves", ["pid"], unique=False)
|
||||
op.create_index(op.f("ix_solves_tid"), "solves", ["tid"], unique=False)
|
||||
op.create_index(op.f("ix_solves_uid"), "solves", ["uid"], unique=False)
|
||||
op.create_table(
|
||||
"team_player_invitation",
|
||||
sa.Column("team_id", sa.Integer(), nullable=True),
|
||||
sa.Column("user_id", sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["team_id"],
|
||||
["teams.tid"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["user_id"],
|
||||
["users.uid"],
|
||||
),
|
||||
)
|
||||
op.create_table(
|
||||
"wrong_egg",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("eid", sa.Integer(), nullable=True),
|
||||
sa.Column("tid", sa.Integer(), nullable=True),
|
||||
sa.Column("uid", sa.Integer(), nullable=True),
|
||||
sa.Column("date", sa.DateTime(), nullable=True),
|
||||
sa.Column("submission", sa.Unicode(length=64), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["eid"],
|
||||
["eggs.eid"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["tid"],
|
||||
["teams.tid"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["uid"],
|
||||
["users.uid"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_wrong_egg_eid"), "wrong_egg", ["eid"], unique=False)
|
||||
op.create_index(op.f("ix_wrong_egg_tid"), "wrong_egg", ["tid"], unique=False)
|
||||
op.create_index(op.f("ix_wrong_egg_uid"), "wrong_egg", ["uid"], unique=False)
|
||||
op.create_table(
|
||||
"wrong_flags",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("pid", sa.Integer(), nullable=True),
|
||||
sa.Column("tid", sa.Integer(), nullable=True),
|
||||
sa.Column("uid", sa.Integer(), nullable=True),
|
||||
sa.Column("date", sa.DateTime(), nullable=True),
|
||||
sa.Column("flag", sa.Unicode(length=256), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["pid"],
|
||||
["problems.pid"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["tid"],
|
||||
["teams.tid"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["uid"],
|
||||
["users.uid"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_wrong_flags_flag"), "wrong_flags", ["flag"], unique=False)
|
||||
op.create_index(op.f("ix_wrong_flags_id"), "wrong_flags", ["id"], unique=False)
|
||||
op.create_index(op.f("ix_wrong_flags_pid"), "wrong_flags", ["pid"], unique=False)
|
||||
op.create_index(op.f("ix_wrong_flags_tid"), "wrong_flags", ["tid"], unique=False)
|
||||
op.create_index(op.f("ix_wrong_flags_uid"), "wrong_flags", ["uid"], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_wrong_flags_uid'), table_name='wrong_flags')
|
||||
op.drop_index(op.f('ix_wrong_flags_tid'), table_name='wrong_flags')
|
||||
op.drop_index(op.f('ix_wrong_flags_pid'), table_name='wrong_flags')
|
||||
op.drop_index(op.f('ix_wrong_flags_id'), table_name='wrong_flags')
|
||||
op.drop_index(op.f('ix_wrong_flags_flag'), table_name='wrong_flags')
|
||||
op.drop_table('wrong_flags')
|
||||
op.drop_index(op.f('ix_wrong_egg_uid'), table_name='wrong_egg')
|
||||
op.drop_index(op.f('ix_wrong_egg_tid'), table_name='wrong_egg')
|
||||
op.drop_index(op.f('ix_wrong_egg_eid'), table_name='wrong_egg')
|
||||
op.drop_table('wrong_egg')
|
||||
op.drop_table('team_player_invitation')
|
||||
op.drop_index(op.f('ix_solves_uid'), table_name='solves')
|
||||
op.drop_index(op.f('ix_solves_tid'), table_name='solves')
|
||||
op.drop_index(op.f('ix_solves_pid'), table_name='solves')
|
||||
op.drop_index(op.f('ix_solves_id'), table_name='solves')
|
||||
op.drop_table('solves')
|
||||
op.drop_table('player_team_invitation')
|
||||
op.drop_index(op.f('ix_password_reset_tokens_uid'), table_name='password_reset_tokens')
|
||||
op.drop_table('password_reset_tokens')
|
||||
op.drop_index(op.f('ix_jobs_uid'), table_name='jobs')
|
||||
op.drop_index(op.f('ix_jobs_tid'), table_name='jobs')
|
||||
op.drop_index(op.f('ix_jobs_status'), table_name='jobs')
|
||||
op.drop_index(op.f('ix_jobs_pid'), table_name='jobs')
|
||||
op.drop_table('jobs')
|
||||
op.drop_table('game_states')
|
||||
op.drop_index(op.f('ix_egg_solves_uid'), table_name='egg_solves')
|
||||
op.drop_index(op.f('ix_egg_solves_tid'), table_name='egg_solves')
|
||||
op.drop_index(op.f('ix_egg_solves_eid'), table_name='egg_solves')
|
||||
op.drop_table('egg_solves')
|
||||
op.drop_index(op.f('ix_users_username'), table_name='users')
|
||||
op.drop_index(op.f('ix_users_uid'), table_name='users')
|
||||
op.drop_index(op.f('ix_users_easyctf'), table_name='users')
|
||||
op.drop_table('users')
|
||||
op.drop_table('team_classroom')
|
||||
op.drop_index(op.f('ix_files_pid'), table_name='files')
|
||||
op.drop_index(op.f('ix_files_id'), table_name='files')
|
||||
op.drop_table('files')
|
||||
op.drop_table('classroom_invitation')
|
||||
op.drop_index(op.f('ix_autogen_files_tid'), table_name='autogen_files')
|
||||
op.drop_index(op.f('ix_autogen_files_pid'), table_name='autogen_files')
|
||||
op.drop_index(op.f('ix_autogen_files_id'), table_name='autogen_files')
|
||||
op.drop_index(op.f('ix_autogen_files_filename'), table_name='autogen_files')
|
||||
op.drop_table('autogen_files')
|
||||
op.drop_index(op.f('ix_teams_tid'), table_name='teams')
|
||||
op.drop_table('teams')
|
||||
op.drop_index(op.f('ix_problems_pid'), table_name='problems')
|
||||
op.drop_table('problems')
|
||||
op.drop_index(op.f('ix_judge_api_keys_key'), table_name='judge_api_keys')
|
||||
op.drop_table('judge_api_keys')
|
||||
op.drop_index(op.f('ix_eggs_flag'), table_name='eggs')
|
||||
op.drop_table('eggs')
|
||||
op.drop_index(op.f('ix_config_key'), table_name='config')
|
||||
op.drop_table('config')
|
||||
op.drop_table('classrooms')
|
||||
op.drop_index(op.f("ix_wrong_flags_uid"), table_name="wrong_flags")
|
||||
op.drop_index(op.f("ix_wrong_flags_tid"), table_name="wrong_flags")
|
||||
op.drop_index(op.f("ix_wrong_flags_pid"), table_name="wrong_flags")
|
||||
op.drop_index(op.f("ix_wrong_flags_id"), table_name="wrong_flags")
|
||||
op.drop_index(op.f("ix_wrong_flags_flag"), table_name="wrong_flags")
|
||||
op.drop_table("wrong_flags")
|
||||
op.drop_index(op.f("ix_wrong_egg_uid"), table_name="wrong_egg")
|
||||
op.drop_index(op.f("ix_wrong_egg_tid"), table_name="wrong_egg")
|
||||
op.drop_index(op.f("ix_wrong_egg_eid"), table_name="wrong_egg")
|
||||
op.drop_table("wrong_egg")
|
||||
op.drop_table("team_player_invitation")
|
||||
op.drop_index(op.f("ix_solves_uid"), table_name="solves")
|
||||
op.drop_index(op.f("ix_solves_tid"), table_name="solves")
|
||||
op.drop_index(op.f("ix_solves_pid"), table_name="solves")
|
||||
op.drop_index(op.f("ix_solves_id"), table_name="solves")
|
||||
op.drop_table("solves")
|
||||
op.drop_table("player_team_invitation")
|
||||
op.drop_index(
|
||||
op.f("ix_password_reset_tokens_uid"), table_name="password_reset_tokens"
|
||||
)
|
||||
op.drop_table("password_reset_tokens")
|
||||
op.drop_index(op.f("ix_jobs_uid"), table_name="jobs")
|
||||
op.drop_index(op.f("ix_jobs_tid"), table_name="jobs")
|
||||
op.drop_index(op.f("ix_jobs_status"), table_name="jobs")
|
||||
op.drop_index(op.f("ix_jobs_pid"), table_name="jobs")
|
||||
op.drop_table("jobs")
|
||||
op.drop_table("game_states")
|
||||
op.drop_index(op.f("ix_egg_solves_uid"), table_name="egg_solves")
|
||||
op.drop_index(op.f("ix_egg_solves_tid"), table_name="egg_solves")
|
||||
op.drop_index(op.f("ix_egg_solves_eid"), table_name="egg_solves")
|
||||
op.drop_table("egg_solves")
|
||||
op.drop_index(op.f("ix_users_username"), table_name="users")
|
||||
op.drop_index(op.f("ix_users_uid"), table_name="users")
|
||||
op.drop_index(op.f("ix_users_easyctf"), table_name="users")
|
||||
op.drop_table("users")
|
||||
op.drop_table("team_classroom")
|
||||
op.drop_index(op.f("ix_files_pid"), table_name="files")
|
||||
op.drop_index(op.f("ix_files_id"), table_name="files")
|
||||
op.drop_table("files")
|
||||
op.drop_table("classroom_invitation")
|
||||
op.drop_index(op.f("ix_autogen_files_tid"), table_name="autogen_files")
|
||||
op.drop_index(op.f("ix_autogen_files_pid"), table_name="autogen_files")
|
||||
op.drop_index(op.f("ix_autogen_files_id"), table_name="autogen_files")
|
||||
op.drop_index(op.f("ix_autogen_files_filename"), table_name="autogen_files")
|
||||
op.drop_table("autogen_files")
|
||||
op.drop_index(op.f("ix_teams_tid"), table_name="teams")
|
||||
op.drop_table("teams")
|
||||
op.drop_index(op.f("ix_problems_pid"), table_name="problems")
|
||||
op.drop_table("problems")
|
||||
op.drop_index(op.f("ix_judge_api_keys_key"), table_name="judge_api_keys")
|
||||
op.drop_table("judge_api_keys")
|
||||
op.drop_index(op.f("ix_eggs_flag"), table_name="eggs")
|
||||
op.drop_table("eggs")
|
||||
op.drop_index(op.f("ix_config_key"), table_name="config")
|
||||
op.drop_table("config")
|
||||
op.drop_table("classrooms")
|
||||
# ### end Alembic commands ###
|
||||
|
|
Loading…
Reference in New Issue