Merge branch 'master' of github.com:IceCTF/new-platform

master
Glitch 2016-08-12 23:50:33 +00:00
commit b4dbc062d2
32 changed files with 412 additions and 204 deletions

1
.gitignore vendored
View File

@ -102,5 +102,6 @@ tags
dev.db dev.db
dump.rdb dump.rdb
/problems /problems
/problem_static
/secrets /secrets
/database /database

5
app.py
View File

@ -12,7 +12,7 @@ from data.database import db
import data import data
# Blueprints # Blueprints
from routes import api, admin, teams, users, challenges, tickets, scoreboard from routes import api, admin, teams, users, challenges, tickets, scoreboard, shell
if config.production: if config.production:
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
@ -30,6 +30,7 @@ app.register_blueprint(users.users)
app.register_blueprint(challenges.challenges) app.register_blueprint(challenges.challenges)
app.register_blueprint(tickets.tickets) app.register_blueprint(tickets.tickets)
app.register_blueprint(scoreboard.scoreboard) app.register_blueprint(scoreboard.scoreboard)
app.register_blueprint(shell.shell)
@app.before_request @app.before_request
@ -68,7 +69,7 @@ def scoreboard_variables():
@app.route('/') @app.route('/')
def root(): def root():
if g.logged_in: if g.logged_in:
return redirect(url_for('team.dashboard')) return redirect(url_for('teams.dashboard'))
return redirect(url_for('users.register')) return redirect(url_for('users.register'))

View File

@ -7,7 +7,7 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ctf_name = "IceCTF" ctf_name = "IceCTF"
eligibility = "In order to be eligible for prizes, all members of your team must be Icelandic residents, and you must not have more than three team members." eligibility = "In order to be eligible for prizes, all members of your team must be Icelandic residents, and you must not have more than three team members."
tagline = "The Icelandic Hacking Competition" tagline = "The Icelandic Hacking Competition"
#IRC Channel # IRC Channel
ctf_chat_channel = "#IceCTF" ctf_chat_channel = "#IceCTF"
ctf_home_url = "https://icec.tf" ctf_home_url = "https://icec.tf"
@ -32,8 +32,8 @@ immediate_scoreboard = False
disallowed_domain = "icec.tf" disallowed_domain = "icec.tf"
# Where the static stuff is stored # Where the static stuff is stored
static_prefix = "http://127.0.0.1/static/" static_prefix = "/problem-static/"
static_dir = "{}/static/".format(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) static_dir = "{}/problem_static/".format(os.path.dirname(os.path.abspath(__file__)))
custom_stylesheet = "css/main.css" custom_stylesheet = "css/main.css"
# Shell accounts? # Shell accounts?
@ -41,14 +41,16 @@ custom_stylesheet = "css/main.css"
enable_shell = True enable_shell = True
shell_port = 22 shell_port = 22
shell_host = "shell.icec.tf"
shell_user_prefixes = ["ctf-"] shell_user_prefixes = ["ctf-"]
shell_password_length = 6 shell_password_length = 8
shell_free_acounts = 10 shell_free_acounts = 10
shell_max_accounts = 99999 shell_max_accounts = 99999
shell_user_creation = "sudo useradd -m {username} -p {password} -g ctf -b /home_users" shell_user_creation = "sudo useradd -m {username} -p {password} -g ctf -b /home_users"
# when the competition begins # when the competition begins
competition_begin = datetime(1970, 1, 1, 0, 0) competition_begin = datetime(1970, 1, 1, 0, 0)
competition_end = datetime(2018, 1, 1, 0, 0) competition_end = datetime(2018, 1, 1, 0, 0)

107
ctftool
View File

@ -17,7 +17,7 @@ import yaml
import argparse import argparse
import logging import logging
tables = [Team, User, UserAccess, Challenge, ChallengeSolve, ChallengeFailure, NewsItem, TroubleTicket, TicketComment, Notification, ScoreAdjustment, AdminUser, SshAccount] tables = [Team, User, UserAccess, Stage, Challenge, ChallengeSolve, ChallengeFailure, NewsItem, TroubleTicket, TicketComment, Notification, ScoreAdjustment, AdminUser, SshAccount]
def create_tables(args): def create_tables(args):
check = True check = True
@ -66,58 +66,73 @@ def add_admin(args):
secret = "".join([r.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567") for i in range(16)]) secret = "".join([r.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567") for i in range(16)])
AdminUser.create(username=username, password=pwhash, secret=secret) AdminUser.create(username=username, password=pwhash, secret=secret)
print("AdminUser created; Enter the following key into your favorite TOTP application (Google Authenticator Recommended): {}".format(secret)) print("AdminUser created; Enter the following key into your favorite TOTP application (Google Authenticator Recommended): {}".format(secret))
def scan_challenges_problem(d, files):
staticpaths = {}
if "static.yml" in files:
with open(os.path.join(d, "static.yml")) as f:
statics = yaml.load(f)
for static in statics:
h = hashlib.sha256()
with open(os.path.join(d, static), "rb") as staticfile:
while True:
buf = staticfile.read(4096)
h.update(buf)
if not buf:
break
if "." in static:
name, ext = static.split(".", maxsplit=1)
fn = "{}_{}.{}".format(name, h.hexdigest(), ext)
else:
fn = "{}_{}".format(static, h.hexdigest())
staticpaths[static] = fn
shutil.copy(os.path.join(d, static), os.path.join(config.static_dir, fn))
print(fn)
with open(os.path.join(d, "problem.yml")) as f:
data = yaml.load(f)
print("Inserting problem in directory %s" % (d))
for i in staticpaths:
print("looking for |{}|".format(i))
data["description"] = data["description"].replace("|{}|".format(i), "{}{}".format(config.static_prefix, staticpaths[i]))
data["stage"] = Stage.get(Stage.alias == data["stage"])
query = Challenge.select().where(Challenge.alias == data["alias"])
if query.exists():
print("Updating " + str(data["name"]) + "...")
q = Challenge.update(**data).where(Challenge.alias == data["alias"])
q.execute()
else:
Challenge.create(**data)
def scan_challenges_stage(d, files):
with open(os.path.join(d, "stage.yml")) as f:
data = yaml.load(f)
query = Stage.select().where(Stage.alias == data["alias"])
if query.exists():
print("Updating %s..." % (data["name"]))
q = Challenge.update(**data).where(Stage.alias == data["alias"])
else:
Stage.create(**data)
def scan_challenges(args): def scan_challenges(args):
path = args.path path = args.path
dirs = [j for j in [os.path.join(path, i) for i in os.listdir(path)] if os.path.isdir(j)]
# recurse to 2 levels
# TODO: do this better
dirs = [j for j in [os.path.join(k, i) for k in dirs for i in os.listdir(k)] if os.path.isdir(j)]
print(dirs)
n = 0 n = 0
for d in dirs: os.makedirs(config.static_dir, exist_ok=True)
staticpaths = {} for root, dirs, files in os.walk(path):
if os.path.exists(os.path.join(d, "static.yml")): if "problem.yml" in files:
with open(os.path.join(d, "static.yml")) as f: n += 1
statics = yaml.load(f) scan_challenges_problem(root, files)
for static in statics: if "stage.yml" in files:
h = hashlib.sha256() scan_challenges_stage(root, files)
with open(os.path.join(d, static), "rb") as staticfile:
while True:
buf = staticfile.read(4096)
h.update(buf)
if not buf:
break
if "." in static:
name, ext = static.split(".", maxsplit=1)
fn = "{}_{}.{}".format(name, h.hexdigest(), ext)
else:
fn = "{}_{}".format(static, h.hexdigest())
staticpaths[static] = fn
shutil.copy(os.path.join(d, static), os.path.join(config.static_dir, fn))
print(fn)
if os.path.exists(os.path.join(d, "problem.yml")):
with open(os.path.join(d, "problem.yml")) as f:
n += 1
data = yaml.load(f)
print("Inserting problem in directory %s" % (d))
for i in staticpaths:
print("looking for |{}|".format(i))
data["description"] = data["description"].replace("|{}|".format(i), "{}{}".format(config.static_prefix, staticpaths[i]))
query = Challenge.select().where(Challenge.name == data["name"])
if query.exists():
print("Updating " + str(data["name"]) + "...")
q = Challenge.update(**data).where(Challenge.name == data["name"])
q.execute()
else:
Challenge.create(**data)
print(n, "challenges loaded") print(n, "challenges loaded")
def add_challenge(args): def add_challenge(args):
challengefile = args.file challengefile = args.file
with open(challengefile) as f: with open(challengefile) as f:
@ -152,7 +167,7 @@ def clear_challenges(args):
def recache_solves(args): def recache_solves(args):
r = redis.StrictRedis() r = redis.StrictRedis(host=config.redis.host, port=config.redis.port, db=config.redis.db)
for chal in Challenge.select(): for chal in Challenge.select():
r.hset("solves", chal.id, chal.solves.count()) r.hset("solves", chal.id, chal.solves.count())
print(r.hvals("solves")) print(r.hvals("solves"))

