python: Run black over the source

This commit is contained in:
Michael Zhang 2022-11-29 03:20:25 -06:00
parent 4cf0c40fb5
commit 9f3346f158
No known key found for this signature in database
GPG Key ID: BDA47A31A3C8EE6B
31 changed files with 1458 additions and 714 deletions

View File

@ -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";
};

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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]

View File

@ -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

View File

@ -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")

View File

@ -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"),
)

View File

@ -11,4 +11,4 @@ SUPPORTED_LANGUAGES = {
"python2": "Python 2",
"python3": "Python 3",
"java": "Java",
}
}

View File

@ -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)

View File

@ -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))

View File

@ -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):

View File

@ -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!")

View File

@ -13,4 +13,4 @@ class GameStateUpdateForm(FlaskForm):
try:
json.loads(field.data)
except:
raise ValidationError('invalid json!')
raise ValidationError("invalid json!")

View File

@ -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")

View File

@ -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):

View File

@ -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."
)

View File

@ -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="{}")

View File

@ -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()

View File

@ -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

View File

@ -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")

View File

@ -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")

View File

@ -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)

View File

@ -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,
)

View File

@ -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"])

View File

@ -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

View File

@ -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):

View File

@ -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()

View File

@ -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:

View File

@ -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 ###