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
|
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
|
import redis
|
||||||
from datetime import datetime, timedelta
|
import socket
|
||||||
from peewee import fn
|
import logging
|
||||||
|
|
||||||
from utils import decorators, flag, cache, misc, captcha, email, select
|
|
||||||
import utils.scoreboard
|
|
||||||
|
|
||||||
import config
|
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:
|
if config.production:
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
else:
|
else:
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
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
|
@app.before_request
|
||||||
def make_info_available():
|
def make_info_available():
|
||||||
if "user_id" in session:
|
if "user_id" in session:
|
||||||
g.logged_in = True
|
g.logged_in = True
|
||||||
try:
|
current_user = data.user.get_user(id=session["user_id"])
|
||||||
g.user = 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.user_restricts = g.user.restricts.split(",")
|
||||||
g.team = g.user.team
|
g.team = g.user.team
|
||||||
g.team_restricts = g.team.restricts.split(",")
|
g.team_restricts = g.team.restricts.split(",")
|
||||||
except User.DoesNotExist:
|
else:
|
||||||
g.logged_in = False
|
g.logged_in = False
|
||||||
session.pop("user_id")
|
session.pop("user_id")
|
||||||
return render_template("login.html")
|
return render_template("login.html")
|
||||||
else:
|
else:
|
||||||
g.logged_in = False
|
g.logged_in = False
|
||||||
|
|
||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def scoreboard_variables():
|
def scoreboard_variables():
|
||||||
var = dict(config=config, select=select)
|
var = dict(config=config, select=select)
|
||||||
|
@ -45,555 +57,64 @@ def scoreboard_variables():
|
||||||
var["logged_in"] = True
|
var["logged_in"] = True
|
||||||
var["user"] = g.user
|
var["user"] = g.user
|
||||||
var["team"] = g.team
|
var["team"] = g.team
|
||||||
# TODO should this apply to users or teams?
|
var["notifications"] = data.notification.get_notifications()
|
||||||
# var["notifications"] = Notification.select().where(Notification.user == g.user)
|
|
||||||
var["notifications"] = []
|
|
||||||
else:
|
else:
|
||||||
var["logged_in"] = False
|
var["logged_in"] = False
|
||||||
var["notifications"] = []
|
var["notifications"] = []
|
||||||
|
|
||||||
return var
|
return var
|
||||||
|
|
||||||
# Blueprints
|
|
||||||
from modules import api, admin
|
|
||||||
app.register_blueprint(api.api)
|
|
||||||
app.register_blueprint(admin.admin)
|
|
||||||
|
|
||||||
# Publically accessible things
|
|
||||||
|
|
||||||
@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('team.dashboard'))
|
||||||
return redirect(url_for('register'))
|
return redirect(url_for('users.register'))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/chat/')
|
@app.route('/chat/')
|
||||||
def chat():
|
def chat():
|
||||||
return render_template("chat.html")
|
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
|
# Debug
|
||||||
@app.route('/debug/')
|
@app.route('/debug/')
|
||||||
def debug_app():
|
def debug_app():
|
||||||
return jsonify(hostname=socket.gethostname())
|
return jsonify(hostname=socket.gethostname())
|
||||||
|
|
||||||
# Manage Peewee database sessions and Redis
|
|
||||||
|
|
||||||
|
# Manage Peewee database sessions and Redis
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def before_request():
|
def before_request():
|
||||||
db.connect()
|
db.connect()
|
||||||
g.redis = redis.StrictRedis(host=config.redis.host, port=config.redis.port, db=config.redis.db)
|
g.redis = redis.StrictRedis(host=config.redis.host, port=config.redis.port, db=config.redis.db)
|
||||||
g.connected = True
|
g.connected = True
|
||||||
|
|
||||||
|
|
||||||
@app.teardown_request
|
@app.teardown_request
|
||||||
def teardown_request(exc):
|
def teardown_request(exc):
|
||||||
if getattr(g, 'connected', False):
|
if getattr(g, 'connected', False):
|
||||||
db.close()
|
db.close()
|
||||||
g.redis.connection_pool.disconnect()
|
g.redis.connection_pool.disconnect()
|
||||||
|
|
||||||
# CSRF things
|
|
||||||
|
|
||||||
|
# CSRF things
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def csrf_protect():
|
def csrf_protect():
|
||||||
csrf_exempt = ['/teamconfirm/']
|
csrf_exempt = ['/teamconfirm/']
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
token = session.get('_csrf_token', None)
|
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!"
|
return "Invalid CSRF token!"
|
||||||
|
|
||||||
|
|
||||||
def generate_csrf_token():
|
def generate_csrf_token():
|
||||||
if '_csrf_token' not in session:
|
if '_csrf_token' not in session:
|
||||||
session['_csrf_token'] = misc.generate_random_string(64)
|
session['_csrf_token'] = misc.generate_random_string(64)
|
||||||
return session['_csrf_token']
|
return session['_csrf_token']
|
||||||
|
|
||||||
|
|
||||||
app.jinja_env.globals['csrf_token'] = generate_csrf_token
|
app.jinja_env.globals['csrf_token'] = generate_csrf_token
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -2,6 +2,7 @@ import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
production = os.getenv("PRODUCTION", None) is not None
|
production = os.getenv("PRODUCTION", None) is not None
|
||||||
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
ctf_name = "IceCTF"
|
ctf_name = "IceCTF"
|
||||||
#IRC Channel
|
#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_token = CharField(null = True)
|
||||||
password_reset_expired = DateTimeField(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())
|
self.password = bcrypt.hashpw(pw.encode("utf-8"), bcrypt.gensalt())
|
||||||
return
|
return
|
||||||
|
|
||||||
def checkPassword(self, pw):
|
def check_password(self, pw):
|
||||||
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):
|
||||||
|
@ -72,6 +72,7 @@ class Challenge(BaseModel):
|
||||||
flag = TextField()
|
flag = TextField()
|
||||||
|
|
||||||
class ChallengeSolve(BaseModel):
|
class ChallengeSolve(BaseModel):
|
||||||
|
user = ForeignKeyField(User, related_name='solves')
|
||||||
team = ForeignKeyField(Team, related_name='solves')
|
team = ForeignKeyField(Team, related_name='solves')
|
||||||
challenge = ForeignKeyField(Challenge, related_name='solves')
|
challenge = ForeignKeyField(Challenge, related_name='solves')
|
||||||
time = DateTimeField()
|
time = DateTimeField()
|
||||||
|
@ -80,6 +81,7 @@ class ChallengeSolve(BaseModel):
|
||||||
primary_key = CompositeKey('team', 'challenge')
|
primary_key = CompositeKey('team', 'challenge')
|
||||||
|
|
||||||
class ChallengeFailure(BaseModel):
|
class ChallengeFailure(BaseModel):
|
||||||
|
user = ForeignKeyField(User, related_name='failures')
|
||||||
team = ForeignKeyField(Team, related_name='failures')
|
team = ForeignKeyField(Team, related_name='failures')
|
||||||
challenge = ForeignKeyField(Challenge, related_name='failures')
|
challenge = ForeignKeyField(Challenge, related_name='failures')
|
||||||
attempt = CharField()
|
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
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import config
|
import config
|
||||||
|
|
||||||
|
|
||||||
def get_all_scores(teams, solves, adjustments):
|
def get_all_scores(teams, solves, adjustments):
|
||||||
scores = {team.id: 0 for team in teams}
|
scores = {team.id: 0 for team in teams}
|
||||||
for solve in solves:
|
for solve in solves:
|
||||||
|
@ -13,6 +14,7 @@ def get_all_scores(teams, solves, adjustments):
|
||||||
|
|
||||||
return scores
|
return scores
|
||||||
|
|
||||||
|
|
||||||
def get_last_solves(teams, solves):
|
def get_last_solves(teams, solves):
|
||||||
last = {team.id: datetime(1970, 1, 1) for team in teams}
|
last = {team.id: datetime(1970, 1, 1) for team in teams}
|
||||||
for solve in solves:
|
for solve in solves:
|
||||||
|
@ -20,6 +22,7 @@ def get_last_solves(teams, solves):
|
||||||
last[solve.team_id] = solve.time
|
last[solve.team_id] = solve.time
|
||||||
return last
|
return last
|
||||||
|
|
||||||
|
|
||||||
def calculate_scores():
|
def calculate_scores():
|
||||||
solves = ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge)
|
solves = ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge)
|
||||||
adjustments = ScoreAdjustment.select()
|
adjustments = ScoreAdjustment.select()
|
||||||
|
@ -39,10 +42,11 @@ def calculate_scores():
|
||||||
# eligible, teamid, teamname, affiliation, score
|
# 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]])))]
|
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):
|
def calculate_graph(scoredata):
|
||||||
solves = list(ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge).order_by(ChallengeSolve.time))
|
solves = list(ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge).order_by(ChallengeSolve.time))
|
||||||
adjustments = list(ScoreAdjustment.select())
|
adjustments = list(ScoreAdjustment.select())
|
||||||
scoredata = [i for i in scoredata if i[0]] # Only eligible teams are on the score graph
|
scoredata = [i for i in scoredata if i[0]] # Only eligible teams are on the score graph
|
||||||
graph_data = []
|
graph_data = []
|
||||||
for eligible, tid, name, affiliation, score in scoredata[:config.teams_on_graph]:
|
for eligible, tid, name, affiliation, score in scoredata[:config.teams_on_graph]:
|
||||||
our_solves = [i for i in solves if i.team_id == tid]
|
our_solves = [i for i in solves if i.team_id == tid]
|
|
@ -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 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
|
||||||
import utils.admin
|
import utils.admin
|
||||||
import utils.scoreboard
|
from data import scoreboard
|
||||||
from utils.decorators import admin_required, csrf_check
|
from utils.decorators import admin_required, csrf_check
|
||||||
from utils.notification import make_link
|
from utils.notification import make_link
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from config import secret
|
from config import secret
|
||||||
admin = Blueprint("admin", "admin", url_prefix="/admin")
|
admin = Blueprint("admin", __name__, url_prefix="/admin")
|
||||||
|
|
||||||
|
|
||||||
@admin.route("/")
|
@admin.route("/")
|
||||||
def admin_root():
|
def admin_root():
|
||||||
|
@ -16,6 +17,7 @@ def admin_root():
|
||||||
else:
|
else:
|
||||||
return redirect(url_for(".admin_login"))
|
return redirect(url_for(".admin_login"))
|
||||||
|
|
||||||
|
|
||||||
@admin.route("/login/", methods=["GET", "POST"])
|
@admin.route("/login/", methods=["GET", "POST"])
|
||||||
def admin_login():
|
def admin_login():
|
||||||
if request.method == "GET":
|
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̷͍̤̠͖̣͈.̺̩̦̻.")
|
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")
|
return render_template("admin/login.html")
|
||||||
|
|
||||||
|
|
||||||
@admin.route("/dashboard/")
|
@admin.route("/dashboard/")
|
||||||
@admin_required
|
@admin_required
|
||||||
def admin_dashboard():
|
def admin_dashboard():
|
||||||
teams = Team.select()
|
teams = Team.select()
|
||||||
solves = ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge)
|
solves = ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge)
|
||||||
adjustments = ScoreAdjustment.select()
|
adjustments = ScoreAdjustment.select()
|
||||||
scoredata = utils.scoreboard.get_all_scores(teams, solves, adjustments)
|
scoredata = scoreboard.get_all_scores(teams, solves, adjustments)
|
||||||
lastsolvedata = utils.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)
|
||||||
|
|
||||||
|
|
||||||
@admin.route("/tickets/")
|
@admin.route("/tickets/")
|
||||||
@admin_required
|
@admin_required
|
||||||
def admin_tickets():
|
def admin_tickets():
|
||||||
tickets = list(TroubleTicket.select(TroubleTicket, Team).join(Team).order_by(TroubleTicket.id.desc()))
|
tickets = list(TroubleTicket.select(TroubleTicket, Team).join(Team).order_by(TroubleTicket.id.desc()))
|
||||||
return render_template("admin/tickets.html", tickets=tickets)
|
return render_template("admin/tickets.html", tickets=tickets)
|
||||||
|
|
||||||
|
|
||||||
@admin.route("/tickets/<int:ticket>/")
|
@admin.route("/tickets/<int:ticket>/")
|
||||||
@admin_required
|
@admin_required
|
||||||
def admin_ticket_detail(ticket):
|
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))
|
comments = list(TicketComment.select().where(TicketComment.ticket == ticket).order_by(TicketComment.time))
|
||||||
return render_template("admin/ticket_detail.html", ticket=ticket, comments=comments)
|
return render_template("admin/ticket_detail.html", ticket=ticket, comments=comments)
|
||||||
|
|
||||||
|
|
||||||
@admin.route("/tickets/<int:ticket>/comment/", methods=["POST"])
|
@admin.route("/tickets/<int:ticket>/comment/", methods=["POST"])
|
||||||
@admin_required
|
@admin_required
|
||||||
def admin_ticket_comment(ticket):
|
def admin_ticket_comment(ticket):
|
||||||
|
@ -89,12 +95,14 @@ def admin_ticket_comment(ticket):
|
||||||
|
|
||||||
return redirect(url_for(".admin_ticket_detail", ticket=ticket.id))
|
return redirect(url_for(".admin_ticket_detail", ticket=ticket.id))
|
||||||
|
|
||||||
|
|
||||||
@admin.route("/team/<int:tid>/")
|
@admin.route("/team/<int:tid>/")
|
||||||
@admin_required
|
@admin_required
|
||||||
def admin_show_team(tid):
|
def admin_show_team(tid):
|
||||||
team = Team.get(Team.id == tid)
|
team = Team.get(Team.id == tid)
|
||||||
return render_template("admin/team.html", team=team)
|
return render_template("admin/team.html", team=team)
|
||||||
|
|
||||||
|
|
||||||
@admin.route("/team/<int:tid>/<csrf>/impersonate/")
|
@admin.route("/team/<int:tid>/<csrf>/impersonate/")
|
||||||
@csrf_check
|
@csrf_check
|
||||||
@admin_required
|
@admin_required
|
||||||
|
@ -102,6 +110,7 @@ def admin_impersonate_team(tid):
|
||||||
session["team_id"] = tid
|
session["team_id"] = tid
|
||||||
return redirect(url_for("scoreboard"))
|
return redirect(url_for("scoreboard"))
|
||||||
|
|
||||||
|
|
||||||
@admin.route("/team/<int:tid>/<csrf>/toggle_eligibility/")
|
@admin.route("/team/<int:tid>/<csrf>/toggle_eligibility/")
|
||||||
@csrf_check
|
@csrf_check
|
||||||
@admin_required
|
@admin_required
|
||||||
|
@ -112,6 +121,7 @@ def admin_toggle_eligibility(tid):
|
||||||
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/")
|
@admin.route("/team/<int:tid>/<csrf>/toggle_eligibility_lock/")
|
||||||
@csrf_check
|
@csrf_check
|
||||||
@admin_required
|
@admin_required
|
||||||
|
@ -122,6 +132,7 @@ def admin_toggle_eligibility_lock(tid):
|
||||||
flash("Eligibility lock set to {}".format(team.eligibility_locked))
|
flash("Eligibility lock set to {}".format(team.eligibility_locked))
|
||||||
return redirect(url_for(".admin_show_team", tid=tid))
|
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):
|
||||||
|
@ -135,6 +146,7 @@ def admin_score_adjust(tid):
|
||||||
|
|
||||||
return redirect(url_for(".admin_show_team", tid=tid))
|
return redirect(url_for(".admin_show_team", tid=tid))
|
||||||
|
|
||||||
|
|
||||||
@admin.route("/logout/")
|
@admin.route("/logout/")
|
||||||
def admin_logout():
|
def admin_logout():
|
||||||
del session["admin"]
|
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>
|
<img class="top-logo" src={{ url_for('static', filename='img/logo.png') }}></img>
|
||||||
<ul class="right hide-on-med-and-down">
|
<ul class="right hide-on-med-and-down">
|
||||||
{% if logged_in %}
|
{% if logged_in %}
|
||||||
<li><a href="{{ url_for('logout') }}">Logout</a></li>
|
<li><a href="{{ url_for('users.logout') }}">Logout</a></li>
|
||||||
<li><a href="{{ url_for('user_dashboard') }}">{{ user.username }}</a></li>
|
<li><a href="{{ url_for('users.dashboard') }}">{{ user.username }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not logged_in %}
|
{% if not logged_in %}
|
||||||
{% if config.registration %}
|
{% if config.registration %}
|
||||||
<li><a href="{{ url_for('register') }}">Register</a></li>
|
<li><a href="{{ url_for('users.register') }}">Register</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li><a href="{{ url_for('login') }}">Login</a></li>
|
<li><a href="{{ url_for('users.login') }}">Login</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,12 +50,12 @@
|
||||||
|
|
||||||
{% if config.competition_is_running() %}
|
{% if config.competition_is_running() %}
|
||||||
{% if logged_in %}
|
{% 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-icon"><i class="material-icons blue-text">flag</i></div>
|
||||||
<div class="side-text">Challenges</div>
|
<div class="side-text">Challenges</div>
|
||||||
</a></li>
|
</a></li>
|
||||||
{% endif %}
|
{% 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-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>
|
||||||
|
@ -69,7 +69,7 @@
|
||||||
<div class="side-icon"><i class="material-icons green-text">chat</i></div>
|
<div class="side-icon"><i class="material-icons green-text">chat</i></div>
|
||||||
<div class="side-text">Chat</div>
|
<div class="side-text">Chat</div>
|
||||||
</a></li>
|
</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-icon"><i class="material-icons amber-text">report_problem</i></div>
|
||||||
<div class="side-text">Tickets</div>
|
<div class="side-text">Tickets</div>
|
||||||
</a></li>
|
</a></li>
|
||||||
|
@ -78,27 +78,27 @@
|
||||||
<div class="divider" /></div>
|
<div class="divider" /></div>
|
||||||
{% if logged_in %}
|
{% 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-icon"><i class="material-icons indigo-text">track_changes</i></div>
|
||||||
<div class="side-text">My Team</div>
|
<div class="side-text">My Team</div>
|
||||||
</a></li>
|
</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-icon"><i class="material-icons teal-text">account_circle</i></div>
|
||||||
<div class="side-text">{{ user.username }}</div>
|
<div class="side-text">{{ user.username }}</div>
|
||||||
</a></li>
|
</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-icon"><i class="material-icons red-text">block</i></div>
|
||||||
<div class="side-text">Logout</div>
|
<div class="side-text">Logout</div>
|
||||||
</a></li>
|
</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not logged_in %}
|
{% if not logged_in %}
|
||||||
{% if config.registration %}
|
{% 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-icon"><i class="material-icons indigo-text">person_add</i></div>
|
||||||
<div class="side-text">Register</div>
|
<div class="side-text">Register</div>
|
||||||
</a></li>
|
</a></li>
|
||||||
{% endif %}
|
{% 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-icon"><i class="material-icons amber-text">lock</i></div>
|
||||||
<div class="side-text">Login</div>
|
<div class="side-text">Login</div>
|
||||||
</a></li>
|
</a></li>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Solves for {{ challenge.name }}</h2>
|
<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>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
|
@ -64,13 +64,13 @@
|
||||||
<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>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>
|
</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<br /><br />
|
<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>
|
</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="row no-bot">
|
||||||
<div class="col s12 m10">
|
<div class="col s12 m10">
|
||||||
<div class="input-field">
|
<div class="input-field">
|
|
@ -53,7 +53,7 @@
|
||||||
Team
|
Team
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<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>
|
{{ team.affiliation }}.</p>
|
||||||
<p>Your team is currently marked {{ "eligible" if team.eligible() else "ineligible" }}.</p>
|
<p>Your team is currently marked {{ "eligible" if team.eligible() else "ineligible" }}.</p>
|
||||||
</div>
|
</div>
|
|
@ -9,7 +9,7 @@
|
||||||
<small><abbr class="time" title="{{ comment.time }}">{{ comment.time }}</abbr> · {{ comment.comment_by }}</small>
|
<small><abbr class="time" title="{{ comment.time }}">{{ comment.time }}</abbr> · {{ comment.comment_by }}</small>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<br />
|
<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">
|
<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>
|
|
@ -3,11 +3,11 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Trouble tickets</h2>
|
<h2>Trouble tickets</h2>
|
||||||
{% if tickets %}
|
{% 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">
|
<div class="collection">
|
||||||
{% for ticket in tickets %}
|
{% for ticket in tickets %}
|
||||||
{% if ticket.active %}
|
{% 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 %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,11 +16,11 @@ 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('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 %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% 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 %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -17,7 +17,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row no-bot">
|
<div class="row no-bot">
|
||||||
<div class="col s6">
|
<div class="col s6">
|
||||||
<a href="{{ url_for('forgot_password') }}">Forgot password?</a>
|
<a href="{{ url_for('.forgot_password') }}">Forgot password?</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col s6">
|
<div class="col s6">
|
||||||
<button class="btn waves-effect waves-light right" type="submit">Login</button>
|
<button class="btn waves-effect waves-light right" type="submit">Login</button>
|
|
@ -1,18 +1,19 @@
|
||||||
from ctferror import *
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from . import misc
|
from . import misc
|
||||||
|
from exceptions import CaptchaError
|
||||||
|
|
||||||
import config
|
import config
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
|
||||||
def verify_captcha():
|
def verify_captcha():
|
||||||
if "g-recaptcha-response" not in request.form:
|
if "g-recaptcha-response" not in request.form:
|
||||||
return CAPTCHA_NOT_COMPLETED
|
raise CaptchaError("Captcha not completed!")
|
||||||
|
|
||||||
captcha_response = request.form["g-recaptcha-response"]
|
captcha_response = request.form["g-recaptcha-response"]
|
||||||
verify_data = dict(secret=config.secret.recaptcha_secret, response=captcha_response, remoteip=misc.get_ip())
|
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"]
|
result = requests.post("https://www.google.com/recaptcha/api/siteverify", verify_data).json()["success"]
|
||||||
if not result:
|
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 random
|
||||||
import config
|
import config
|
||||||
import json
|
from flask import request
|
||||||
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
|
|
||||||
|
|
||||||
allowed_chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
allowed_chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
|
||||||
|
|
||||||
def generate_random_string(length=32, chars=allowed_chars):
|
def generate_random_string(length=32, chars=allowed_chars):
|
||||||
r = random.SystemRandom()
|
r = random.SystemRandom()
|
||||||
return "".join([r.choice(chars) for i in range(length)])
|
return "".join([r.choice(chars) for i in range(length)])
|
||||||
|
|
||||||
|
|
||||||
def generate_team_key():
|
def generate_team_key():
|
||||||
return config.ctf_name.lower() + "_" + generate_random_string(32, allowed_chars)
|
return config.ctf_name.lower() + "_" + generate_random_string(32, allowed_chars)
|
||||||
|
|
||||||
|
|
||||||
def generate_confirmation_key():
|
def generate_confirmation_key():
|
||||||
return generate_random_string(48)
|
return generate_random_string(48)
|
||||||
|
|
||||||
|
|
||||||
def get_ip():
|
def get_ip():
|
||||||
return request.headers.get(config.proxied_ip_header, request.remote_addr)
|
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