View File

@ -1,15 +1,18 @@
import redis import redis
import data.scoreboard from data import scoreboard
import json import json
import config
r = redis.StrictRedis() from data.database import db
def set_complex(key, val):
r.set(key, json.dumps(val))
def run(): def run():
data = data.scoreboard.calculate_scores() r = redis.StrictRedis(host=config.redis.host, port=config.redis.port, db=config.redis.db)
graphdata = utils.scoreboard.calculate_graph(data) db.connect()
def set_complex(key, val):
r.set(key, json.dumps(val))
data = scoreboard.calculate_scores()
graphdata = scoreboard.calculate_graph(data)
set_complex("scoreboard", data) set_complex("scoreboard", data)
set_complex("graph", graphdata) set_complex("graph", graphdata)
db.close()

View File

@ -1,20 +1,19 @@
import api
import spur import spur
import pwd
import random import random
import config import config
from data.database import db from data.database import db
from data import ssh from data import ssh
import utils.misc
def run(): def run():
db.connect() db.connect()
try: try:
shell = spur.SshShell( shell = spur.SshShell(
hostname=config.secrets.shell_host, hostname=config.shell_host,
username=config.secrets.shell_username, username=config.secret.shell_username,
password=config.secrets.shell_password, private_key_file=config.secret.shell_privkey,
port=config.shell_port, port=config.shell_port,
missing_host_key=spur.ssh.MissingHostKey.accept missing_host_key=spur.ssh.MissingHostKey.accept
) )
@ -43,10 +42,10 @@ def run():
accounts = [] accounts = []
while new_accounts > 0: while new_accounts > 0:
username = random.choice(api.config.shell_user_prefixes) + \ username = random.choice(config.shell_user_prefixes) + \
str(random.randint(0, api.config.shell_max_accounts)) str(random.randint(0, config.shell_max_accounts))
plaintext_password = api.common.token()[:api.config.shell_password_length] plaintext_password = utils.misc.generate_random_string(config.shell_password_length, chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789")
hashed_password = shell.run(["bash", "-c", "echo '{}' | openssl passwd -1 -stdin".format(plaintext_password)]) hashed_password = shell.run(["bash", "-c", "echo '{}' | openssl passwd -1 -stdin".format(plaintext_password)])
hashed_password = hashed_password.output.decode("utf-8").strip() hashed_password = hashed_password.output.decode("utf-8").strip()
@ -75,7 +74,7 @@ def run():
new_accounts -= 1 new_accounts -= 1
if len(accounts) > 0: if len(accounts) > 0:
ssh.insert_accounts(accounts) ssh.create_accounts(accounts)
print("Successfully imported accounts.") print("Successfully imported accounts.")
for team in teams: for team in teams:

View File

@ -1,17 +1,58 @@
from data.database import Challenge, ChallengeSolve, ChallengeFailure, ScoreAdjustment, Team from data.database import Stage, Challenge, ChallengeSolve, ChallengeFailure, ScoreAdjustment, Team
from datetime import datetime from datetime import datetime
from exceptions import ValidationError from exceptions import ValidationError
from flask import g from flask import g
import config import config
def get_stages():
return list(Stage.select().order_by(Stage.name))
def get_stage_challenges(stage_id):
print(stage_id)
return list(Challenge.select(Challenge.alias).where(Challenge.stage == stage_id))
def get_categories():
return [q.category for q in Challenge.select(Challenge.category).distinct().order_by(Challenge.category)]
def get_challenges(): def get_challenges():
return Challenge.select().order_by(Challenge.points, Challenge.name) challenges = Challenge.select().where(Challenge.enabled == True).order_by(Challenge.stage, Challenge.points, Challenge.name)
d = dict()
for chall in challenges:
if chall.stage_id in d:
d[chall.stage_id].append(chall)
else:
d[chall.stage_id] = [chall]
return d
def get_challenge(id): def get_solve_counts():
# TODO: optimize
d = dict()
for k in Challenge.select(Challenge.id):
d[k.id] = get_solve_count(k.id)
return d
def get_solve_count(chall_id):
s = g.redis.hget("solves", chall_id)
if s is not None:
return int(s.decode())
else:
return -1
def get_challenge(id=None, alias=None):
try: try:
return Challenge.get(Challenge.id == id) if id is not None:
return Challenge.get(Challenge.id == id, Challenge.enabled == True)
elif alias is not None:
return Challenge.get(Challenge.alias == alias, Challenge.enabled == True)
else:
raise ValueError("Invalid argument")
except Challenge.DoesNotExist: except Challenge.DoesNotExist:
raise ValidationError("Challenge does not exist!") raise ValidationError("Challenge does not exist!")
@ -20,6 +61,10 @@ def get_solved(team):
return Challenge.select().join(ChallengeSolve).where(ChallengeSolve.team == g.team) return Challenge.select().join(ChallengeSolve).where(ChallengeSolve.team == g.team)
def get_solves(team):
return ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge).where(ChallengeSolve.team == g.team)
def get_adjustments(team): def get_adjustments(team):
return ScoreAdjustment.select().where(ScoreAdjustment.team == team) return ScoreAdjustment.select().where(ScoreAdjustment.team == team)
@ -35,7 +80,7 @@ def submit_flag(chall, user, team, flag):
raise ValidationError("This challenge is disabled.") raise ValidationError("This challenge is disabled.")
elif flag.strip().lower() != chall.flag.strip().lower(): elif flag.strip().lower() != chall.flag.strip().lower():
ChallengeFailure.create(user=user, team=team, challenge=chall, attempt=flag, time=datetime.now()) ChallengeFailure.create(user=user, team=team, challenge=chall, attempt=flag, time=datetime.now())
return "Incorrect flag" raise ValidationError("Incorrect flag")
else: else:
ChallengeSolve.create(user=user, team=team, challenge=chall, time=datetime.now()) ChallengeSolve.create(user=user, team=team, challenge=chall, time=datetime.now())
g.redis.hincrby("solves", chall.id, 1) g.redis.hincrby("solves", chall.id, 1)

