ColdCore/app.py

342 lines
12 KiB
Python
Raw Normal View History

2016-03-14 03:17:21 +00:00
from flask import Flask, render_template, session, redirect, url_for, request, g, flash, jsonify
2015-11-08 06:10:26 +00:00
app = Flask(__name__)
from database import Team, TeamAccess, Challenge, ChallengeSolve, ChallengeFailure, ScoreAdjustment, TroubleTicket, TicketComment, Notification, db
2015-11-08 06:10:26 +00:00
from datetime import datetime
from peewee import fn
2015-11-10 16:51:45 +00:00
2015-11-12 20:47:30 +00:00
from utils import decorators, flag, cache, misc, captcha, email
2015-11-10 16:51:45 +00:00
import utils.scoreboard
2015-11-08 06:10:26 +00:00
import config
import utils
import redis
2015-11-08 19:04:02 +00:00
import requests
2016-03-14 03:15:15 +00:00
import socket
2015-11-08 06:10:26 +00:00
2015-11-29 02:40:11 +00:00
app.secret_key = config.secret.key
2015-11-08 06:10:26 +00:00
import logging
logging.basicConfig(level=logging.DEBUG)
@app.before_request
def make_info_available():
if "team_id" in session:
g.team = Team.get(Team.id == session["team_id"])
2015-12-17 12:57:35 +00:00
g.team_restricts = g.team.restricts.split(",")
2015-11-08 06:10:26 +00:00
@app.context_processor
def scoreboard_variables():
var = dict(config=config)
if "team_id" in session:
var["logged_in"] = True
var["team"] = g.team
var["notifications"] = Notification.select().where(Notification.team == g.team)
2015-11-08 06:10:26 +00:00
else:
var["logged_in"] = False
var["notifications"] = []
2015-11-08 06:10:26 +00:00
return var
2015-11-12 03:41:04 +00:00
# Blueprints
from modules import api, admin
2015-11-12 03:41:04 +00:00
app.register_blueprint(api.api)
app.register_blueprint(admin.admin)
2015-11-12 03:41:04 +00:00
2015-11-08 06:10:26 +00:00
# Publically accessible things
@app.route('/')
def root():
return redirect(url_for('scoreboard'))
2016-04-17 17:18:44 +00:00
@app.route('/chat/')
def chat():
return render_template("chat.html")
2015-11-08 06:10:26 +00:00
@app.route('/scoreboard/')
def scoreboard():
2015-11-10 16:51:45 +00:00
data = cache.get_complex("scoreboard")
graphdata = cache.get_complex("graph")
2015-11-08 19:04:02 +00:00
if not data or not graphdata:
2015-11-10 16:51:45 +00:00
data = utils.scoreboard.calculate_scores()
graphdata = utils.scoreboard.calculate_graph(data)
2016-03-14 02:48:37 +00:00
utils.scoreboard.set_complex("scoreboard", data, 120)
utils.scoreboard.set_complex("graph", graphdata, 120)
2015-11-10 04:41:07 +00:00
2015-11-12 16:40:55 +00:00
return render_template("scoreboard.html", data=data, graphdata=graphdata)
2015-11-08 06:10:26 +00:00
@app.route('/login/', methods=["GET", "POST"])
def login():
if request.method == "GET":
return render_template("login.html")
elif request.method == "POST":
team_key = request.form["team_key"]
try:
team = Team.get(Team.key == team_key)
2015-11-10 16:51:45 +00:00
TeamAccess.create(team=team, ip=misc.get_ip(), time=datetime.now())
2015-11-08 06:10:26 +00:00
session["team_id"] = team.id
flash("Login success.")
return redirect(url_for('dashboard'))
except Team.DoesNotExist:
flash("Couldn't find your team. Check your team key.", "error")
return render_template("login.html")
@app.route('/register/', methods=["GET", "POST"])
def register():
2016-04-17 16:33:42 +00:00
if not config.registration:
return "Registration is currently disabled."
2015-11-08 06:10:26 +00:00
if request.method == "GET":
return render_template("register.html")
elif request.method == "POST":
2015-11-11 12:41:01 +00:00
error, message = captcha.verify_captcha()
if error:
flash(message)
2015-11-08 19:04:02 +00:00
return render_template("register.html")
team_name = request.form["team_name"].strip()
team_email = request.form["team_email"].strip()
2015-11-08 06:10:26 +00:00
team_elig = "team_eligibility" in request.form
affiliation = request.form["affiliation"].strip()
if len(team_name) > 50 or not team_name:
flash("You must have a team name!")
return render_template("register.html")
if not (team_email and "." in team_email and "@" in team_email):
flash("You must have a valid team email!")
return render_template("register.html")
if not affiliation:
affiliation = "No affiliation"
2015-11-10 16:51:45 +00:00
team_key = misc.generate_team_key()
confirmation_key = misc.generate_confirmation_key()
2015-11-11 12:41:01 +00:00
2015-11-09 02:44:04 +00:00
team = Team.create(name=team_name, email=team_email, eligible=team_elig, affiliation=affiliation, key=team_key,
email_confirmation_key=confirmation_key)
2015-11-10 16:51:45 +00:00
TeamAccess.create(team=team, ip=misc.get_ip(), time=datetime.now())
2015-11-09 02:44:04 +00:00
2015-11-10 16:51:45 +00:00
email.send_confirmation_email(team_email, confirmation_key, team_key)
2015-11-09 02:44:04 +00:00
2015-11-08 06:10:26 +00:00
session["team_id"] = team.id
flash("Team created.")
return redirect(url_for('dashboard'))
@app.route('/logout/')
def logout():
session.pop("team_id")
2015-11-08 08:19:44 +00:00
flash("You've successfully logged out.")
2015-11-08 06:10:26 +00:00
return redirect(url_for('root'))
# Things that require a team
2015-11-09 02:44:04 +00:00
@app.route('/confirm_email/', methods=["POST"])
2015-11-10 16:51:45 +00:00
@decorators.login_required
2015-11-09 02:44:04 +00:00
def confirm_email():
if request.form["confirmation_key"] == g.team.email_confirmation_key:
flash("Email confirmed!")
g.team.email_confirmed = True
g.team.save()
else:
flash("Incorrect confirmation key.")
return redirect(url_for('dashboard'))
2015-11-08 06:10:26 +00:00
@app.route('/team/', methods=["GET", "POST"])
2015-11-10 16:51:45 +00:00
@decorators.login_required
2015-11-08 06:10:26 +00:00
def 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])
2015-11-08 19:04:02 +00:00
first_login = False
if g.team.first_login:
first_login = True
g.team.first_login = False
g.team.save()
return render_template("dashboard.html", team_solves=team_solves, team_adjustments=team_adjustments, team_score=team_score, first_login=first_login)
2015-11-08 06:10:26 +00:00
elif request.method == "POST":
2015-11-10 04:41:07 +00:00
if g.redis.get("ul{}".format(session["team_id"])):
flash("You're changing your information too fast!")
return redirect(url_for('dashboard'))
team_name = request.form["team_name"].strip()
team_email = request.form["team_email"].strip()
affiliation = request.form["affiliation"].strip()
2015-11-08 06:10:26 +00:00
team_elig = "team_eligibility" in request.form
2015-11-09 02:44:04 +00:00
if len(team_name) > 50 or not team_name:
flash("You must have a team name!")
return render_template("dashboard.html")
if not (team_email and "." in team_email and "@" in team_email):
flash("You must have a valid team email!")
return render_template("dashboard.html")
if not affiliation:
affiliation = "No affiliation"
2015-11-09 02:44:04 +00:00
email_changed = (team_email != g.team.email)
2015-11-08 06:10:26 +00:00
g.team.name = team_name
g.team.email = team_email
g.team.affiliation = affiliation
if not g.team.eligibility_locked:
g.team.eligible = team_elig
g.redis.set("ul{}".format(session["team_id"]), str(datetime.now()), 120)
2015-11-09 02:44:04 +00:00
if email_changed:
2015-11-10 16:51:45 +00:00
g.team.email_confirmation_key = misc.generate_confirmation_key()
2015-11-09 02:44:04 +00:00
g.team.email_confirmed = False
2015-11-10 16:51:45 +00:00
misc.send_confirmation_email(team_email, g.team.email_confirmation_key, g.team.key)
2015-11-09 02:44:04 +00:00
flash("Changes saved. Please check your email for a new confirmation key.")
else:
flash("Changes saved.")
2015-11-08 06:10:26 +00:00
g.team.save()
2015-11-09 02:44:04 +00:00
2015-11-08 06:10:26 +00:00
return redirect(url_for('dashboard'))
@app.route('/challenges/')
2015-12-17 12:57:35 +00:00
@decorators.must_be_allowed_to("view challenges")
2015-11-10 16:51:45 +00:00
@decorators.competition_running_required
@decorators.confirmed_email_required
2015-11-08 06:10:26 +00:00
def challenges():
2016-04-27 22:07:39 +00:00
chals = Challenge.select().order_by(Challenge.points, Challenge.name)
2015-11-08 06:10:26 +00:00
solved = Challenge.select().join(ChallengeSolve).where(ChallengeSolve.team == g.team)
2015-12-03 23:18:00 +00:00
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}))
2015-12-03 23:18:00 +00:00
return render_template("challenges.html", challenges=chals, solved=solved, categories=categories, solves=solves)
@app.route('/challenges/<int:challenge>/solves/')
2015-12-17 12:57:35 +00:00
@decorators.must_be_allowed_to("view challenge solves")
@decorators.must_be_allowed_to("view challenges")
2015-12-03 23:18:00 +00:00
@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)
2015-11-08 06:10:26 +00:00
@app.route('/submit/<int:challenge>/', methods=["POST"])
2015-12-17 12:57:35 +00:00
@decorators.must_be_allowed_to("solve challenges")
@decorators.must_be_allowed_to("view challenges")
2015-11-10 16:51:45 +00:00
@decorators.competition_running_required
@decorators.confirmed_email_required
2015-11-08 06:10:26 +00:00
def submit(challenge):
chal = Challenge.get(Challenge.id == challenge)
2015-11-10 16:51:45 +00:00
flagval = request.form["flag"]
2015-11-08 06:10:26 +00:00
2015-11-10 16:51:45 +00:00
code, message = flag.submit_flag(g.team, chal, flagval)
flash(message)
2015-11-08 06:10:26 +00:00
return redirect(url_for('challenges'))
# Trouble tickets
@app.route('/tickets/')
2015-12-17 12:57:35 +00:00
@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"])
2015-12-17 12:57:35 +00:00
@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":
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>/')
2015-12-17 12:57:35 +00:00
@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)
return render_template("ticket_detail.html", ticket=ticket, comments=comments)
@app.route('/tickets/<int:ticket>/comment/', methods=["POST"])
2015-12-17 12:57:35 +00:00
@decorators.must_be_allowed_to("comment on tickets")
@decorators.must_be_allowed_to("view tickets")
def team_ticket_comment(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"))
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))
2016-03-14 03:15:15 +00:00
# Debug
@app.route('/debug/')
def debug_app():
return jsonify(hostname=socket.gethostname())
2015-11-08 06:10:26 +00:00
# Manage Peewee database sessions and Redis
@app.before_request
def before_request():
db.connect()
g.redis = redis.StrictRedis()
@app.teardown_request
def teardown_request(exc):
db.close()
g.redis.connection_pool.disconnect()
# CSRF things
@app.before_request
def csrf_protect():
if request.method == "POST":
2015-12-03 23:18:00 +00:00
token = session.get('_csrf_token', None)
2015-11-08 06:10:26 +00:00
if not token or token != request.form["_csrf_token"]:
return "Invalid CSRF token!"
def generate_csrf_token():
if '_csrf_token' not in session:
2015-11-10 16:51:45 +00:00
session['_csrf_token'] = misc.generate_random_string(64)
2015-11-08 06:10:26 +00:00
return session['_csrf_token']
app.jinja_env.globals['csrf_token'] = generate_csrf_token
if __name__ == '__main__':
app.run(debug=True, port=8001)