moved so much stuff around and rewrote the api
parent
e8ecccc7d0
commit
9a7fb14f56
19
2
19
2
|
@ -1,19 +0,0 @@
|
|||
from app import app, url_for
|
||||
app.config["SERVER_NAME"] = "server"
|
||||
|
||||
with app.app_context():
|
||||
import urllib
|
||||
output = []
|
||||
for rule in app.url_map.iter_rules():
|
||||
|
||||
options = {}
|
||||
for arg in rule.arguments:
|
||||
options[arg] = "[{0}]".format(arg)
|
||||
|
||||
methods = ','.join(rule.methods)
|
||||
url = url_for(rule.endpoint, **options)
|
||||
line = urllib.unquote("{:50s} {:20s} {}".format(rule.endpoint, methods, url))
|
||||
output.append(line)
|
||||
|
||||
for line in sorted(output):
|
||||
print(line)
|
555
app.py
555
app.py
|
@ -1,43 +1,55 @@
|
|||
from flask import Flask, render_template, session, redirect, url_for, request, g, flash, jsonify
|
||||
app = Flask(__name__)
|
||||
|
||||
from database import User, Team, UserAccess, Challenge, ChallengeSolve, ChallengeFailure, ScoreAdjustment, TroubleTicket, TicketComment, Notification, db
|
||||
from datetime import datetime, timedelta
|
||||
from peewee import fn
|
||||
|
||||
from utils import decorators, flag, cache, misc, captcha, email, select
|
||||
import utils.scoreboard
|
||||
import redis
|
||||
import socket
|
||||
import logging
|
||||
|
||||
import config
|
||||
import utils
|
||||
import redis
|
||||
import requests
|
||||
import socket
|
||||
|
||||
app.secret_key = config.secret.key
|
||||
from utils import misc, select
|
||||
|
||||
from data.database import db
|
||||
import data
|
||||
|
||||
# Blueprints
|
||||
from routes import api, admin, teams, users, challenges, tickets, scoreboard
|
||||
|
||||
import logging
|
||||
if config.production:
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
else:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = config.secret.key
|
||||
|
||||
|
||||
app.register_blueprint(api.api)
|
||||
app.register_blueprint(admin.admin)
|
||||
app.register_blueprint(teams.teams)
|
||||
app.register_blueprint(users.users)
|
||||
app.register_blueprint(challenges.challenges)
|
||||
app.register_blueprint(tickets.tickets)
|
||||
app.register_blueprint(scoreboard.scoreboard)
|
||||
|
||||
|
||||
@app.before_request
|
||||
def make_info_available():
|
||||
if "user_id" in session:
|
||||
g.logged_in = True
|
||||
try:
|
||||
g.user = User.get(User.id == session["user_id"])
|
||||
current_user = data.user.get_user(id=session["user_id"])
|
||||
if current_user is not None:
|
||||
g.user = current_user
|
||||
g.user_restricts = g.user.restricts.split(",")
|
||||
g.team = g.user.team
|
||||
g.team_restricts = g.team.restricts.split(",")
|
||||
except User.DoesNotExist:
|
||||
else:
|
||||
g.logged_in = False
|
||||
session.pop("user_id")
|
||||
return render_template("login.html")
|
||||
else:
|
||||
g.logged_in = False
|
||||
|
||||
|
||||
@app.context_processor
|
||||
def scoreboard_variables():
|
||||
var = dict(config=config, select=select)
|
||||
|
@ -45,555 +57,64 @@ def scoreboard_variables():
|
|||
var["logged_in"] = True
|
||||
var["user"] = g.user
|
||||
var["team"] = g.team
|
||||
# TODO should this apply to users or teams?
|
||||
# var["notifications"] = Notification.select().where(Notification.user == g.user)
|
||||
var["notifications"] = []
|
||||
var["notifications"] = data.notification.get_notifications()
|
||||
else:
|
||||
var["logged_in"] = False
|
||||
var["notifications"] = []
|
||||
|
||||
return var
|
||||
|
||||
# Blueprints
|
||||
from modules import api, admin
|
||||
app.register_blueprint(api.api)
|
||||
app.register_blueprint(admin.admin)
|
||||
|
||||
# Publically accessible things
|
||||
|
||||
@app.route('/')
|
||||
def root():
|
||||
if g.logged_in:
|
||||
return redirect(url_for('team_dashboard'))
|
||||
return redirect(url_for('register'))
|
||||
return redirect(url_for('team.dashboard'))
|
||||
return redirect(url_for('users.register'))
|
||||
|
||||
|
||||
@app.route('/chat/')
|
||||
def chat():
|
||||
return render_template("chat.html")
|
||||
|
||||
@app.route('/scoreboard/')
|
||||
def scoreboard():
|
||||
data = cache.get_complex("scoreboard")
|
||||
graphdata = cache.get_complex("graph")
|
||||
if data is None or graphdata is None:
|
||||
if config.immediate_scoreboard:
|
||||
data = utils.scoreboard.calculate_scores()
|
||||
graphdata = utils.scoreboard.calculate_graph(data)
|
||||
utils.scoreboard.set_complex("scoreboard", data, 120)
|
||||
utils.scoreboard.set_complex("graph", graphdata, 120)
|
||||
else:
|
||||
return "No scoreboard data available. Please contact an organizer."
|
||||
|
||||
return render_template("scoreboard.html", data=data, graphdata=graphdata)
|
||||
|
||||
@app.route('/login/', methods=["GET", "POST"])
|
||||
def login():
|
||||
if request.method == "GET":
|
||||
return render_template("login.html")
|
||||
elif request.method == "POST":
|
||||
username = request.form["username"]
|
||||
password = request.form["password"]
|
||||
|
||||
try:
|
||||
user = User.get(User.username == username)
|
||||
if(user.checkPassword(password)):
|
||||
UserAccess.create(user=user, ip=misc.get_ip(), time=datetime.now())
|
||||
session["user_id"] = user.id
|
||||
flash("Login successful.")
|
||||
return redirect(url_for('team_dashboard'))
|
||||
else:
|
||||
flash("Incorrect username or password", "error")
|
||||
return render_template("login.html")
|
||||
except User.DoesNotExist:
|
||||
flash("Incorrect username or password", "error")
|
||||
return render_template("login.html")
|
||||
|
||||
@app.route('/register/', methods=["GET", "POST"])
|
||||
def register():
|
||||
if not config.registration:
|
||||
if "admin" in session and session["admin"]:
|
||||
pass
|
||||
else:
|
||||
return "Registration is currently disabled. Email icectf@icec.tf to create an account."
|
||||
|
||||
if request.method == "GET":
|
||||
return render_template("register.html")
|
||||
elif request.method == "POST":
|
||||
error, message = captcha.verify_captcha()
|
||||
if error:
|
||||
flash(message)
|
||||
return render_template("register.html")
|
||||
|
||||
username = request.form["username"].strip()
|
||||
user_email = request.form["email"].strip()
|
||||
password = request.form["password"].strip()
|
||||
confirm_password = request.form["confirm_password"].strip()
|
||||
background = request.form["background"].strip()
|
||||
country = request.form["country"].strip()
|
||||
|
||||
tshirt_size = ""
|
||||
gender = ""
|
||||
if "tshirt_size" in request.form.keys():
|
||||
tshirt_size = request.form["tshirt_size"].strip()
|
||||
if "gender" in request.form.keys():
|
||||
gender = request.form["gender"].strip()
|
||||
|
||||
join_team = bool(int(request.form["join_team"].strip()))
|
||||
if join_team:
|
||||
team_key = request.form["team_key"].strip()
|
||||
else:
|
||||
team_name = request.form["team_name"].strip()
|
||||
team_affiliation = request.form["team_affiliation"].strip()
|
||||
|
||||
if len(username) > 50 or not username:
|
||||
flash("You must have a username!")
|
||||
return render_template("register.html")
|
||||
|
||||
try:
|
||||
user = User.get(User.username == username)
|
||||
flash("This username is already in use!")
|
||||
return render_template("register.html")
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
|
||||
if password != confirm_password:
|
||||
flash("Password does not match confirmation")
|
||||
return render_template("register.html")
|
||||
|
||||
if not (user_email and "." in user_email and "@" in user_email):
|
||||
flash("You must have a valid email!")
|
||||
return render_template("register.html")
|
||||
|
||||
if not email.is_valid_email(user_email):
|
||||
flash("You're lying")
|
||||
return render_template("register.html")
|
||||
|
||||
if (not tshirt_size == "") and (not tshirt_size in select.TShirts):
|
||||
flash("Invalid T-shirt size")
|
||||
return render_template("register.html")
|
||||
|
||||
if not background in select.BackgroundKeys:
|
||||
flash("Invalid Background")
|
||||
return render_template("register.html")
|
||||
|
||||
if not country in select.CountryKeys:
|
||||
flash("Invalid Background")
|
||||
return render_template("register.html")
|
||||
|
||||
if (not gender == "") and (not gender in ["M", "F"]):
|
||||
flash("Invalid gender")
|
||||
return render_template("register.html")
|
||||
|
||||
confirmation_key = misc.generate_confirmation_key()
|
||||
|
||||
team=None
|
||||
if join_team:
|
||||
try:
|
||||
team = Team.get(Team.key == team_key)
|
||||
except Team.DoesNotExist:
|
||||
flash("Couldn't find this team, check your team key.")
|
||||
return render_template("register.html")
|
||||
else:
|
||||
if not team_name or len(team_name) > 50:
|
||||
flash("Missing team name")
|
||||
return render_template("register.html")
|
||||
if not team_affiliation or len(team_affiliation) > 100:
|
||||
team_affiliation = "No affiliation"
|
||||
try:
|
||||
team = Team.get(Team.name == team_name)
|
||||
flash("This team name is already in use!")
|
||||
return render_template("register.html")
|
||||
except Team.DoesNotExist:
|
||||
pass
|
||||
team_key = misc.generate_team_key()
|
||||
team = Team.create(name=team_name, affiliation=team_affiliation, key=team_key)
|
||||
|
||||
|
||||
user = User.create(username=username, email=user_email,
|
||||
background=background, country=country,
|
||||
tshirt_size=tshirt_size, gender=gender,
|
||||
email_confirmation_key=confirmation_key,
|
||||
team=team)
|
||||
user.setPassword(password)
|
||||
user.save()
|
||||
|
||||
UserAccess.create(user=user, ip=misc.get_ip(), time=datetime.now())
|
||||
# print(confirmation_key)
|
||||
|
||||
email.send_confirmation_email(user_email, confirmation_key)
|
||||
|
||||
session["user_id"] = user.id
|
||||
flash("Registration finished")
|
||||
return redirect(url_for('user_dashboard'))
|
||||
|
||||
@app.route('/logout/')
|
||||
def logout():
|
||||
session.pop("user_id")
|
||||
flash("You've successfully logged out.")
|
||||
return redirect(url_for('login'))
|
||||
|
||||
# Things that require a team
|
||||
|
||||
@app.route('/confirm_email/<confirmation_key>', methods=["GET"])
|
||||
@decorators.login_required
|
||||
def confirm_email(confirmation_key):
|
||||
if confirmation_key == g.user.email_confirmation_key:
|
||||
flash("Email confirmed!")
|
||||
g.user.email_confirmed = True
|
||||
g.user.save()
|
||||
else:
|
||||
flash("Incorrect confirmation key.")
|
||||
return redirect(url_for('user_dashboard'))
|
||||
|
||||
@app.route('/forgot_password/', methods=["GET", "POST"])
|
||||
def forgot_password():
|
||||
if request.method == "GET":
|
||||
return render_template("forgot_password.html")
|
||||
elif request.method == "POST":
|
||||
username = request.form["username"].strip()
|
||||
if len(username) > 50 or not username:
|
||||
flash("You must have a username!")
|
||||
return redirect(url_for('forgot_password'))
|
||||
|
||||
try:
|
||||
user = User.get(User.username == username)
|
||||
user.password_reset_token = misc.generate_confirmation_key()
|
||||
user.password_reset_expired = datetime.now() + timedelta(days=1)
|
||||
user.save()
|
||||
email.send_password_reset_email(user.email, user.password_reset_token)
|
||||
flash("Forgot password email sent! Check your email.")
|
||||
return render_template("forgot_password.html")
|
||||
except User.DoesNotExist:
|
||||
flash("Username is not registered", "error")
|
||||
return render_template("forgot_password.html")
|
||||
|
||||
@app.route('/reset_password/<password_reset_token>', methods=["GET", "POST"])
|
||||
def reset_password(password_reset_token):
|
||||
if request.method == "GET":
|
||||
return render_template("reset_password.html")
|
||||
elif request.method == "POST":
|
||||
password = request.form["password"].strip()
|
||||
confirm_password = request.form["confirm_password"].strip()
|
||||
|
||||
if not password == confirm_password:
|
||||
flash("Password does not match")
|
||||
return render_template("reset_password.html", password_reset_token=password_reset_token)
|
||||
|
||||
if not password_reset_token:
|
||||
flash("Reset Token is invalid", "error")
|
||||
return redirect(url_for("forgot_password"))
|
||||
|
||||
try:
|
||||
user = User.get(User.password_reset_token == password_reset_token)
|
||||
if user.password_reset_expired < datetime.now():
|
||||
flash("Token expired")
|
||||
return redirect(url_for("forgot_password"))
|
||||
|
||||
user.setPassword(password)
|
||||
user.password_reset_token = None
|
||||
user.save()
|
||||
flash("Password successfully reset")
|
||||
return redirect(url_for("login"))
|
||||
except User.DoesNotExist:
|
||||
flash("Reset Token is invalid", "error")
|
||||
return redirect(url_for("forgot_password"))
|
||||
|
||||
@app.route('/user/', methods=["GET", "POST"])
|
||||
@decorators.login_required
|
||||
def user_dashboard():
|
||||
if request.method == "GET":
|
||||
first_login = False
|
||||
if g.user.first_login:
|
||||
first_login = True
|
||||
g.user.first_login = False
|
||||
g.user.save()
|
||||
return render_template("user.html", first_login=first_login)
|
||||
elif request.method == "POST":
|
||||
if g.redis.get("ul{}".format(session["user_id"])):
|
||||
flash("You're changing your information too fast!")
|
||||
return redirect(url_for('user_dashboard'))
|
||||
|
||||
username = request.form["username"].strip()
|
||||
user_email = request.form["email"].strip()
|
||||
password = request.form["password"].strip()
|
||||
confirm_password = request.form["confirm_password"].strip()
|
||||
background = request.form["background"].strip()
|
||||
country = request.form["country"].strip()
|
||||
|
||||
tshirt_size = ""
|
||||
gender = ""
|
||||
if "tshirt_size" in request.form.keys():
|
||||
tshirt_size = request.form["tshirt_size"].strip()
|
||||
if "gender" in request.form.keys():
|
||||
gender = request.form["gender"].strip()
|
||||
|
||||
if len(username) > 50 or not username:
|
||||
flash("You must have a username!")
|
||||
return redirect(url_for('user_dashboard'))
|
||||
if g.user.username != username:
|
||||
try:
|
||||
user = User.get(User.username == username)
|
||||
flash("This username is already in use!")
|
||||
return redirect(url_for('user_dashboard'))
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
|
||||
if not (user_email and "." in user_email and "@" in user_email):
|
||||
flash("You must have a valid email!")
|
||||
return redirect(url_for('user_dashboard'))
|
||||
|
||||
if not email.is_valid_email(user_email):
|
||||
flash("You're lying")
|
||||
return redirect(url_for('user_dashboard'))
|
||||
|
||||
if (not tshirt_size == "") and (not tshirt_size in select.TShirts):
|
||||
flash("Invalid T-shirt size")
|
||||
return redirect(url_for('user_dashboard'))
|
||||
|
||||
if not background in select.BackgroundKeys:
|
||||
flash("Invalid Background")
|
||||
return redirect(url_for('user_dashboard'))
|
||||
|
||||
if not country in select.CountryKeys:
|
||||
flash("Invalid Background")
|
||||
return redirect(url_for('user_dashboard'))
|
||||
|
||||
if (not gender == "") and (not gender in ["M", "F"]):
|
||||
flash("Invalid gender")
|
||||
return redirect(url_for('user_dashboard'))
|
||||
|
||||
email_changed = (user_email != g.user.email)
|
||||
|
||||
g.user.username = username
|
||||
g.user.email = user_email
|
||||
g.user.background = background
|
||||
g.user.country = country
|
||||
g.user.gender = gender
|
||||
g.user.tshirt_size = tshirt_size
|
||||
|
||||
g.redis.set("ul{}".format(session["user_id"]), str(datetime.now()), 120)
|
||||
|
||||
if password != "":
|
||||
if password != confirm_password:
|
||||
flash("Password does not match confirmation")
|
||||
return redirect(url_for('user_dashboard'))
|
||||
g.user.setPassword(password)
|
||||
|
||||
if email_changed:
|
||||
g.user.email_confirmation_key = misc.generate_confirmation_key()
|
||||
g.user.email_confirmed = False
|
||||
|
||||
email.send_confirmation_email(user_email, g.user.email_confirmation_key)
|
||||
flash("Changes saved. Please check your email for a new confirmation key.")
|
||||
else:
|
||||
flash("Changes saved.")
|
||||
g.user.save()
|
||||
|
||||
|
||||
return redirect(url_for('user_dashboard'))
|
||||
@app.route('/team/', methods=["GET", "POST"])
|
||||
@decorators.login_required
|
||||
def team_dashboard():
|
||||
if request.method == "GET":
|
||||
team_solves = ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge).where(ChallengeSolve.team == g.team)
|
||||
team_adjustments = ScoreAdjustment.select().where(ScoreAdjustment.team == g.team)
|
||||
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)
|
||||
elif request.method == "POST":
|
||||
if g.redis.get("ul{}".format(session["user_id"])):
|
||||
flash("You're changing your information too fast!")
|
||||
return redirect(url_for('team_dashboard'))
|
||||
|
||||
team_name = request.form["team_name"].strip()
|
||||
affiliation = request.form["team_affiliation"].strip()
|
||||
|
||||
if len(team_name) > 50 or not team_name:
|
||||
flash("You must have a team name!")
|
||||
return redirect(url_for('team_dashboard'))
|
||||
|
||||
if not affiliation or len(affiliation) > 100:
|
||||
affiliation = "No affiliation"
|
||||
|
||||
if g.team.name != team_name:
|
||||
try:
|
||||
team = Team.get(Team.name == team_name)
|
||||
flash("This team name is already in use!")
|
||||
return redirect(url_for('team_dashboard'))
|
||||
except Team.DoesNotExist:
|
||||
pass
|
||||
|
||||
g.team.name = team_name
|
||||
g.team.affiliation = affiliation
|
||||
|
||||
g.redis.set("ul{}".format(session["user_id"]), str(datetime.now()), 120)
|
||||
|
||||
flash("Changes saved.")
|
||||
g.team.save()
|
||||
|
||||
|
||||
return redirect(url_for('team_dashboard'))
|
||||
|
||||
@app.route('/teamconfirm/', methods=["POST"])
|
||||
def teamconfirm():
|
||||
if utils.misc.get_ip() in config.confirm_ip:
|
||||
team_name = request.form["team_name"].strip()
|
||||
team_key = request.form["team_key"].strip()
|
||||
try:
|
||||
team = Team.get(Team.name == team_name)
|
||||
except Team.DoesNotExist:
|
||||
return "invalid", 403
|
||||
if team.key == team_key:
|
||||
return "ok", 200
|
||||
else:
|
||||
return "invalid", 403
|
||||
else:
|
||||
return "unauthorized", 401
|
||||
|
||||
@app.route('/challenges/')
|
||||
@decorators.must_be_allowed_to("view challenges")
|
||||
@decorators.competition_running_required
|
||||
@decorators.confirmed_email_required
|
||||
def challenges():
|
||||
chals = Challenge.select().order_by(Challenge.points, Challenge.name)
|
||||
solved = Challenge.select().join(ChallengeSolve).where(ChallengeSolve.team == g.team)
|
||||
solves = {i: int(g.redis.hget("solves", i).decode()) for i in [k.id for k in chals]}
|
||||
categories = sorted(list({chal.category for chal in chals}))
|
||||
return render_template("challenges.html", challenges=chals, solved=solved, categories=categories, solves=solves)
|
||||
|
||||
@app.route('/challenges/<int:challenge>/solves/')
|
||||
@decorators.must_be_allowed_to("view challenge solves")
|
||||
@decorators.must_be_allowed_to("view challenges")
|
||||
@decorators.competition_running_required
|
||||
@decorators.confirmed_email_required
|
||||
def challenge_show_solves(challenge):
|
||||
chal = Challenge.get(Challenge.id == challenge)
|
||||
solves = ChallengeSolve.select(ChallengeSolve, Team).join(Team).order_by(ChallengeSolve.time).where(ChallengeSolve.challenge == chal)
|
||||
return render_template("challenge_solves.html", challenge=chal, solves=solves)
|
||||
|
||||
@app.route('/submit/<int:challenge>/', methods=["POST"])
|
||||
@decorators.must_be_allowed_to("solve challenges")
|
||||
@decorators.must_be_allowed_to("view challenges")
|
||||
@decorators.competition_running_required
|
||||
@decorators.confirmed_email_required
|
||||
def submit(challenge):
|
||||
chal = Challenge.get(Challenge.id == challenge)
|
||||
flagval = request.form["flag"]
|
||||
|
||||
code, message = flag.submit_flag(g.team, chal, flagval)
|
||||
flash(message)
|
||||
return redirect(url_for('challenges'))
|
||||
|
||||
# Trouble tickets
|
||||
|
||||
@app.route('/tickets/')
|
||||
@decorators.must_be_allowed_to("view tickets")
|
||||
@decorators.login_required
|
||||
def team_tickets():
|
||||
return render_template("tickets.html", tickets=list(g.team.tickets))
|
||||
|
||||
@app.route('/tickets/new/', methods=["GET", "POST"])
|
||||
@decorators.must_be_allowed_to("submit tickets")
|
||||
@decorators.must_be_allowed_to("view tickets")
|
||||
@decorators.login_required
|
||||
def open_ticket():
|
||||
if request.method == "GET":
|
||||
return render_template("open_ticket.html")
|
||||
elif request.method == "POST":
|
||||
if g.redis.get("ticketl{}".format(session["user_id"])):
|
||||
return "You're doing that too fast."
|
||||
g.redis.set("ticketl{}".format(g.team.id), "1", 30)
|
||||
summary = request.form["summary"]
|
||||
description = request.form["description"]
|
||||
opened_at = datetime.now()
|
||||
ticket = TroubleTicket.create(team=g.team, summary=summary, description=description, opened_at=opened_at)
|
||||
flash("Ticket #{} opened.".format(ticket.id))
|
||||
return redirect(url_for("team_ticket_detail", ticket=ticket.id))
|
||||
|
||||
@app.route('/tickets/<int:ticket>/')
|
||||
@decorators.must_be_allowed_to("view tickets")
|
||||
@decorators.login_required
|
||||
def team_ticket_detail(ticket):
|
||||
try:
|
||||
ticket = TroubleTicket.get(TroubleTicket.id == ticket)
|
||||
except TroubleTicket.DoesNotExist:
|
||||
flash("Couldn't find ticket #{}.".format(ticket))
|
||||
return redirect(url_for("team_tickets"))
|
||||
|
||||
if ticket.team != g.team:
|
||||
flash("That's not your ticket.")
|
||||
return redirect(url_for("team_tickets"))
|
||||
|
||||
comments = TicketComment.select().where(TicketComment.ticket == ticket).order_by(TicketComment.time)
|
||||
return render_template("ticket_detail.html", ticket=ticket, comments=comments)
|
||||
|
||||
@app.route('/tickets/<int:ticket>/comment/', methods=["POST"])
|
||||
@decorators.must_be_allowed_to("comment on tickets")
|
||||
@decorators.must_be_allowed_to("view tickets")
|
||||
def team_ticket_comment(ticket):
|
||||
if g.redis.get("ticketl{}".format(session["user_id"])):
|
||||
return "You're doing that too fast."
|
||||
g.redis.set("ticketl{}".format(g.team.id), "1", 30)
|
||||
try:
|
||||
ticket = TroubleTicket.get(TroubleTicket.id == ticket)
|
||||
except TroubleTicket.DoesNotExist:
|
||||
flash("Couldn't find ticket #{}.".format(ticket))
|
||||
return redirect(url_for("team_tickets"))
|
||||
|
||||
if ticket.team != g.team:
|
||||
flash("That's not your ticket.")
|
||||
return redirect(url_for("team_tickets"))
|
||||
|
||||
if request.form["comment"]:
|
||||
TicketComment.create(ticket=ticket, comment_by=g.team.name, comment=request.form["comment"], time=datetime.now())
|
||||
flash("Comment added.")
|
||||
|
||||
if ticket.active and "resolved" in request.form:
|
||||
ticket.active = False
|
||||
ticket.save()
|
||||
flash("Ticket closed.")
|
||||
|
||||
elif not ticket.active and "resolved" not in request.form:
|
||||
ticket.active = True
|
||||
ticket.save()
|
||||
flash("Ticket re-opened.")
|
||||
|
||||
return redirect(url_for("team_ticket_detail", ticket=ticket.id))
|
||||
|
||||
# Debug
|
||||
@app.route('/debug/')
|
||||
def debug_app():
|
||||
return jsonify(hostname=socket.gethostname())
|
||||
|
||||
# Manage Peewee database sessions and Redis
|
||||
|
||||
# Manage Peewee database sessions and Redis
|
||||
@app.before_request
|
||||
def before_request():
|
||||
db.connect()
|
||||
g.redis = redis.StrictRedis(host=config.redis.host, port=config.redis.port, db=config.redis.db)
|
||||
g.connected = True
|
||||
|
||||
|
||||
@app.teardown_request
|
||||
def teardown_request(exc):
|
||||
if getattr(g, 'connected', False):
|
||||
db.close()
|
||||
g.redis.connection_pool.disconnect()
|
||||
|
||||
# CSRF things
|
||||
|
||||
# CSRF things
|
||||
@app.before_request
|
||||
def csrf_protect():
|
||||
csrf_exempt = ['/teamconfirm/']
|
||||
|
||||
if request.method == "POST":
|
||||
token = session.get('_csrf_token', None)
|
||||
if (not token or token != request.form["_csrf_token"]) and not request.path in csrf_exempt:
|
||||
if (not token or token != request.form["_csrf_token"]) and request.path not in csrf_exempt:
|
||||
return "Invalid CSRF token!"
|
||||
|
||||
|
||||
def generate_csrf_token():
|
||||
if '_csrf_token' not in session:
|
||||
session['_csrf_token'] = misc.generate_random_string(64)
|
||||
return session['_csrf_token']
|
||||
|
||||
|
||||
app.jinja_env.globals['csrf_token'] = generate_csrf_token
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -2,6 +2,7 @@ import os
|
|||
from datetime import datetime
|
||||
|
||||
production = os.getenv("PRODUCTION", None) is not None
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
ctf_name = "IceCTF"
|
||||
#IRC Channel
|
||||
|
|
11
ctferror.py
11
ctferror.py
|
@ -1,11 +0,0 @@
|
|||
SUCCESS = (0, "Success!")
|
||||
|
||||
FLAG_SUBMISSION_TOO_FAST = (1001, "You're submitting flags too fast!")
|
||||
FLAG_SUBMITTED_ALREADY = (1002, "You've already solved that problem!")
|
||||
FLAG_INCORRECT = (1003, "Incorrect flag.")
|
||||
FLAG_CANNOT_SUBMIT_WHILE_DISABLED = (1004, "You cannot submit a flag for a disabled problem.")
|
||||
|
||||
CAPTCHA_NOT_COMPLETED = (2001, "Please complete the CAPTCHA.")
|
||||
CAPTCHA_INVALID = (2002, "Invalid CAPTCHA response.")
|
||||
|
||||
NOTIFICATION_NOT_YOURS = (3001, "You cannot dismiss notifications that do not belong to you.")
|
|
@ -0,0 +1,38 @@
|
|||
from data.database import Challenge, ChallengeSolve, ChallengeFailure, ScoreAdjustment
|
||||
from datetime import datetime
|
||||
from exceptions import ValidationError
|
||||
from flask import g
|
||||
|
||||
def get_challenges():
|
||||
return Challenge.select().order_by(Challenge.points, Challenge.name)
|
||||
|
||||
def get_challenge(id):
|
||||
try:
|
||||
return Challenge.get(Challenge.id == id)
|
||||
except Challenge.DoesNotExist:
|
||||
raise ValidationError("Challenge does not exist!")
|
||||
|
||||
def get_solved(team):
|
||||
return Challenge.select().join(ChallengeSolve).where(ChallengeSolve.team == g.team)
|
||||
|
||||
def get_adjustments(team):
|
||||
return ScoreAdjustment.select().where(ScoreAdjustment.team == team)
|
||||
|
||||
def get_challenge_solves(chall):
|
||||
return ChallengeSolve.select(ChallengeSolve, Team).join(Team).order_by(ChallengeSolve.time).where(ChallengeSolve.challenge == chall)
|
||||
|
||||
def submit_flag(chall, user, team, flag):
|
||||
if team.solved(chall):
|
||||
raise ValidationError("Your team has already solved this problem!")
|
||||
elif not chall.enabled:
|
||||
raise ValidationError("This challenge is disabled.")
|
||||
elif flag.strip().lower() != chall.flag.strip.lower():
|
||||
ChallengeFailure.create(user=user, team=team, challenge=chall, attempt=flag, time=datetime.now())
|
||||
return "Incorrect flag"
|
||||
else:
|
||||
ChallengeSolve.create(user=user, team=team, challenge=chall, time=datetime.now())
|
||||
g.redis.hincrby("solves", challenge.id, 1)
|
||||
if config.immediate_scoreboard:
|
||||
g.redis.delete("scoreboard")
|
||||
g.redis.delete("graph")
|
||||
return "Correct!"
|
|
@ -46,11 +46,11 @@ class User(BaseModel):
|
|||
password_reset_token = CharField(null = True)
|
||||
password_reset_expired = DateTimeField(null = True)
|
||||
|
||||
def setPassword(self, pw):
|
||||
def set_password(self, pw):
|
||||
self.password = bcrypt.hashpw(pw.encode("utf-8"), bcrypt.gensalt())
|
||||
return
|
||||
|
||||
def checkPassword(self, pw):
|
||||
def check_password(self, pw):
|
||||
return bcrypt.checkpw(pw.encode("utf-8"), self.password.encode("utf-8"))
|
||||
|
||||
def eligible(self):
|
||||
|
@ -72,6 +72,7 @@ class Challenge(BaseModel):
|
|||
flag = TextField()
|
||||
|
||||
class ChallengeSolve(BaseModel):
|
||||
user = ForeignKeyField(User, related_name='solves')
|
||||
team = ForeignKeyField(Team, related_name='solves')
|
||||
challenge = ForeignKeyField(Challenge, related_name='solves')
|
||||
time = DateTimeField()
|
||||
|
@ -80,6 +81,7 @@ class ChallengeSolve(BaseModel):
|
|||
primary_key = CompositeKey('team', 'challenge')
|
||||
|
||||
class ChallengeFailure(BaseModel):
|
||||
user = ForeignKeyField(User, related_name='failures')
|
||||
team = ForeignKeyField(Team, related_name='failures')
|
||||
challenge = ForeignKeyField(Challenge, related_name='failures')
|
||||
attempt = CharField()
|
|
@ -0,0 +1,14 @@
|
|||
from data.database import Notification
|
||||
from exceptions import ValidationError
|
||||
|
||||
def get_notifications(team):
|
||||
return Notification.select().where(Notification.team == team)
|
||||
|
||||
def get_notification(team, id):
|
||||
try:
|
||||
return Notification.get(Notification.id == id and Notification.team == team)
|
||||
except Notification.DoesNotExist:
|
||||
raise ValidationError("Notification does not exist!")
|
||||
|
||||
def delete_notification(notification):
|
||||
notification.delete_instance()
|
|
@ -1,8 +1,9 @@
|
|||
from database import Team, Challenge, ChallengeSolve, ScoreAdjustment
|
||||
from .database import Team, Challenge, ChallengeSolve, ScoreAdjustment
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import config
|
||||
|
||||
|
||||
def get_all_scores(teams, solves, adjustments):
|
||||
scores = {team.id: 0 for team in teams}
|
||||
for solve in solves:
|
||||
|
@ -13,6 +14,7 @@ def get_all_scores(teams, solves, adjustments):
|
|||
|
||||
return scores
|
||||
|
||||
|
||||
def get_last_solves(teams, solves):
|
||||
last = {team.id: datetime(1970, 1, 1) for team in teams}
|
||||
for solve in solves:
|
||||
|
@ -20,6 +22,7 @@ def get_last_solves(teams, solves):
|
|||
last[solve.team_id] = solve.time
|
||||
return last
|
||||
|
||||
|
||||
def calculate_scores():
|
||||
solves = ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge)
|
||||
adjustments = ScoreAdjustment.select()
|
||||
|
@ -39,6 +42,7 @@ def calculate_scores():
|
|||
# eligible, teamid, teamname, affiliation, score
|
||||
return [(team_mapping[i[0]].eligible(), i[0], team_mapping[i[0]].name, team_mapping[i[0]].affiliation, i[1]) for idx, i in enumerate(sorted(scores.items(), key=lambda k: (-k[1], most_recent_solve[k[0]])))]
|
||||
|
||||
|
||||
def calculate_graph(scoredata):
|
||||
solves = list(ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge).order_by(ChallengeSolve.time))
|
||||
adjustments = list(ScoreAdjustment.select())
|
|
@ -0,0 +1,48 @@
|
|||
from exceptions import ValidationError
|
||||
from .database import Team
|
||||
from utils import misc
|
||||
|
||||
|
||||
def get_team(id=None, name=None, key=None):
|
||||
try:
|
||||
if name:
|
||||
return Team.get(Team.name == name)
|
||||
elif id:
|
||||
return Team.get(Team.id == id)
|
||||
elif key:
|
||||
return Team.get(Team.key == key)
|
||||
else:
|
||||
raise ValueError("Invalid call")
|
||||
except Team.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
def validate(name, affiliation):
|
||||
if name is not None:
|
||||
if not name or len(name) > 50:
|
||||
raise ValidationError("A team name is required.")
|
||||
if get_team(name=name):
|
||||
raise ValidationError("A team with that name already exists.")
|
||||
|
||||
|
||||
def create_team(name, affiliation):
|
||||
if not affiliation:
|
||||
affiliation = "No affiliation"
|
||||
validate(name, affiliation)
|
||||
|
||||
team_key = misc.generate_team_key()
|
||||
team = Team.create(name=name, affiliation=affiliation, key=team_key)
|
||||
|
||||
return True, team
|
||||
|
||||
|
||||
def update_team(current_team, name, affiliation):
|
||||
if not affiliation:
|
||||
affiliation = "No affiliation"
|
||||
if current_team.name == name:
|
||||
name = None
|
||||
validate(name, affiliation)
|
||||
if name:
|
||||
current_team.name = name
|
||||
current_team.affiliation = affiliation
|
||||
current_team.save()
|
|
@ -0,0 +1,30 @@
|
|||
from data.database import TroubleTicket, TicketComment
|
||||
from datetime import datetime
|
||||
from exceptions import ValidationError
|
||||
|
||||
|
||||
def get_tickets(team):
|
||||
return team.tickets
|
||||
|
||||
def get_ticket(team, id):
|
||||
try:
|
||||
return TroubleTicket.get(TroubleTicket.id == ticket and TroubleTicket.team == team)
|
||||
except TroubleTicket.DoesNotExist:
|
||||
raise ValidationError("Ticket not found!")
|
||||
|
||||
def get_comments(ticket):
|
||||
return TicketComment.select().where(TicketComment.ticket == ticket).order_by(TicketComment.time)
|
||||
|
||||
def create_ticket(team, summary, description):
|
||||
return TroubleTicket.create(team=g.team, summary=summary, description=description, opened_at=datetime.now())
|
||||
|
||||
def create_comment(ticket, user, comment):
|
||||
TicketComment.create(ticket=ticket, comment_by=user.username, comment=request.form["comment"], time=datetime.now())
|
||||
|
||||
def open_ticket(ticket):
|
||||
ticket.active = True
|
||||
ticket.save()
|
||||
|
||||
def close_ticket(ticket):
|
||||
ticket.active = False
|
||||
ticket.save()
|
|
@ -0,0 +1,139 @@
|
|||
from exceptions import ValidationError
|
||||
from .database import User, UserAccess
|
||||
from datetime import datetime, timedelta
|
||||
import utils
|
||||
|
||||
|
||||
def get_user(username=None, id=None):
|
||||
try:
|
||||
if username:
|
||||
return User.get(User.username == username)
|
||||
elif id:
|
||||
return User.get(User.id == id)
|
||||
else:
|
||||
raise ValueError("Invalid call")
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
def login(username, password):
|
||||
user = get_user(username=username)
|
||||
if not user:
|
||||
return False
|
||||
|
||||
if(user.check_password(password)):
|
||||
UserAccess.create(user=user, ip=utils.misc.get_ip(), time=datetime.now())
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def validate(username, email, password, background, country, tshirt_size=None, gender=None):
|
||||
if not email or "." not in email or "@" not in email:
|
||||
raise ValidationError("You must have a valid email!")
|
||||
|
||||
if not email.is_valid_email(email):
|
||||
raise ValidationError("You're lying")
|
||||
|
||||
if background not in utils.select.BackgroundKeys:
|
||||
raise ValidationError("Invalid Background")
|
||||
|
||||
if country not in utils.select.CountryKeys:
|
||||
raise ValidationError("Invalid Background")
|
||||
|
||||
if tshirt_size and (tshirt_size not in utils.select.TShirts):
|
||||
raise ValidationError("Invalid T-shirt size")
|
||||
|
||||
if gender and (gender not in ["M", "F"]):
|
||||
raise ValidationError("Invalid gender")
|
||||
|
||||
if password is not None:
|
||||
if len(password) < 6:
|
||||
raise ValidationError("Password is too short.")
|
||||
if username is not None:
|
||||
if not username or len(username) > 50:
|
||||
raise ValidationError("Invalid username")
|
||||
if get_user(username=username):
|
||||
raise ValidationError("That username has already been taken.")
|
||||
|
||||
|
||||
def create_user(username, email, password, background, country, team, tshirt_size=None, gender=None):
|
||||
validate(username, email, password, background, country, tshirt_size=tshirt_size, gender=gender)
|
||||
|
||||
assert team is not None
|
||||
confirmation_key = utils.misc.generate_confirmation_key()
|
||||
|
||||
user = User.create(username=username, email=email,
|
||||
background=background, country=country,
|
||||
tshirt_size=tshirt_size, gender=gender,
|
||||
email_confirmation_key=confirmation_key,
|
||||
team=team)
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
|
||||
UserAccess.create(user=user, ip=utils.misc.get_ip(), time=datetime.now())
|
||||
|
||||
utils.email.send_confirmation_email(email, confirmation_key)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
def confirm_email(current_user, confirmation_key):
|
||||
if current_user.email_confirmed:
|
||||
raise ValidationError("Email already confirmed")
|
||||
if current_user.confirmation_key == confirmation_key:
|
||||
current_user.email_confirmed = True
|
||||
current_user.save()
|
||||
else:
|
||||
raise ValidationError("Invalid confirmation key!")
|
||||
|
||||
|
||||
def forgot_password(username):
|
||||
user = get_user(username=username)
|
||||
if user is None:
|
||||
return
|
||||
user.password_reset_token = utils.misc.generate_confirmation_key()
|
||||
user.password_reset_expired = datetime.now() + timedelta(days=1)
|
||||
user.save()
|
||||
utils.email.send_password_reset_email(user.email, user.password_reset_token)
|
||||
|
||||
|
||||
def reset_password(token, password):
|
||||
if len(password) < 6:
|
||||
raise ValidationError("Password is too short!")
|
||||
try:
|
||||
user = User.get(User.password_reset_token == password_reset_token)
|
||||
if user.password_reset_expired < datetime.now():
|
||||
raise ValidationError("Token expired")
|
||||
user.set_password(password)
|
||||
user.password_reset_token = None
|
||||
user.save()
|
||||
except User.DoesNotExist:
|
||||
raise ValidationError("Invalid reset token!")
|
||||
|
||||
|
||||
def update_user(current_user, username, email, password, background, country, tshirt_size=None, gender=None):
|
||||
if username == current_user.username:
|
||||
username = None
|
||||
if password == "":
|
||||
password = None
|
||||
validate(username, email, password, background, country, tshirt_size, gender)
|
||||
if username:
|
||||
current_user.username = username
|
||||
if password:
|
||||
current_user.set_password(password)
|
||||
email_changed = (current_user.email != email) # send email after saving to db
|
||||
if email_changed:
|
||||
current_user.email_confirmation_key = utils.misc.generate_confirmation_key()
|
||||
current_user.email_confirmed = False
|
||||
current_user.email = email
|
||||
current_user.background = background
|
||||
current_user.country = country
|
||||
current_user.tshirt_size = tshirt_size
|
||||
current_user.gender = gender
|
||||
current_user.save()
|
||||
|
||||
if email_changed:
|
||||
utils.email.send_confirmation_email(email, current_user.email_confirmation_key)
|
||||
return "Changes saved. Check your email for a new confirmation key."
|
||||
else:
|
||||
return "Changes saved."
|
|
@ -0,0 +1,5 @@
|
|||
class ValidationError(Exception):
|
||||
pass
|
||||
|
||||
class CaptchaError(Exception):
|
||||
pass
|
|
@ -1,42 +0,0 @@
|
|||
from flask import Blueprint, jsonify, g, request
|
||||
from database import Challenge, Notification, Team, Challenge, ChallengeSolve
|
||||
from utils import decorators, flag, scoreboard
|
||||
from ctferror import *
|
||||
from datetime import datetime
|
||||
import config
|
||||
|
||||
api = Blueprint("api", "api", url_prefix="/api")
|
||||
@api.route("/submit/<int:challenge>.json", methods=["POST"])
|
||||
@decorators.must_be_allowed_to("solve challenges")
|
||||
@decorators.must_be_allowed_to("view challenges")
|
||||
@decorators.competition_running_required
|
||||
@decorators.confirmed_email_required
|
||||
def submit_api(challenge):
|
||||
chal = Challenge.get(Challenge.id == challenge)
|
||||
flagval = request.form["flag"]
|
||||
|
||||
code, message = flag.submit_flag(g.team, chal, flagval)
|
||||
return jsonify(dict(code=code, message=message))
|
||||
|
||||
@api.route("/dismiss/<int:nid>.json", methods=["POST"])
|
||||
@decorators.login_required
|
||||
def dismiss_notification(nid):
|
||||
n = Notification.get(Notification.id == nid)
|
||||
if g.team != n.team:
|
||||
code, message = NOTIFICATION_NOT_YOURS
|
||||
else:
|
||||
Notification.delete().where(Notification.id == nid).execute()
|
||||
code, message = SUCCESS
|
||||
return jsonify(dict(code=code, message=message))
|
||||
|
||||
@api.route("/_ctftime/")
|
||||
def ctftime_scoreboard_json():
|
||||
if not config.immediate_scoreboard and datetime.now() < config.competition_end:
|
||||
return "unavailable", 503
|
||||
|
||||
scores = scoreboard.calculate_scores()
|
||||
standings = [dict(team=i[2], score=i[4], outward=not i[0]) for i in scores]
|
||||
for index, standing in enumerate(standings):
|
||||
standing["pos"] = index + 1
|
||||
|
||||
return jsonify(standings=standings)
|
|
@ -1,13 +1,14 @@
|
|||
from flask import Blueprint, render_template, request, session, redirect, url_for, flash
|
||||
from database import AdminUser, Team, Challenge, ChallengeSolve, ChallengeFailure, ScoreAdjustment, TroubleTicket, TicketComment, Notification
|
||||
from data.database import AdminUser, Team, Challenge, ChallengeSolve, ScoreAdjustment, TroubleTicket, TicketComment, Notification
|
||||
import utils
|
||||
import utils.admin
|
||||
import utils.scoreboard
|
||||
from data import scoreboard
|
||||
from utils.decorators import admin_required, csrf_check
|
||||
from utils.notification import make_link
|
||||
from datetime import datetime
|
||||
from config import secret
|
||||
admin = Blueprint("admin", "admin", url_prefix="/admin")
|
||||
admin = Blueprint("admin", __name__, url_prefix="/admin")
|
||||
|
||||
|
||||
@admin.route("/")
|
||||
def admin_root():
|
||||
|
@ -16,6 +17,7 @@ def admin_root():
|
|||
else:
|
||||
return redirect(url_for(".admin_login"))
|
||||
|
||||
|
||||
@admin.route("/login/", methods=["GET", "POST"])
|
||||
def admin_login():
|
||||
if request.method == "GET":
|
||||
|
@ -42,23 +44,26 @@ def admin_login():
|
|||
flash("Y̸̤̗͍̘ͅo͙̠͈͎͎͙̟u̺ ̘̘̘̹̩̹h͔̟̟̗͠a̠͈v͍̻̮̗̬̬̣e̟̫̼̹̠͕ ̠̳͖͡ma͈̱͟d̙͍̀ͅe̵͕̙̯̟̟̞̳ ͉͚̙a̡̱̮̫̰̰ ̜̙̝̭͚t̜̙͚̗͇ͅͅe͉r҉r̸͎̝̞̙̦̹i͏̙b̶̜̟̭͕l̗̰̰̠̳̝̕e͎̥ ̸m̰̯̮̲̘̻͍̀is̜̲̮͍͔̘͕͟t̟͈̮a̙̤͎̠ķ̝̺͇̩e̷͍̤̠͖̣͈.̺̩̦̻.")
|
||||
return render_template("admin/login.html")
|
||||
|
||||
|
||||
@admin.route("/dashboard/")
|
||||
@admin_required
|
||||
def admin_dashboard():
|
||||
teams = Team.select()
|
||||
solves = ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge)
|
||||
adjustments = ScoreAdjustment.select()
|
||||
scoredata = utils.scoreboard.get_all_scores(teams, solves, adjustments)
|
||||
lastsolvedata = utils.scoreboard.get_last_solves(teams, solves)
|
||||
tickets = list(TroubleTicket.select().where(TroubleTicket.active == True))
|
||||
scoredata = scoreboard.get_all_scores(teams, solves, adjustments)
|
||||
lastsolvedata = scoreboard.get_last_solves(teams, solves)
|
||||
tickets = list(TroubleTicket.select().where(TroubleTicket.active==True))
|
||||
return render_template("admin/dashboard.html", teams=teams, scoredata=scoredata, lastsolvedata=lastsolvedata, tickets=tickets)
|
||||
|
||||
|
||||
@admin.route("/tickets/")
|
||||
@admin_required
|
||||
def admin_tickets():
|
||||
tickets = list(TroubleTicket.select(TroubleTicket, Team).join(Team).order_by(TroubleTicket.id.desc()))
|
||||
return render_template("admin/tickets.html", tickets=tickets)
|
||||
|
||||
|
||||
@admin.route("/tickets/<int:ticket>/")
|
||||
@admin_required
|
||||
def admin_ticket_detail(ticket):
|
||||
|
@ -66,6 +71,7 @@ def admin_ticket_detail(ticket):
|
|||
comments = list(TicketComment.select().where(TicketComment.ticket == ticket).order_by(TicketComment.time))
|
||||
return render_template("admin/ticket_detail.html", ticket=ticket, comments=comments)
|
||||
|
||||
|
||||
@admin.route("/tickets/<int:ticket>/comment/", methods=["POST"])
|
||||
@admin_required
|
||||
def admin_ticket_comment(ticket):
|
||||
|
@ -89,12 +95,14 @@ def admin_ticket_comment(ticket):
|
|||
|
||||
return redirect(url_for(".admin_ticket_detail", ticket=ticket.id))
|
||||
|
||||
|
||||
@admin.route("/team/<int:tid>/")
|
||||
@admin_required
|
||||
def admin_show_team(tid):
|
||||
team = Team.get(Team.id == tid)
|
||||
return render_template("admin/team.html", team=team)
|
||||
|
||||
|
||||
@admin.route("/team/<int:tid>/<csrf>/impersonate/")
|
||||
@csrf_check
|
||||
@admin_required
|
||||
|
@ -102,6 +110,7 @@ def admin_impersonate_team(tid):
|
|||
session["team_id"] = tid
|
||||
return redirect(url_for("scoreboard"))
|
||||
|
||||
|
||||
@admin.route("/team/<int:tid>/<csrf>/toggle_eligibility/")
|
||||
@csrf_check
|
||||
@admin_required
|
||||
|
@ -112,6 +121,7 @@ def admin_toggle_eligibility(tid):
|
|||
flash("Eligibility set to {}".format(team.eligible))
|
||||
return redirect(url_for(".admin_show_team", tid=tid))
|
||||
|
||||
|
||||
@admin.route("/team/<int:tid>/<csrf>/toggle_eligibility_lock/")
|
||||
@csrf_check
|
||||
@admin_required
|
||||
|
@ -122,6 +132,7 @@ def admin_toggle_eligibility_lock(tid):
|
|||
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_required
|
||||
def admin_score_adjust(tid):
|
||||
|
@ -135,6 +146,7 @@ def admin_score_adjust(tid):
|
|||
|
||||
return redirect(url_for(".admin_show_team", tid=tid))
|
||||
|
||||
|
||||
@admin.route("/logout/")
|
||||
def admin_logout():
|
||||
del session["admin"]
|
|
@ -0,0 +1,52 @@
|
|||
from flask import Blueprint, jsonify, g, request
|
||||
from data import challenge, notification, scoreboard
|
||||
from utils import decorators, ratelimit
|
||||
import exceptions
|
||||
from datetime import datetime
|
||||
import config
|
||||
|
||||
api = Blueprint("api", __name__, url_prefix="/api")
|
||||
|
||||
|
||||
@api.route("/submit/<int:challenge>.json", methods=["POST"])
|
||||
@decorators.must_be_allowed_to("solve challenges")
|
||||
@decorators.must_be_allowed_to("view challenges")
|
||||
@decorators.competition_running_required
|
||||
@decorators.confirmed_email_required
|
||||
@ratelimit.ratelimit(limit=10, per=120, over_limit=ratelimit.on_over_api_limit)
|
||||
def submit_api(challenge_id):
|
||||
try:
|
||||
chall = challenge.get_challenge(challenge_id)
|
||||
except exceptions.ValidationError as e:
|
||||
return jsonify(dict(code=1001, message=str(e)))
|
||||
flag = request.form["flag"]
|
||||
|
||||
try:
|
||||
challenge.submit_flag(chall, g.user, g.team, flag)
|
||||
return jsonify(dict(code=0, message="Success!"))
|
||||
except exceptions.ValidationError as e:
|
||||
return jsonify(dict(code=1001, message=str(e)))
|
||||
|
||||
|
||||
@api.route("/dismiss/<int:nid>.json", methods=["POST"])
|
||||
@decorators.login_required
|
||||
def dismiss_notification(nid):
|
||||
try:
|
||||
n = notification.get_notification(g.team, nid)
|
||||
notification.delete_notification(n)
|
||||
return jsonify(dict(code=0, message="Success!"))
|
||||
except exceptions.ValidationError as e:
|
||||
return jsonify(dict(code=1001, message=str(e)))
|
||||
|
||||
|
||||
@api.route("/_ctftime/")
|
||||
def ctftime_scoreboard_json():
|
||||
if not config.immediate_scoreboard and datetime.now() < config.competition_end:
|
||||
return "unavailable", 503
|
||||
|
||||
scores = scoreboard.calculate_scores()
|
||||
standings = [dict(team=i[2], score=i[4], outward=not i[0]) for i in scores]
|
||||
for index, standing in enumerate(standings):
|
||||
standing["pos"] = index + 1
|
||||
|
||||
return jsonify(standings=standings)
|
|
@ -0,0 +1,57 @@
|
|||
from flask import Blueprint, g, request, render_template, flash, redirect, url_for
|
||||
|
||||
from utils import decorators, ratelimit
|
||||
from data import challenge
|
||||
|
||||
import exceptions
|
||||
|
||||
challenges = Blueprint("challenges", __name__, template_folder="../templates/challenges")
|
||||
|
||||
|
||||
@challenges.route('/challenges/')
|
||||
@decorators.must_be_allowed_to("view challenges")
|
||||
@decorators.competition_running_required
|
||||
@decorators.confirmed_email_required
|
||||
def index():
|
||||
chals = challenge.get_challenges()
|
||||
solved = challenge.get_solved(g.team)
|
||||
solves = {i: int(g.redis.hget("solves", i).decode()) for i in [k.id for k in chals]}
|
||||
categories = sorted(list({chal.category for chal in chals}))
|
||||
return render_template("challenges.html", challenges=chals, solved=solved, categories=categories, solves=solves)
|
||||
|
||||
|
||||
@challenges.route('/challenges/<int:challenge>/solves/')
|
||||
@decorators.must_be_allowed_to("view challenge solves")
|
||||
@decorators.must_be_allowed_to("view challenges")
|
||||
@decorators.competition_running_required
|
||||
@decorators.confirmed_email_required
|
||||
def show_solves(challenge):
|
||||
try:
|
||||
chall = challenge.get_challenge(challenge)
|
||||
except exceptions.ValidationError as e:
|
||||
flash(str(e))
|
||||
return redirect(url_for(".index"))
|
||||
solves = challenge.get_challenge_solves(chall)
|
||||
return render_template("challenge_solves.html", challenge=chall, solves=solves)
|
||||
|
||||
|
||||
@challenges.route('/submit/<int:challenge>/', methods=["POST"])
|
||||
@decorators.must_be_allowed_to("solve challenges")
|
||||
@decorators.must_be_allowed_to("view challenges")
|
||||
@decorators.competition_running_required
|
||||
@decorators.confirmed_email_required
|
||||
@ratelimit.ratelimit(limit=10, per=120)
|
||||
def submit(challenge_id):
|
||||
try:
|
||||
chall = challenge.get_challenge(challenge_id)
|
||||
except exceptions.ValidationError as e:
|
||||
flash(str(e))
|
||||
return redirect(url_for(".index"))
|
||||
flag = request.form["flag"]
|
||||
|
||||
try:
|
||||
challenge.submit_flag(chall, g.user, g.team, flag)
|
||||
flash("Success!")
|
||||
except exceptions.ValidationError as e:
|
||||
flash(str(e))
|
||||
return redirect(url_for('.index'))
|
|
@ -0,0 +1,24 @@
|
|||
from flask import Blueprint, render_template
|
||||
|
||||
from utils import cache
|
||||
|
||||
import data
|
||||
import config
|
||||
|
||||
scoreboard = Blueprint("scoreboard", __name__, template_folder="../templates/scoreboard")
|
||||
|
||||
|
||||
@scoreboard.route('/scoreboard/')
|
||||
def index():
|
||||
scoreboard_data = cache.get_complex("scoreboard")
|
||||
graphdata = cache.get_complex("graph")
|
||||
if scoreboard_data is None or graphdata is None:
|
||||
if config.immediate_scoreboard:
|
||||
scoreboard_data = scoreboard.calculate_scores()
|
||||
graphdata = scoreboard.calculate_graph(data)
|
||||
data.scoreboard.set_complex("scoreboard", data, 120)
|
||||
data.scoreboard.set_complex("graph", graphdata, 120)
|
||||
else:
|
||||
return "No scoreboard data available. Please contact an organizer."
|
||||
|
||||
return render_template("scoreboard.html", data=scoreboard_data, graphdata=graphdata)
|
|
@ -0,0 +1,51 @@
|
|||
from flask import Blueprint, g, request, render_template, flash, url_for, redirect
|
||||
|
||||
from data import team, challenge
|
||||
|
||||
from utils import decorators, ratelimit
|
||||
import utils
|
||||
import config
|
||||
|
||||
import exceptions
|
||||
|
||||
teams = Blueprint("teams", __name__, template_folder="../templates/teams")
|
||||
# Things that require a team
|
||||
|
||||
|
||||
@teams.route('/team/', methods=["GET", "POST"])
|
||||
@decorators.login_required
|
||||
@ratelimit.ratelimit(limit=6, per=120)
|
||||
def dashboard():
|
||||
if request.method == "GET":
|
||||
team_solves = challenge.get_solved(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])
|
||||
return render_template("team.html", team_solves=team_solves, team_adjustments=team_adjustments, team_score=team_score)
|
||||
elif request.method == "POST":
|
||||
|
||||
team_name = request.form["team_name"].strip()
|
||||
affiliation = request.form["team_affiliation"].strip()
|
||||
|
||||
try:
|
||||
team.update_team(g.team, team_name, affiliation)
|
||||
flash("Changes saved.")
|
||||
except exceptions.ValidationError as e:
|
||||
flash(str(e))
|
||||
|
||||
return redirect(url_for('.dashboard'))
|
||||
|
||||
|
||||
@teams.route('/teamconfirm/', methods=["POST"])
|
||||
def teamconfirm():
|
||||
if utils.misc.get_ip() in config.confirm_ip:
|
||||
team_name = request.form["team_name"].strip()
|
||||
team_key = request.form["team_key"].strip()
|
||||
t = team.get_team(name=team_name)
|
||||
if t is None:
|
||||
return "invalid", 403
|
||||
if t.key == team_key:
|
||||
return "ok", 200
|
||||
else:
|
||||
return "invalid", 403
|
||||
else:
|
||||
return "unauthorized", 401
|
|
@ -0,0 +1,69 @@
|
|||
from flask import Blueprint, jsonify, g, request
|
||||
|
||||
from utils import decorators, ratelimit
|
||||
|
||||
from data import ticket
|
||||
|
||||
import exceptions
|
||||
|
||||
tickets = Blueprint("tickets", __name__, template_folder="../templates/tickets")
|
||||
# Trouble tickets
|
||||
|
||||
@tickets.route('/tickets/')
|
||||
@decorators.must_be_allowed_to("view tickets")
|
||||
@decorators.login_required
|
||||
def index():
|
||||
return render_template("tickets.html", tickets=list(ticket.get_tickets(g.team)))
|
||||
|
||||
@tickets.route('/tickets/new/', methods=["GET", "POST"])
|
||||
@decorators.must_be_allowed_to("submit tickets")
|
||||
@decorators.must_be_allowed_to("view tickets")
|
||||
@decorators.login_required
|
||||
@ratelimit.ratelimit(limit=1, per=120)
|
||||
def open_ticket():
|
||||
if request.method == "GET":
|
||||
return render_template("open_ticket.html")
|
||||
elif request.method == "POST":
|
||||
summary = request.form["summary"]
|
||||
description = request.form["description"]
|
||||
ticket = ticket.create_ticket(g.team, summary, description)
|
||||
flash("Ticket #{} opened.".format(ticket.id))
|
||||
return redirect(url_for(".detail", ticket=ticket.id))
|
||||
|
||||
@tickets.route('/tickets/<int:ticket>/')
|
||||
@decorators.must_be_allowed_to("view tickets")
|
||||
@decorators.login_required
|
||||
def detail(ticket):
|
||||
try:
|
||||
ticket = ticket.get_ticket(g.team, ticket)
|
||||
except exceptions.ValidationError as e:
|
||||
flash(str(e))
|
||||
return redirect(url_for(".index"))
|
||||
|
||||
comments = ticket.get_comments(ticket)
|
||||
return render_template("ticket_detail.html", ticket=ticket, comments=comments)
|
||||
|
||||
@tickets.route('/tickets/<int:ticket>/comment/', methods=["POST"])
|
||||
@decorators.must_be_allowed_to("comment on tickets")
|
||||
@decorators.must_be_allowed_to("view tickets")
|
||||
@ratelimit.ratelimit(limit=1, per=120)
|
||||
def comment(ticket):
|
||||
try:
|
||||
ticket = ticket.get_ticket(g.team, ticket)
|
||||
except exceptions.ValidationError as e:
|
||||
flash(str(e))
|
||||
return redirect(url_for(".index"))
|
||||
|
||||
if request.form["comment"]:
|
||||
ticket.create_comment(ticket, g.user, request.form["comment"])
|
||||
flash("Comment added.")
|
||||
|
||||
if ticket.active and "resolved" in request.form:
|
||||
ticket.close_ticket(ticket)
|
||||
flash("Ticket closed.")
|
||||
|
||||
elif not ticket.active and "resolved" not in request.form:
|
||||
ticket.open_ticket(ticket)
|
||||
flash("Ticket re-opened.")
|
||||
|
||||
return redirect(url_for(".detail", ticket=ticket.id))
|
|
@ -0,0 +1,191 @@
|
|||
from flask import Blueprint, g, request, render_template, url_for, redirect, session, flash
|
||||
|
||||
from data import user, team
|
||||
|
||||
from utils import decorators, ratelimit, captcha
|
||||
import config
|
||||
import exceptions
|
||||
|
||||
users = Blueprint("users", __name__, template_folder="../templates/users")
|
||||
|
||||
|
||||
@users.route('/login/', methods=["GET", "POST"])
|
||||
@ratelimit.ratelimit(limit=6, per=120)
|
||||
def login():
|
||||
if request.method == "GET":
|
||||
return render_template("login.html")
|
||||
elif request.method == "POST":
|
||||
username = request.form["username"]
|
||||
password = request.form["password"]
|
||||
|
||||
if user.login(username, password):
|
||||
session["user_id"] = user.id
|
||||
flash("Login successful.")
|
||||
return redirect(url_for('teams.dashboard'))
|
||||
else:
|
||||
flash("Incorrect username or password", "error")
|
||||
return redirect(url_for('.login'))
|
||||
|
||||
|
||||
@users.route('/register/', methods=["GET", "POST"])
|
||||
@ratelimit.ratelimit(limit=6, per=120)
|
||||
def register():
|
||||
if not config.registration:
|
||||
if "admin" in session and session["admin"]:
|
||||
pass
|
||||
else:
|
||||
return "Registration is currently disabled. Email icectf@icec.tf to create an account."
|
||||
|
||||
if request.method == "GET":
|
||||
return render_template("register.html")
|
||||
elif request.method == "POST":
|
||||
try:
|
||||
captcha.verify_captcha()
|
||||
except exceptions.CaptchaError as e:
|
||||
flash(str(e))
|
||||
return redirect(url_for(".register"))
|
||||
|
||||
username = request.form["username"].strip()
|
||||
user_email = request.form["email"].strip()
|
||||
password = request.form["password"].strip()
|
||||
confirm_password = request.form["confirm_password"].strip()
|
||||
background = request.form["background"].strip()
|
||||
country = request.form["country"].strip()
|
||||
|
||||
tshirt_size = ""
|
||||
gender = ""
|
||||
if "tshirt_size" in request.form.keys():
|
||||
tshirt_size = request.form["tshirt_size"].strip()
|
||||
if "gender" in request.form.keys():
|
||||
gender = request.form["gender"].strip()
|
||||
|
||||
if password != confirm_password:
|
||||
flash("Passwords do not match!")
|
||||
return redirect(url_for('.register'))
|
||||
|
||||
join_team = bool(int(request.form["join_team"].strip()))
|
||||
if join_team:
|
||||
team_key = request.form["team_key"].strip()
|
||||
t = team.get_team(key=team_key)
|
||||
if not team:
|
||||
flash("This team could not be found, check your team key.")
|
||||
return redirect(url_for('.register'))
|
||||
else:
|
||||
team_name = request.form["team_name"].strip()
|
||||
team_affiliation = request.form["team_affiliation"].strip()
|
||||
try:
|
||||
t = team.create_team(team_name, team_affiliation)
|
||||
except exceptions.ValidationError as e:
|
||||
flash(str(e))
|
||||
return redirect(url_for('.register'))
|
||||
|
||||
# note: this is technically a race condition, the team can exist without a user but w/e
|
||||
# the team keys are impossible to predict
|
||||
try:
|
||||
u = user.create_user(username, user_email,
|
||||
password, background,
|
||||
country, t,
|
||||
tshirt_size=tshirt_size, gender=gender)
|
||||
except exceptions.ValidationError as e:
|
||||
if not join_team:
|
||||
team.delete_instance()
|
||||
flash(str(e))
|
||||
return redirect(url_for('.register'))
|
||||
session["user_id"] = u.id
|
||||
flash("Registration finished")
|
||||
return redirect(url_for('.dashboard'))
|
||||
|
||||
|
||||
@users.route('/logout/')
|
||||
def logout():
|
||||
session.pop("user_id")
|
||||
flash("You've successfully logged out.")
|
||||
return redirect(url_for('.login'))
|
||||
|
||||
|
||||
@users.route('/confirm_email/<confirmation_key>', methods=["GET"])
|
||||
@decorators.login_required
|
||||
@ratelimit.ratelimit(limit=6, per=120)
|
||||
def confirm_email(confirmation_key):
|
||||
try:
|
||||
user.confirm_email(g.user, confirmation_key)
|
||||
flash("Email confirmed!")
|
||||
except exceptions.ValidationError as e:
|
||||
flash(str(e))
|
||||
return redirect(url_for('.dashboard'))
|
||||
|
||||
|
||||
@users.route('/forgot_password/', methods=["GET", "POST"])
|
||||
@ratelimit.ratelimit(limit=6, per=120)
|
||||
def forgot_password():
|
||||
if request.method == "GET":
|
||||
return render_template("forgot_password.html")
|
||||
elif request.method == "POST":
|
||||
username = request.form["username"].strip()
|
||||
if len(username) > 50 or not username:
|
||||
flash("You must have a username!")
|
||||
return redirect(url_for('.forgot_password'))
|
||||
user.forgot_password(username=username)
|
||||
flash("Forgot password email sent! Check your email.")
|
||||
return render_template("forgot_password.html")
|
||||
|
||||
|
||||
@users.route('/reset_password/<password_reset_token>', methods=["GET", "POST"])
|
||||
@ratelimit.ratelimit(limit=6, per=120)
|
||||
def reset_password(password_reset_token):
|
||||
if request.method == "GET":
|
||||
return render_template("reset_password.html")
|
||||
elif request.method == "POST":
|
||||
password = request.form["password"].strip()
|
||||
confirm_password = request.form["confirm_password"].strip()
|
||||
|
||||
if not password == confirm_password:
|
||||
flash("Password does not match")
|
||||
return render_template("reset_password.html", password_reset_token=password_reset_token)
|
||||
|
||||
try:
|
||||
user.reset_password(password_reset_token, password)
|
||||
flash("Password successfully reset")
|
||||
return redirect(url_for(".login"))
|
||||
except exceptions.ValidationError as e:
|
||||
flash(str(e))
|
||||
return redirect(url_for(".reset_password", password_reset_token))
|
||||
|
||||
|
||||
@users.route('/user/', methods=["GET", "POST"])
|
||||
@decorators.login_required
|
||||
@ratelimit.ratelimit(limit=6, per=120)
|
||||
def dashboard():
|
||||
if request.method == "GET":
|
||||
first_login = False
|
||||
if g.user.first_login:
|
||||
first_login = True
|
||||
g.user.first_login = False
|
||||
g.user.save()
|
||||
return render_template("user.html", first_login=first_login)
|
||||
elif request.method == "POST":
|
||||
username = request.form["username"].strip()
|
||||
email = request.form["email"].strip()
|
||||
password = request.form["password"].strip()
|
||||
confirm_password = request.form["confirm_password"].strip()
|
||||
background = request.form["background"].strip()
|
||||
country = request.form["country"].strip()
|
||||
|
||||
tshirt_size = ""
|
||||
gender = ""
|
||||
if "tshirt_size" in request.form.keys():
|
||||
tshirt_size = request.form["tshirt_size"].strip()
|
||||
if "gender" in request.form.keys():
|
||||
gender = request.form["gender"].strip()
|
||||
|
||||
if password != "":
|
||||
if password != confirm_password:
|
||||
flash("Password does not match confirmation")
|
||||
return redirect(url_for('.dashboard'))
|
||||
|
||||
try:
|
||||
msg = user.update_user(g.user, username, email, password, background, country, tshirt_size, gender)
|
||||
flash(msg)
|
||||
except exceptions.ValidationError as e:
|
||||
flash(str(e))
|
||||
return redirect(url_for('.dashboard'))
|
|
@ -33,14 +33,14 @@
|
|||
<img class="top-logo" src={{ url_for('static', filename='img/logo.png') }}></img>
|
||||
<ul class="right hide-on-med-and-down">
|
||||
{% if logged_in %}
|
||||
<li><a href="{{ url_for('logout') }}">Logout</a></li>
|
||||
<li><a href="{{ url_for('user_dashboard') }}">{{ user.username }}</a></li>
|
||||
<li><a href="{{ url_for('users.logout') }}">Logout</a></li>
|
||||
<li><a href="{{ url_for('users.dashboard') }}">{{ user.username }}</a></li>
|
||||
{% endif %}
|
||||
{% if not logged_in %}
|
||||
{% if config.registration %}
|
||||
<li><a href="{{ url_for('register') }}">Register</a></li>
|
||||
<li><a href="{{ url_for('users.register') }}">Register</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{{ url_for('login') }}">Login</a></li>
|
||||
<li><a href="{{ url_for('users.login') }}">Login</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -50,12 +50,12 @@
|
|||
|
||||
{% if config.competition_is_running() %}
|
||||
{% if logged_in %}
|
||||
<li><a href="{{ url_for('challenges') }}">
|
||||
<li><a href="{{ url_for('challenges.index') }}">
|
||||
<div class="side-icon"><i class="material-icons blue-text">flag</i></div>
|
||||
<div class="side-text">Challenges</div>
|
||||
</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{{ url_for('scoreboard') }}">
|
||||
<li><a href="{{ url_for('scoreboard.index') }}">
|
||||
<div class="side-icon"><i class="material-icons red-text">timeline</i></div>
|
||||
<div class="side-text">Scoreboard</div>
|
||||
</a></li>
|
||||
|
@ -69,7 +69,7 @@
|
|||
<div class="side-icon"><i class="material-icons green-text">chat</i></div>
|
||||
<div class="side-text">Chat</div>
|
||||
</a></li>
|
||||
<li><a href="{{ url_for('team_tickets') }}">
|
||||
<li><a href="{{ url_for('tickets.index') }}">
|
||||
<div class="side-icon"><i class="material-icons amber-text">report_problem</i></div>
|
||||
<div class="side-text">Tickets</div>
|
||||
</a></li>
|
||||
|
@ -78,27 +78,27 @@
|
|||
<div class="divider" /></div>
|
||||
{% if logged_in %}
|
||||
|
||||
<li><a href="{{ url_for('team_dashboard') }}">
|
||||
<li><a href="{{ url_for('teams.dashboard') }}">
|
||||
<div class="side-icon"><i class="material-icons indigo-text">track_changes</i></div>
|
||||
<div class="side-text">My Team</div>
|
||||
</a></li>
|
||||
<li><a href="{{ url_for('user_dashboard') }}">
|
||||
<li><a href="{{ url_for('users.dashboard') }}">
|
||||
<div class="side-icon"><i class="material-icons teal-text">account_circle</i></div>
|
||||
<div class="side-text">{{ user.username }}</div>
|
||||
</a></li>
|
||||
<li><a href="{{ url_for('logout') }}">
|
||||
<li><a href="{{ url_for('users.logout') }}">
|
||||
<div class="side-icon"><i class="material-icons red-text">block</i></div>
|
||||
<div class="side-text">Logout</div>
|
||||
</a></li>
|
||||
{% endif %}
|
||||
{% if not logged_in %}
|
||||
{% if config.registration %}
|
||||
<li><a href="{{ url_for('register') }}">
|
||||
<li><a href="{{ url_for('users.register') }}">
|
||||
<div class="side-icon"><i class="material-icons indigo-text">person_add</i></div>
|
||||
<div class="side-text">Register</div>
|
||||
</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{{ url_for('login') }}">
|
||||
<li><a href="{{ url_for('users.login') }}">
|
||||
<div class="side-icon"><i class="material-icons amber-text">lock</i></div>
|
||||
<div class="side-text">Login</div>
|
||||
</a></li>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<h2>Solves for {{ challenge.name }}</h2>
|
||||
<a href="{{ url_for('challenges') }}"><< Back to challenges</a>
|
||||
<a href="{{ url_for('.index') }}"><< Back to challenges</a>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
|
@ -64,13 +64,13 @@
|
|||
<p>{{ challenge.description | safe }}
|
||||
{% if challenge in solved %}
|
||||
<br /><br /><strong>You've solved this challenge!</strong><br />
|
||||
<a href="{{ url_for('challenge_show_solves', challenge=challenge.id) }}">View solves</a>
|
||||
<a href="{{ url_for('.show_solves', challenge=challenge.id) }}">View solves</a>
|
||||
</p>
|
||||
{% else %}
|
||||
<br /><br />
|
||||
<a href="{{ url_for('challenge_show_solves', challenge=challenge.id) }}">View solves</a>
|
||||
<a href="{{ url_for('.show_solves', challenge=challenge.id) }}">View solves</a>
|
||||
</p>
|
||||
<form class="flag-form" action="{{ url_for('submit', challenge=challenge.id) }}" data-challengeid="{{ challenge.id }}" method="POST">
|
||||
<form class="flag-form" action="{{ url_for('.submit', challenge=challenge.id) }}" data-challengeid="{{ challenge.id }}" method="POST">
|
||||
<div class="row no-bot">
|
||||
<div class="col s12 m10">
|
||||
<div class="input-field">
|
|
@ -53,7 +53,7 @@
|
|||
Team
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p>Your score is currently {{ team_score }}. <a href="{{ url_for('challenges') }}">Go solve more challenges!</a></p>
|
||||
<p>Your score is currently {{ team_score }}. <a href="{{ url_for('challenges.index') }}">Go solve more challenges!</a></p>
|
||||
{{ team.affiliation }}.</p>
|
||||
<p>Your team is currently marked {{ "eligible" if team.eligible() else "ineligible" }}.</p>
|
||||
</div>
|
|
@ -9,7 +9,7 @@
|
|||
<small><abbr class="time" title="{{ comment.time }}">{{ comment.time }}</abbr> · {{ comment.comment_by }}</small>
|
||||
{% endfor %}
|
||||
<br />
|
||||
<form action="{{ url_for('team_ticket_comment', ticket=ticket.id) }}" method="POST">
|
||||
<form action="{{ url_for('.comment', ticket=ticket.id) }}" method="POST">
|
||||
<div class="input-field">
|
||||
<textarea id="comment" name="comment" class="materialize-textarea"></textarea>
|
||||
<label for="comment">Comment</label>
|
|
@ -3,11 +3,11 @@
|
|||
{% block content %}
|
||||
<h2>Trouble tickets</h2>
|
||||
{% if tickets %}
|
||||
You have the following open tickets. If you're having an issue, you can <a href="{{ url_for('open_ticket') }}">open a new ticket</a>.
|
||||
You have the following open tickets. If you're having an issue, you can <a href="{{ url_for('.open_ticket') }}">open a new ticket</a>.
|
||||
<div class="collection">
|
||||
{% for ticket in tickets %}
|
||||
{% if ticket.active %}
|
||||
<a class="collection-item" href="{{ url_for('team_ticket_detail', ticket=ticket.id) }}">#{{ ticket.id }} {{ ticket.summary }}</a>
|
||||
<a class="collection-item" href="{{ url_for('.detail', ticket=ticket.id) }}">#{{ ticket.id }} {{ ticket.summary }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
@ -16,11 +16,11 @@ You have the following closed tickets:
|
|||
<div class="collection">
|
||||
{% for ticket in tickets %}
|
||||
{% if not ticket.active %}
|
||||
<a class="collection-item" href="{{ url_for('team_ticket_detail', ticket=ticket.id) }}">#{{ ticket.id }} {{ ticket.summary }}</a>
|
||||
<a class="collection-item" href="{{ url_for('.detail', ticket=ticket.id) }}">#{{ ticket.id }} {{ ticket.summary }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
You have no open tickets right now. You can <a href="{{ url_for('open_ticket') }}">open one</a> if you're having an issue.
|
||||
You have no open tickets right now. You can <a href="{{ url_for('.open_ticket') }}">open one</a> if you're having an issue.
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -17,7 +17,7 @@
|
|||
</div>
|
||||
<div class="row no-bot">
|
||||
<div class="col s6">
|
||||
<a href="{{ url_for('forgot_password') }}">Forgot password?</a>
|
||||
<a href="{{ url_for('.forgot_password') }}">Forgot password?</a>
|
||||
</div>
|
||||
<div class="col s6">
|
||||
<button class="btn waves-effect waves-light right" type="submit">Login</button>
|
|
@ -1,18 +1,19 @@
|
|||
from ctferror import *
|
||||
from flask import request
|
||||
from . import misc
|
||||
from exceptions import CaptchaError
|
||||
|
||||
import config
|
||||
import requests
|
||||
|
||||
|
||||
def verify_captcha():
|
||||
if "g-recaptcha-response" not in request.form:
|
||||
return CAPTCHA_NOT_COMPLETED
|
||||
raise CaptchaError("Captcha not completed!")
|
||||
|
||||
captcha_response = request.form["g-recaptcha-response"]
|
||||
verify_data = dict(secret=config.secret.recaptcha_secret, response=captcha_response, remoteip=misc.get_ip())
|
||||
result = requests.post("https://www.google.com/recaptcha/api/siteverify", verify_data).json()["success"]
|
||||
if not result:
|
||||
return CAPTCHA_INVALID
|
||||
raise CaptchaError("Captcha Invalid!")
|
||||
|
||||
return SUCCESS
|
||||
return True
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
from database import Challenge, ChallengeSolve, ChallengeFailure
|
||||
from flask import g
|
||||
from ctferror import *
|
||||
from datetime import datetime
|
||||
import config
|
||||
|
||||
def submit_flag(team, challenge, flag):
|
||||
if g.redis.get("rl{}".format(team.id)):
|
||||
delta = config.competition_end - datetime.now()
|
||||
if delta.total_seconds() > (config.flag_rl * 6):
|
||||
return FLAG_SUBMISSION_TOO_FAST
|
||||
|
||||
if team.solved(challenge):
|
||||
return FLAG_SUBMITTED_ALREADY
|
||||
elif not challenge.enabled:
|
||||
return FLAG_CANNOT_SUBMIT_WHILE_DISABLED
|
||||
elif flag.strip().lower() != challenge.flag.strip().lower():
|
||||
g.redis.set("rl{}".format(team.id), str(datetime.now()), config.flag_rl)
|
||||
ChallengeFailure.create(team=team, challenge=challenge, attempt=flag, time=datetime.now())
|
||||
return FLAG_INCORRECT
|
||||
else:
|
||||
g.redis.hincrby("solves", challenge.id, 1)
|
||||
if config.immediate_scoreboard:
|
||||
g.redis.delete("scoreboard")
|
||||
g.redis.delete("graph")
|
||||
ChallengeSolve.create(team=team, challenge=challenge, time=datetime.now())
|
||||
return SUCCESS
|
|
@ -1,24 +1,23 @@
|
|||
import random
|
||||
import config
|
||||
import json
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from functools import wraps
|
||||
from flask import request, session, redirect, url_for, flash, g
|
||||
from database import Team, Challenge, ChallengeSolve, ScoreAdjustment
|
||||
from flask import request
|
||||
|
||||
allowed_chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
|
||||
def generate_random_string(length=32, chars=allowed_chars):
|
||||
r = random.SystemRandom()
|
||||
return "".join([r.choice(chars) for i in range(length)])
|
||||
|
||||
|
||||
def generate_team_key():
|
||||
return config.ctf_name.lower() + "_" + generate_random_string(32, allowed_chars)
|
||||
|
||||
|
||||
def generate_confirmation_key():
|
||||
return generate_random_string(48)
|
||||
|
||||
|
||||
def get_ip():
|
||||
return request.headers.get(config.proxied_ip_header, request.remote_addr)
|
||||
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import time
|
||||
from functools import update_wrapper
|
||||
from flask import request, g, jsonify, session
|
||||
|
||||
|
||||
class RateLimit(object):
|
||||
expiration_window = 10
|
||||
|
||||
def __init__(self, key_prefix, limit, per, send_x_headers):
|
||||
self.reset = (int(time.time()) // per) * per + per
|
||||
self.key = key_prefix + str(self.reset)
|
||||
self.limit = limit
|
||||
self.per = per
|
||||
self.send_x_headers = send_x_headers
|
||||
p = g.redis.pipeline()
|
||||
p.incr(self.key)
|
||||
p.expireat(self.key, self.reset + self.expiration_window)
|
||||
self.current = min(p.execute()[0], limit)
|
||||
|
||||
remaining = property(lambda x: x.limit - x.current)
|
||||
over_limit = property(lambda x: x.current >= x.limit)
|
||||
|
||||
|
||||
def get_view_rate_limit():
|
||||
return getattr(g, '_view_rate_limit', None)
|
||||
|
||||
|
||||
def on_over_limit(limit):
|
||||
flash("You are doing that too fast!")
|
||||
return redirect(request.path)
|
||||
|
||||
|
||||
def on_over_api_limit(limit):
|
||||
return jsonify(dict(code=1000, message="You are doing that too fast!"))
|
||||
|
||||
|
||||
def scope_func():
|
||||
id = str(request.remote_addr)
|
||||
if g.logged_in:
|
||||
id += "/%s" % (session["user_id"])
|
||||
return id
|
||||
|
||||
|
||||
def ratelimit(limit, per=300, send_x_headers=True,
|
||||
methods=["POST"],
|
||||
over_limit=on_over_limit,
|
||||
scope_func=scope_func,
|
||||
key_func=lambda: request.endpoint):
|
||||
def decorator(f):
|
||||
def rate_limited(*args, **kwargs):
|
||||
if request.method in methods:
|
||||
key = 'rate-limit/%s/%s/' % (key_func(), scope_func())
|
||||
rlimit = RateLimit(key, limit, per, send_x_headers)
|
||||
g._view_rate_limit = rlimit
|
||||
if over_limit is not None and rlimit.over_limit:
|
||||
return over_limit(rlimit)
|
||||
return f(*args, **kwargs)
|
||||
return update_wrapper(rate_limited, f)
|
||||
return decorator
|
Loading…
Reference in New Issue