View File

@ -1,6 +1,9 @@
from peewee import * from peewee import PostgresqlDatabase, SqliteDatabase
from peewee import Model, CharField, TextField, IntegerField, BooleanField, DateTimeField, ForeignKeyField, CompositeKey
import bcrypt import bcrypt
import config import config
if config.production: if config.production:
db = PostgresqlDatabase(config.database.database, user=config.database.user, password=config.database.password) db = PostgresqlDatabase(config.database.database, user=config.database.user, password=config.database.password)
else: else:
@ -11,16 +14,20 @@ class BaseModel(Model):
class Meta: class Meta:
database = db database = db
class Team(BaseModel): class Team(BaseModel):
name = CharField(unique=True) name = CharField(unique=True)
affiliation = CharField(null = True) affiliation = CharField(null=True)
restricts = TextField(default="") restricts = TextField(default="")
key = CharField(unique=True, index=True) key = CharField(unique=True, index=True)
eligibility = BooleanField(null=True)
def solved(self, challenge): def solved(self, challenge):
return ChallengeSolve.select().where(ChallengeSolve.team == self, ChallengeSolve.challenge == challenge).count() return ChallengeSolve.select().where(ChallengeSolve.team == self, ChallengeSolve.challenge == challenge).count()
def eligible(self): def eligible(self):
if self.eligibility is not None:
return self.eligibility
return all([member.eligible() for member in self.members]) and self.members.count() <= 3 return all([member.eligible() for member in self.members]) and self.members.count() <= 3
@property @property
@ -29,22 +36,23 @@ class Team(BaseModel):
adjust_points = sum([i.value for i in self.adjustments]) adjust_points = sum([i.value for i in self.adjustments])
return challenge_points + adjust_points return challenge_points + adjust_points
class User(BaseModel): class User(BaseModel):
username = CharField(unique=True, index=True) username = CharField(unique=True, index=True)
email = CharField(index=True) email = CharField(index=True)
email_confirmed = BooleanField(default=False) email_confirmed = BooleanField(default=False)
email_confirmation_key = CharField() email_confirmation_key = CharField()
password = CharField(null = True) password = CharField(null=True)
background = CharField() background = CharField()
country = CharField() country = CharField()
tshirt_size = CharField(null = True) tshirt_size = CharField(null=True)
gender = CharField(null = True) gender = CharField(null=True)
first_login = BooleanField(default=True) first_login = BooleanField(default=True)
restricts = TextField(default="") restricts = TextField(default="")
team = ForeignKeyField(Team, related_name="members") team = ForeignKeyField(Team, related_name="members")
banned = BooleanField(default=False) banned = BooleanField(default=False)
password_reset_token = CharField(null = True) password_reset_token = CharField(null=True)
password_reset_expired = DateTimeField(null = True) password_reset_expired = DateTimeField(null=True)
def set_password(self, pw): def set_password(self, pw):
self.password = bcrypt.hashpw(pw.encode("utf-8"), bcrypt.gensalt()) self.password = bcrypt.hashpw(pw.encode("utf-8"), bcrypt.gensalt())
@ -54,15 +62,24 @@ class User(BaseModel):
return bcrypt.checkpw(pw.encode("utf-8"), self.password.encode("utf-8")) return bcrypt.checkpw(pw.encode("utf-8"), self.password.encode("utf-8"))
def eligible(self): def eligible(self):
return self.country == "ISL" return self.country == "ISL" and not self.banned
class UserAccess(BaseModel): class UserAccess(BaseModel):
user = ForeignKeyField(User, related_name='accesses') user = ForeignKeyField(User, related_name='accesses')
ip = CharField() ip = CharField()
time = DateTimeField() time = DateTimeField()
class Stage(BaseModel):
name = CharField()
alias = CharField(unique=True, index=True)
description = CharField(null=True)
class Challenge(BaseModel): class Challenge(BaseModel):
name = CharField() name = CharField()
alias = CharField(unique=True, index=True)
category = CharField() category = CharField()
author = CharField() author = CharField()
description = TextField() description = TextField()
@ -70,6 +87,8 @@ class Challenge(BaseModel):
breakthrough_bonus = IntegerField(default=0) breakthrough_bonus = IntegerField(default=0)
enabled = BooleanField(default=True) enabled = BooleanField(default=True)
flag = TextField() flag = TextField()
stage = ForeignKeyField(Stage, related_name='challenges')
class ChallengeSolve(BaseModel): class ChallengeSolve(BaseModel):
user = ForeignKeyField(User, related_name='solves') user = ForeignKeyField(User, related_name='solves')
@ -80,6 +99,7 @@ class ChallengeSolve(BaseModel):
class Meta: class Meta:
primary_key = CompositeKey('team', 'challenge') primary_key = CompositeKey('team', 'challenge')
class ChallengeFailure(BaseModel): class ChallengeFailure(BaseModel):
user = ForeignKeyField(User, related_name='failures') user = ForeignKeyField(User, related_name='failures')
team = ForeignKeyField(Team, related_name='failures') team = ForeignKeyField(Team, related_name='failures')
@ -87,10 +107,12 @@ class ChallengeFailure(BaseModel):
attempt = CharField() attempt = CharField()
time = DateTimeField() time = DateTimeField()
class NewsItem(BaseModel): class NewsItem(BaseModel):
summary = CharField() summary = CharField()
description = TextField() description = TextField()
class TroubleTicket(BaseModel): class TroubleTicket(BaseModel):
team = ForeignKeyField(Team, related_name='tickets') team = ForeignKeyField(Team, related_name='tickets')
summary = CharField() summary = CharField()
@ -98,26 +120,31 @@ class TroubleTicket(BaseModel):
active = BooleanField(default=True) active = BooleanField(default=True)
opened_at = DateTimeField() opened_at = DateTimeField()
class TicketComment(BaseModel): class TicketComment(BaseModel):
ticket = ForeignKeyField(TroubleTicket, related_name='comments') ticket = ForeignKeyField(TroubleTicket, related_name='comments')
comment_by = CharField() comment_by = CharField()
comment = TextField() comment = TextField()
time = DateTimeField() time = DateTimeField()
class Notification(BaseModel): class Notification(BaseModel):
team = ForeignKeyField(Team, related_name='notifications') team = ForeignKeyField(Team, related_name='notifications')
notification = TextField() notification = TextField()
class ScoreAdjustment(BaseModel): class ScoreAdjustment(BaseModel):
team = ForeignKeyField(Team, related_name='adjustments') team = ForeignKeyField(Team, related_name='adjustments')
value = IntegerField() value = IntegerField()
reason = TextField() reason = TextField()
class AdminUser(BaseModel): class AdminUser(BaseModel):
username = CharField() username = CharField()
password = CharField() password = CharField()
secret = CharField() secret = CharField()
class SshAccount(BaseModel): class SshAccount(BaseModel):
team = ForeignKeyField(Team, null=True, related_name='ssh_account') team = ForeignKeyField(Team, null=True, related_name='ssh_account')
username = CharField() username = CharField()

View File

@ -20,3 +20,9 @@ def assign_shell_account(team):
acct = SshAccount.select().order_by(fn.Random()).get() acct = SshAccount.select().order_by(fn.Random()).get()
acct.team = team acct.team = team
acct.save() acct.save()
def get_team_account(team):
try:
return team.ssh_account.get()
except SshAccount.DoesNotExist:
return None

View File

@ -33,7 +33,7 @@ def create_team(name, affiliation):
team_key = misc.generate_team_key() team_key = misc.generate_team_key()
team = Team.create(name=name, affiliation=affiliation, key=team_key) team = Team.create(name=name, affiliation=affiliation, key=team_key)
return True, team return team
def update_team(current_team, name, affiliation): def update_team(current_team, name, affiliation):

View File

@ -8,7 +8,7 @@ def get_tickets(team):
def get_ticket(team, id): def get_ticket(team, id):
try: try:
return TroubleTicket.get(TroubleTicket.id == ticket and TroubleTicket.team == team) return TroubleTicket.get(TroubleTicket.id == id, TroubleTicket.team == team)
except TroubleTicket.DoesNotExist: except TroubleTicket.DoesNotExist:
raise ValidationError("Ticket not found!") raise ValidationError("Ticket not found!")
@ -16,10 +16,10 @@ def get_comments(ticket):
return TicketComment.select().where(TicketComment.ticket == ticket).order_by(TicketComment.time) return TicketComment.select().where(TicketComment.ticket == ticket).order_by(TicketComment.time)
def create_ticket(team, summary, description): def create_ticket(team, summary, description):
return TroubleTicket.create(team=g.team, summary=summary, description=description, opened_at=datetime.now()) return TroubleTicket.create(team=team, summary=summary, description=description, opened_at=datetime.now())
def create_comment(ticket, user, comment): def create_comment(ticket, user, comment):
TicketComment.create(ticket=ticket, comment_by=user.username, comment=request.form["comment"], time=datetime.now()) TicketComment.create(ticket=ticket, comment_by=user.username, comment=comment, time=datetime.now())
def open_ticket(ticket): def open_ticket(ticket):
ticket.active = True ticket.active = True

View File

@ -82,7 +82,7 @@ def create_user(username, email, password, background, country, team, tshirt_siz
def confirm_email(current_user, confirmation_key): def confirm_email(current_user, confirmation_key):
if current_user.email_confirmed: if current_user.email_confirmed:
raise ValidationError("Email already confirmed") raise ValidationError("Email already confirmed")
if current_user.confirmation_key == confirmation_key: if current_user.email_confirmation_key == confirmation_key:
current_user.email_confirmed = True current_user.email_confirmed = True
current_user.save() current_user.save()
else: else:

View File

@ -7,3 +7,4 @@ pyyaml
oath oath
pycountry pycountry
psycopg2 psycopg2
spur

View File

@ -53,7 +53,7 @@ def admin_dashboard():
adjustments = ScoreAdjustment.select() adjustments = ScoreAdjustment.select()
scoredata = scoreboard.get_all_scores(teams, solves, adjustments) scoredata = scoreboard.get_all_scores(teams, solves, adjustments)
lastsolvedata = scoreboard.get_last_solves(teams, solves) lastsolvedata = scoreboard.get_last_solves(teams, solves)
tickets = list(TroubleTicket.select().where(TroubleTicket.active==True)) tickets = list(TroubleTicket.select().where(TroubleTicket.active == True))
return render_template("admin/dashboard.html", teams=teams, scoredata=scoredata, lastsolvedata=lastsolvedata, tickets=tickets) return render_template("admin/dashboard.html", teams=teams, scoredata=scoredata, lastsolvedata=lastsolvedata, tickets=tickets)
@ -78,19 +78,19 @@ def admin_ticket_comment(ticket):
ticket = TroubleTicket.get(TroubleTicket.id == ticket) ticket = TroubleTicket.get(TroubleTicket.id == ticket)
if request.form["comment"]: if request.form["comment"]:
TicketComment.create(ticket=ticket, comment_by=session["admin"], comment=request.form["comment"], time=datetime.now()) TicketComment.create(ticket=ticket, comment_by=session["admin"], comment=request.form["comment"], time=datetime.now())
Notification.create(team=ticket.team, notification="A response has been added for {}.".format(make_link("ticket #{}".format(ticket.id), url_for("team_ticket_detail", ticket=ticket.id)))) Notification.create(team=ticket.team, notification="A response has been added for {}.".format(make_link("ticket #{}".format(ticket.id), url_for("tickets.detail", ticket_id=ticket.id))))
flash("Comment added.") flash("Comment added.")
if ticket.active and "resolved" in request.form: if ticket.active and "resolved" in request.form:
ticket.active = False ticket.active = False
ticket.save() ticket.save()
Notification.create(team=ticket.team, notification="{} has been marked resolved.".format(make_link("Ticket #{}".format(ticket.id), url_for("team_ticket_detail", ticket=ticket.id)))) Notification.create(team=ticket.team, notification="{} has been marked resolved.".format(make_link("Ticket #{}".format(ticket.id), url_for("tickets.detail", ticket_id=ticket.id))))
flash("Ticket closed.") flash("Ticket closed.")
elif not ticket.active and "resolved" not in request.form: elif not ticket.active and "resolved" not in request.form:
ticket.active = True ticket.active = True
ticket.save() ticket.save()
Notification.create(team=ticket.team, notification="{} has been reopened.".format(make_link("Ticket #{}".format(ticket.id), url_for("team_ticket_detail", ticket=ticket.id)))) Notification.create(team=ticket.team, notification="{} has been reopened.".format(make_link("Ticket #{}".format(ticket.id), url_for("tickets.detail", ticket_id=ticket.id))))
flash("Ticket reopened.") flash("Ticket reopened.")
return redirect(url_for(".admin_ticket_detail", ticket=ticket.id)) return redirect(url_for(".admin_ticket_detail", ticket=ticket.id))
@ -116,23 +116,15 @@ def admin_impersonate_team(tid):
@admin_required @admin_required
def admin_toggle_eligibility(tid): def admin_toggle_eligibility(tid):
team = Team.get(Team.id == tid) team = Team.get(Team.id == tid)
team.eligible = not team.eligible if team.eligibility is None:
team.eligibility = False
else:
team.eligibility = not team.eligibility
team.save() team.save()
flash("Eligibility set to {}".format(team.eligible)) flash("Eligibility set to {}".format(team.eligible))
return redirect(url_for(".admin_show_team", tid=tid)) return redirect(url_for(".admin_show_team", tid=tid))
@admin.route("/team/<int:tid>/<csrf>/toggle_eligibility_lock/")
@csrf_check
@admin_required
def admin_toggle_eligibility_lock(tid):
team = Team.get(Team.id == tid)
team.eligibility_locked = not team.eligibility_locked
team.save()
flash("Eligibility lock set to {}".format(team.eligibility_locked))
return redirect(url_for(".admin_show_team", tid=tid))
@admin.route("/team/<int:tid>/adjust_score/", methods=["POST"]) @admin.route("/team/<int:tid>/adjust_score/", methods=["POST"])
@admin_required @admin_required
def admin_score_adjust(tid): def admin_score_adjust(tid):

View File

@ -8,7 +8,7 @@ import config
api = Blueprint("api", __name__, url_prefix="/api") api = Blueprint("api", __name__, url_prefix="/api")
@api.route("/submit/<int:challenge_id>.json", methods=["POST"]) @api.route("/submit/<challenge_id>.json", methods=["POST"])
@decorators.must_be_allowed_to("solve challenges") @decorators.must_be_allowed_to("solve challenges")
@decorators.must_be_allowed_to("view challenges") @decorators.must_be_allowed_to("view challenges")
@decorators.competition_running_required @decorators.competition_running_required
@ -16,14 +16,15 @@ api = Blueprint("api", __name__, url_prefix="/api")
@ratelimit.ratelimit(limit=10, per=120, over_limit=ratelimit.on_over_api_limit) @ratelimit.ratelimit(limit=10, per=120, over_limit=ratelimit.on_over_api_limit)
def submit_api(challenge_id): def submit_api(challenge_id):
try: try:
chall = challenge.get_challenge(challenge_id) chall = challenge.get_challenge(alias=challenge_id)
except exceptions.ValidationError as e: except exceptions.ValidationError as e:
return jsonify(dict(code=1001, message=str(e))) return jsonify(dict(code=1001, message=str(e)))
flag = request.form["flag"] flag = request.form["flag"]
try: try:
challenge.submit_flag(chall, g.user, g.team, flag) challenge.submit_flag(chall, g.user, g.team, flag)
return jsonify(dict(code=0, message="Success!")) solves = challenge.get_solve_count(chall.id)
return jsonify(dict(code=0, message="Success!", solves=solves))
except exceptions.ValidationError as e: except exceptions.ValidationError as e:
return jsonify(dict(code=1001, message=str(e))) return jsonify(dict(code=1001, message=str(e)))

View File

@ -13,21 +13,23 @@ challenges = Blueprint("challenges", __name__, template_folder="../templates/cha
@decorators.competition_running_required @decorators.competition_running_required
@decorators.confirmed_email_required @decorators.confirmed_email_required
def index(): def index():
chals = challenge.get_challenges() stages = challenge.get_stages()
challs = challenge.get_challenges()
solved = challenge.get_solved(g.team) solved = challenge.get_solved(g.team)
solves = {i: int(g.redis.hget("solves", i).decode()) for i in [k.id for k in chals]} solves = challenge.get_solve_counts()
categories = sorted(list({chal.category for chal in chals})) categories = challenge.get_categories()
return render_template("challenges.html", challenges=chals, solved=solved, categories=categories, solves=solves) first_stage = {chall.alias: True for chall in challs[stages[0].id]} if stages else None
return render_template("challenges.html", stages=stages, first_stage=first_stage, challenges=challs, solved=solved, categories=categories, solves=solves)
@challenges.route('/challenges/<int:challenge_id>/solves/') @challenges.route('/challenges/<challenge_id>/solves/')
@decorators.must_be_allowed_to("view challenge solves") @decorators.must_be_allowed_to("view challenge solves")
@decorators.must_be_allowed_to("view challenges") @decorators.must_be_allowed_to("view challenges")
@decorators.competition_running_required @decorators.competition_running_required
@decorators.confirmed_email_required @decorators.confirmed_email_required
def show_solves(challenge_id): def show_solves(challenge_id):
try: try:
chall = challenge.get_challenge(challenge_id) chall = challenge.get_challenge(alias=challenge_id)
except exceptions.ValidationError as e: except exceptions.ValidationError as e:
flash(str(e)) flash(str(e))
return redirect(url_for(".index")) return redirect(url_for(".index"))
@ -35,7 +37,7 @@ def show_solves(challenge_id):
return render_template("challenge_solves.html", challenge=chall, solves=solves) return render_template("challenge_solves.html", challenge=chall, solves=solves)
@challenges.route('/submit/<int:challenge_id>/', methods=["POST"]) @challenges.route('/submit/<challenge_id>/', methods=["POST"])
@decorators.must_be_allowed_to("solve challenges") @decorators.must_be_allowed_to("solve challenges")
@decorators.must_be_allowed_to("view challenges") @decorators.must_be_allowed_to("view challenges")
@decorators.competition_running_required @decorators.competition_running_required

View File

@ -19,6 +19,6 @@ def index():
data.scoreboard.set_complex("scoreboard", data, 120) data.scoreboard.set_complex("scoreboard", data, 120)
data.scoreboard.set_complex("graph", graphdata, 120) data.scoreboard.set_complex("graph", graphdata, 120)
else: else:
return "No scoreboard data available. Please contact an organizer." return "CTF hasn't started!"
return render_template("scoreboard.html", data=scoreboard_data, graphdata=graphdata) return render_template("scoreboard.html", data=scoreboard_data, graphdata=graphdata)

16
routes/shell.py Normal file
View File

@ -0,0 +1,16 @@
from flask import Blueprint, g, render_template
from data import ssh
from utils import decorators
shell = Blueprint("shell", __name__, template_folder="../templates/shell")
@shell.route('/shell/')
@decorators.must_be_allowed_to("access shell")
@decorators.competition_running_required
@decorators.confirmed_email_required
def index():
account = ssh.get_team_account(g.team)
return render_template("shell.html", account=account)

View File

@ -17,7 +17,7 @@ teams = Blueprint("teams", __name__, template_folder="../templates/teams")
@ratelimit.ratelimit(limit=6, per=120) @ratelimit.ratelimit(limit=6, per=120)
def dashboard(): def dashboard():
if request.method == "GET": if request.method == "GET":
team_solves = challenge.get_solved(g.team) team_solves = challenge.get_solves(g.team)
team_adjustments = challenge.get_adjustments(g.team) team_adjustments = challenge.get_adjustments(g.team)
team_score = sum([i.challenge.points for i in team_solves] + [i.value for i in team_adjustments]) team_score = sum([i.challenge.points for i in team_solves] + [i.value for i in team_adjustments])
return render_template("team.html", team_solves=team_solves, team_adjustments=team_adjustments, team_score=team_score) return render_template("team.html", team_solves=team_solves, team_adjustments=team_adjustments, team_score=team_score)

View File

@ -30,44 +30,44 @@ def open_ticket():
description = request.form["description"] description = request.form["description"]
t = ticket.create_ticket(g.team, summary, description) t = ticket.create_ticket(g.team, summary, description)
flash("Ticket #{} opened.".format(t.id)) flash("Ticket #{} opened.".format(t.id))
return redirect(url_for(".detail", ticket=t.id)) return redirect(url_for(".detail", ticket_id=t.id))
@tickets.route('/tickets/<int:ticket>/') @tickets.route('/tickets/<int:ticket_id>/')
@decorators.must_be_allowed_to("view tickets") @decorators.must_be_allowed_to("view tickets")
@decorators.login_required @decorators.login_required
def detail(ticket): def detail(ticket_id):
try: try:
ticket = ticket.get_ticket(g.team, ticket) t = ticket.get_ticket(g.team, ticket_id)
except exceptions.ValidationError as e: except exceptions.ValidationError as e:
flash(str(e)) flash(str(e))
return redirect(url_for(".index")) return redirect(url_for(".index"))
comments = ticket.get_comments(ticket) comments = ticket.get_comments(t)
return render_template("ticket_detail.html", ticket=ticket, comments=comments) return render_template("ticket_detail.html", ticket=t, comments=comments)
@tickets.route('/tickets/<int:ticket>/comment/', methods=["POST"]) @tickets.route('/tickets/<int:ticket_id>/comment/', methods=["POST"])
@decorators.must_be_allowed_to("comment on tickets") @decorators.must_be_allowed_to("comment on tickets")
@decorators.must_be_allowed_to("view tickets") @decorators.must_be_allowed_to("view tickets")
@ratelimit.ratelimit(limit=1, per=120) @ratelimit.ratelimit(limit=1, per=120)
def comment(ticket): def comment(ticket_id):
try: try:
ticket = ticket.get_ticket(g.team, ticket) t = ticket.get_ticket(g.team, ticket_id)
except exceptions.ValidationError as e: except exceptions.ValidationError as e:
flash(str(e)) flash(str(e))
return redirect(url_for(".index")) return redirect(url_for(".index"))
if request.form["comment"]: if request.form["comment"]:
ticket.create_comment(ticket, g.user, request.form["comment"]) ticket.create_comment(t, g.user, request.form["comment"])
flash("Comment added.") flash("Comment added.")
if ticket.active and "resolved" in request.form: if t.active and "resolved" in request.form:
ticket.close_ticket(ticket) ticket.close_ticket(t)
flash("Ticket closed.") flash("Ticket closed.")
elif not ticket.active and "resolved" not in request.form: elif not t.active and "resolved" not in request.form:
ticket.open_ticket(ticket) ticket.open_ticket(t)
flash("Ticket re-opened.") flash("Ticket re-opened.")
return redirect(url_for(".detail", ticket=ticket.id)) return redirect(url_for(".detail", ticket_id=t.id))

View File

@ -68,7 +68,7 @@ def register():
if join_team: if join_team:
team_key = request.form["team_key"].strip() team_key = request.form["team_key"].strip()
t = team.get_team(key=team_key) t = team.get_team(key=team_key)
if not team: if not t:
flash("This team could not be found, check your team key.") flash("This team could not be found, check your team key.")
return redirect(url_for('.register')) return redirect(url_for('.register'))
else: else:

View File

@ -170,6 +170,25 @@ body {
margin-left: -40px; margin-left: -40px;
} }
code, kbd, pre, samp {
font-family: Menlo,Monaco,Consolas,"Courier New",monospace;
}
.challenge code {
padding: 2px 4px;
font-size: 90%;
color: #c7254e;
background-color: #f9f2f4;
border-radius: 4px;
}
.shell-frame {
border-width: 0px;
width: 100%;
height: calc(100% - 64px);
}
/* MEDIUM */ /* MEDIUM */
@media only screen and (max-width : 992px) { @media only screen and (max-width : 992px) {
.cont { .cont {

View File

@ -19,7 +19,7 @@
<a href="{{ url_for('admin.admin_show_team', tid=team.id) }}">{{ team.name }}</a> <a href="{{ url_for('admin.admin_show_team', tid=team.id) }}">{{ team.name }}</a>
</td> </td>
<td>{{ team.affiliation }}</td> <td>{{ team.affiliation }}</td>
<td>{{ "Eligible" if team.eligible else "Ineligible" }}</td> <td>{{ "Eligible" if team.eligible() else "Ineligible" }}</td>
<td><abbr class="time" title="{{ lastsolvedata[team.id] }}">{{ lastsolvedata[team.id] }}</abbr></td> <td><abbr class="time" title="{{ lastsolvedata[team.id] }}">{{ lastsolvedata[team.id] }}</abbr></td>
<td>{{ scoredata[team.id] }}</td> <td>{{ scoredata[team.id] }}</td>
</tr> </tr>

View File

@ -3,8 +3,7 @@
<h2>{{ team.name }}</h2> <h2>{{ team.name }}</h2>
<a href="{{ url_for('admin.admin_impersonate_team', tid=team.id, csrf=csrf_token()) }}">Impersonate team</a><br /> <a href="{{ url_for('admin.admin_impersonate_team', tid=team.id, csrf=csrf_token()) }}">Impersonate team</a><br />
<p> <p>
This team is <strong>{{ "eligible" if team.eligible else "not eligible" }}</strong> (<a href="{{ url_for('admin.admin_toggle_eligibility', tid=team.id, csrf=csrf_token()) }}">toggle</a>). This team is <strong>{{ "eligible" if team.eligible() else "not eligible" }}</strong> (<a href="{{ url_for('admin.admin_toggle_eligibility', tid=team.id, csrf=csrf_token()) }}">toggle</a>).
Eligibility is <strong>{{ "locked" if team.eligibility_locked else "unlocked" }}</strong> (<a href="{{ url_for('admin.admin_toggle_eligibility_lock', tid=team.id, csrf=csrf_token()) }}">toggle</a>).
</p> </p>
<p>This team's affiliation is <strong>{{ team.affiliation }}</strong></p> <p>This team's affiliation is <strong>{{ team.affiliation }}</strong></p>
<h3>Email</h3> <h3>Email</h3>

View File

@ -48,7 +48,7 @@
</nav> </nav>
<ul id="nav-mobile" class="side-nav fixed"> <ul id="nav-mobile" class="side-nav fixed">
{% if config.competition_is_running() %} {% if config.competition_is_running() or session.admin %}
{% if logged_in %} {% if logged_in %}
<li><a href="{{ url_for('challenges.index') }}"> <li><a href="{{ url_for('challenges.index') }}">
<div class="side-icon"><i class="material-icons blue-text">flag</i></div> <div class="side-icon"><i class="material-icons blue-text">flag</i></div>
@ -59,9 +59,9 @@
<div class="side-icon"><i class="material-icons red-text">timeline</i></div> <div class="side-icon"><i class="material-icons red-text">timeline</i></div>
<div class="side-text">Scoreboard</div> <div class="side-text">Scoreboard</div>
</a></li> </a></li>
<li><a href="{{ url_for('chat') }}"> <li><a href="{{ url_for('shell.index') }}">
<div class="side-icon"><i class="material-icons purple-text">build</i></div> <div class="side-icon"><i class="material-icons purple-text">computer</i></div>
<div class="side-text">Status</div> <div class="side-text">Shell</div>
</a></li> </a></li>
{% endif %} {% endif %}
{% if logged_in %} {% if logged_in %}

View File

@ -1,60 +1,59 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Challenges{% endblock %} {% block title %}Challenges{% endblock %}
{% block head %} {% block head %}
<script>
var state = !{{ solved.count() }};
function openAll() {
$(".collapsible-header").each(function(i, x){ $(x).hasClass("active") || $(x).click(); });
$("#toggleState").html("Collapse all challenges.");
}
function closeAll() {
$(".collapsible-header").each(function(i, x){ $(x).hasClass("active") && $(x).click(); });
$("#toggleState").html("Expand all challenges.");
}
function toggle() {
if(state) closeAll();
else openAll();
state = !state;
}
function filterCategories(t) {
var v = t.options[t.selectedIndex].value;
if(v == "*")
$(".challenge").show();
else {
$(".challenge[data-category=" + v + "]").show();
$(".challenge[data-category!=" + v + "]").hide();
}
}
</script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<section>
<select onchange="filterCategories(this);"> <div class="row">
<option value="*">Show all</option> <div class="col s12">
{% for category in categories %} <div class="card purple darken-1">
<option>{{ category }}</option> <div class="card-content white-text">
{% endfor %} Due to way more users than expected, the platform and some problems may not be stable. Please report all errors encountered in a ticket or on IRC, and be patient :).
</select> </div>
<span class="left"><a href="javascript:toggle()" id="toggleState">{% if solved.count() %}Expand all challenges.{% else %}Collapse all challenges.{% endif %}</a></span> </div>
<br /> </div>
</div>
<ul class="collapsible popout" data-collapsible="accordion"> </section>
{% for challenge in challenges %} <div class="row">
<li class="challenge" data-category="{{ challenge.category }}"> <div class="col s6">
<div id="header{{ challenge.id }}" class="collapsible-header"> <div class="left">
<select onchange="filterCategories(this);">
<option value="*">Show all</option>
{% for category in categories %}
<option>{{ category }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col s6">
<span class="right"><a class="btn" href="#" id="toggleState">Collapse all challenges</a></span>
</div>
</div>
{% for stage in stages %}
<hr />
{% if stage.description %}
<p>{{stage.description}}</p>
{% endif %}
<span class="left"><h4>{{ stage.name }}</h4></span>
<span class="right"><a class="stage-collapse" data-collapse="{{ stage.alias }}" href="#">Toggle</a></span>
<div class="clearfix"></div>
<ul class="collapsible popout" data-collapsible="expandable">
{% for challenge in challenges[stage.id] %}
<li class="challenge" data-category="{{ challenge.category }}" data-stage="{{stage.alias}}">
<div id="header-{{ challenge.alias }}" data-challenge="{{ challenge.alias }}" class="collapsible-header">
<div class="status-dot green circle tooltipped" data-position="right" data-tooltip="This challenge is Online"></div> <div class="status-dot green circle tooltipped" data-position="right" data-tooltip="This challenge is Online"></div>
<div class="challenge-text truncate">{{ challenge.name }}</div> <div class="challenge-text truncate">{{ challenge.name }}</div>
<span class="right"> <span class="right">
<span>{{ challenge.author }}</span> <span>{{ challenge.author }}</span>
<b>&middot;</b> <b>&middot;</b>
<span id="solves{{ challenge.id }}">{{ solves[challenge.id] }}</span> {% if solves[challenge.id] == 1 %}solve{% else %}solves{% endif %} <span id="solves-{{ challenge.alias }}">{{ solves[challenge.id] }}</span> {% if solves[challenge.id] == 1 %}solve{% else %}solves{% endif %}
<b>&middot;</b> <b>&middot;</b>
{{ challenge.category }} {{ challenge.category }}
<b>&middot;</b> <b>&middot;</b>
{{ challenge.points }} pt {{ challenge.points }} pt
{% if challenge in solved %} {% if challenge in solved %}
<i id="check{{ challenge.id }}" class="material-icons green-text right check-icon">done</i> <i id="check-{{ challenge.alias }}" class="material-icons green-text right check-icon">done</i>
{% else %} {% else %}
<div class="check-pad"></div> <div class="check-pad"></div>
{% endif %} {% endif %}
@ -63,19 +62,19 @@
<div class="collapsible-body"> <div class="collapsible-body">
<p>{{ challenge.description | safe }} <p>{{ challenge.description | safe }}
{% if challenge in solved %} {% if challenge in solved %}
<br /><br /><strong>You've solved this challenge!</strong><br /> <br /><br /><strong>Your team has solved this challenge!</strong><br />
<a href="{{ url_for('.show_solves', challenge_id=challenge.id) }}">View solves</a> <a href="{{ url_for('.show_solves', challenge_id=challenge.alias) }}">View solves</a>
</p> </p>
{% else %} {% else %}
<br /><br /> <br /><br />
<a href="{{ url_for('.show_solves', challenge_id=challenge.id) }}">View solves</a> <a href="{{ url_for('.show_solves', challenge_id=challenge.alias) }}">View solves</a>
</p> </p>
<form class="flag-form" action="{{ url_for('.submit', challenge_id=challenge.id) }}" data-challengeid="{{ challenge.id }}" method="POST"> <form class="flag-form" action="{{ url_for('.submit', challenge_id=challenge.alias) }}" data-challenge="{{ challenge.alias }}" method="POST">
<div class="row no-bot"> <div class="row no-bot">
<div class="col s12 m10"> <div class="col s12 m10">
<div class="input-field"> <div class="input-field">
<input required id="flag{{ challenge.id }}" name="flag" type="text" /> <input required id="flag-{{ challenge.alias }}" name="flag" type="text" />
<label for="flag{{ challenge.id }}">Flag</label> <label for="flag-{{ challenge.alias }}">Flag</label>
</div> </div>
</div> </div>
<div class="col s12 m2"> <div class="col s12 m2">
@ -88,6 +87,7 @@
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endfor %}
{% endblock %} {% endblock %}
{% block postscript %} {% block postscript %}
<script> <script>
@ -97,17 +97,77 @@
</script> </script>
{% if config.apisubmit %} {% if config.apisubmit %}
<script> <script>
var challenges_open = JSON.parse(localStorage.getItem("challenges-open")) || {{ first_stage | tojson }};
Object.keys(challenges_open).forEach(function(el){
$(".collapsible-header#header-" + el).addClass("active");
});
$(".collapsible-header").on("click", function(){
var chall = $(this).data('challenge');
if(challenges_open.hasOwnProperty(chall)) {
delete challenges_open[chall];
} else {
challenges_open[chall] = true;
}
localStorage.setItem("challenges-open", JSON.stringify(challenges_open));
});
var state = $(".collapsible-header.active").length == 0;
function updateButton(){
if(state){
$("#toggleState").html("Expand all challenges");
} else {
$("#toggleState").html("Collapse all challenges");
}
}
updateButton(state);
function toggle() {
if(state) {
$(".collapsible-header").each(function(i, x){ $(x).hasClass("active") || $(x).click(); });
} else {
$(".collapsible-header").each(function(i, x){ $(x).hasClass("active") && $(x).click(); });
}
state = !state;
updateButton(state);
}
function filterCategories(t) {
var v = t.options[t.selectedIndex].value;
if(v == "*")
$(".challenge").show();
else {
$(".challenge[data-category=" + v + "]").show();
$(".challenge[data-category!=" + v + "]").hide();
}
}
$("#toggleState").on("click", function(e){
e.preventDefault();
toggle();
});
$(".stage-collapse").on("click", function(e){
e.preventDefault();
var stage = $(this).data('collapse');
var header_selector = ".challenge[data-stage=" + stage + "] .collapsible-header";
var state = $(header_selector + ".active").length != 0;
if(state)
$(header_selector).each(function(i, x){ $(x).hasClass("active") && $(x).click(); });
else
$(header_selector).each(function(i, x){ $(x).hasClass("active") || $(x).click(); });
});
$("form").submit(function(e) { $("form").submit(function(e) {
var id = $(this).attr("data-challengeid"); var id = $(this).data("challenge");
api.makeCall("/submit/" + id + ".json", {flag: $("#flag" + id).val(), _csrf_token: "{{ csrf_token() }}"}, function(data) { var flag = $("#flag-" + id).val();
console.log(id);
console.log(flag);
api.makeCall("/submit/" + id + ".json", {flag: flag, _csrf_token: "{{ csrf_token() }}"}, function(data) {
if(data.code) { if(data.code) {
Materialize.toast(data.message, 4000); Materialize.toast(data.message, 4000);
} }
else { else {
Materialize.toast("Flag accepted!", 4000); Materialize.toast("Flag accepted!", 4000);
$("#check" + id).show(); $("#check-" + id).show();
$("#header" + id).click(); $("#header-" + id).click();
$("#solves" + id).html(parseInt($("#solves" + id).html()) + 1); $("#solves-" + id).html(data.solves);
} }
}); });
return false; return false;

View File

@ -0,0 +1,21 @@
{% extends "base.html" %}
{% block content %}
{% if account %}
<div class="card orange darken-2">
<div class="card-content white-text">
<p class="card-title">Username: {{account.username}} Password: {{account.password}}</p>
<code> ssh {{account.username}}@{{account.hostname}} -p {{account.port}}</code>
</div>
</div>
<div class="center" style="margin-top: 50px;">
<iframe class="shell-frame" src="https://shell.icec.tf/wetty" frameBorder="0"></iframe>
</div>
</div>
{% else %}
<div class="card red darken-2">
<div class="card-content white-text">
<p class="card-title">You don't have an account yet! Don't worry, one will be created shortly.</p>
</div>
</div>
{% endif %}
{% endblock %}

View File

@ -177,6 +177,4 @@
{% endblock %} {% endblock %}
{% block postscript %} {% block postscript %}
<script>$('.modal-trigger').leanModal();</script> <script>$('.modal-trigger').leanModal();</script>
{% endif %}
<script>
{% endblock %} {% endblock %}

View File

@ -9,7 +9,7 @@
<small><abbr class="time" title="{{ comment.time }}">{{ comment.time }}</abbr> &middot; {{ comment.comment_by }}</small> <small><abbr class="time" title="{{ comment.time }}">{{ comment.time }}</abbr> &middot; {{ comment.comment_by }}</small>
{% endfor %} {% endfor %}
<br /> <br />
<form action="{{ url_for('.comment', ticket=ticket.id) }}" method="POST"> <form action="{{ url_for('.comment', ticket_id=ticket.id) }}" method="POST">
<div class="input-field"> <div class="input-field">
<textarea id="comment" name="comment" class="materialize-textarea"></textarea> <textarea id="comment" name="comment" class="materialize-textarea"></textarea>
<label for="comment">Comment</label> <label for="comment">Comment</label>

View File

@ -7,7 +7,7 @@ You have the following open tickets. If you're having an issue, you can <a href=
<div class="collection"> <div class="collection">
{% for ticket in tickets %} {% for ticket in tickets %}
{% if ticket.active %} {% if ticket.active %}
<a class="collection-item" href="{{ url_for('.detail', ticket=ticket.id) }}">#{{ ticket.id }} {{ ticket.summary }}</a> <a class="collection-item" href="{{ url_for('.detail', ticket_id=ticket.id) }}">#{{ ticket.id }} {{ ticket.summary }}</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
@ -16,7 +16,7 @@ You have the following closed tickets:
<div class="collection"> <div class="collection">
{% for ticket in tickets %} {% for ticket in tickets %}
{% if not ticket.active %} {% if not ticket.active %}
<a class="collection-item" href="{{ url_for('.detail', ticket=ticket.id) }}">#{{ ticket.id }} {{ ticket.summary }}</a> <a class="collection-item" href="{{ url_for('.detail', ticket_id=ticket.id) }}">#{{ ticket.id }} {{ ticket.summary }}</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>

View File

@ -9,7 +9,7 @@ def login_required(f):
return f(*args, **kwargs) return f(*args, **kwargs)
else: else:
flash("You need to be logged in") flash("You need to be logged in")
return redirect(url_for('login')) return redirect(url_for('users.login'))
return decorated return decorated
def must_be_allowed_to(thing): def must_be_allowed_to(thing):
@ -17,7 +17,7 @@ def must_be_allowed_to(thing):
@wraps(f) @wraps(f)
def decorated(*args, **kwargs): def decorated(*args, **kwargs):
if getattr(g, 'user_restricts', None) is None: if getattr(g, 'user_restricts', None) is None:
return redirect(url_for('login')) return redirect(url_for('users.login'))
if g.user_restricts and thing in g.user_restricts: if g.user_restricts and thing in g.user_restricts:
return "You are restricted from performing the {} action. Contact an organizer.".format(thing) return "You are restricted from performing the {} action. Contact an organizer.".format(thing)
@ -31,20 +31,20 @@ def confirmed_email_required(f):
if "user_id" in session and session["user_id"]: if "user_id" in session and session["user_id"]:
if not g.user.email_confirmed: if not g.user.email_confirmed:
flash("Please confirm your email") flash("Please confirm your email")
return redirect(url_for('user_dashboard')) return redirect(url_for('users.dashboard'))
else: else:
return f(*args, **kwargs) return f(*args, **kwargs)
else: else:
flash("You need to be logged in to access that page.") flash("You need to be logged in to access that page.")
return redirect(url_for('login')) return redirect(url_for('users.login'))
return decorated return decorated
def competition_running_required(f): def competition_running_required(f):
@wraps(f) @wraps(f)
def decorated(*args, **kwargs): def decorated(*args, **kwargs):
if not config.competition_is_running(): if not config.competition_is_running() and not ("admin" in session and session["admin"]):
flash("The competition hasn't started") flash("The competition hasn't started")
return redirect(url_for('scoreboard')) return redirect(url_for('scoreboard.index'))
return f(*args, **kwargs) return f(*args, **kwargs)
return decorated return decorated

View File

@ -18,7 +18,7 @@ class RateLimit(object):
self.current = min(p.execute()[0], limit) self.current = min(p.execute()[0], limit)
remaining = property(lambda x: x.limit - x.current) remaining = property(lambda x: x.limit - x.current)
over_limit = property(lambda x: x.current >= x.limit) over_limit = property(lambda x: x.current > x.limit)
def get_view_rate_limit(): def get_view_rate_limit():