lots of things!
parent
3e2590090d
commit
d93bc0d438
|
@ -5,3 +5,4 @@ dump.rdb
|
|||
/problems
|
||||
__pycache__
|
||||
/static/*
|
||||
/secrets
|
||||
|
|
29
app.py
29
app.py
|
@ -8,6 +8,7 @@ from peewee import fn
|
|||
import config
|
||||
import utils
|
||||
import redis
|
||||
import requests
|
||||
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
@ -37,10 +38,13 @@ def root():
|
|||
@app.route('/scoreboard/')
|
||||
def scoreboard():
|
||||
data = utils.get_complex("scoreboard")
|
||||
if not data:
|
||||
graphdata = utils.get_complex("graph")
|
||||
if not data or not graphdata:
|
||||
data = utils.calculate_scores()
|
||||
utils.set_complex("scoreboard", data, 10)
|
||||
return render_template("scoreboard.html", data=data)
|
||||
graphdata = utils.calculate_graph(data)
|
||||
utils.set_complex("scoreboard", data, 120)
|
||||
utils.set_complex("graph", graphdata, 120)
|
||||
return render_template("scoreboard.html", data=data, graphdata=graphdata)
|
||||
|
||||
@app.route('/login/', methods=["GET", "POST"])
|
||||
def login():
|
||||
|
@ -64,12 +68,24 @@ def register():
|
|||
if request.method == "GET":
|
||||
return render_template("register.html")
|
||||
elif request.method == "POST":
|
||||
if "g-recaptcha-response" not in request.form:
|
||||
flash("Please complete the CAPTCHA.")
|
||||
return render_template("register.html")
|
||||
|
||||
captcha_response = request.form["g-recaptcha-response"]
|
||||
verify_data = dict(secret=config.secret.recaptcha_secret, response=captcha_response, remoteip=utils.get_ip())
|
||||
result = requests.post("https://www.google.com/recaptcha/api/siteverify", verify_data).json()["success"]
|
||||
if not result:
|
||||
flash("Invalid CAPTCHA response.")
|
||||
return render_template("register.html")
|
||||
|
||||
team_name = request.form["team_name"]
|
||||
team_email = request.form["team_email"]
|
||||
team_elig = "team_eligibility" in request.form
|
||||
affiliation = request.form["affiliation"]
|
||||
team_key = utils.generate_team_key()
|
||||
team = Team.create(name=team_name, email=team_email, eligible=team_elig, affiliation=affiliation, key=team_key)
|
||||
TeamAccess.create(team=team, ip=utils.get_ip(), time=datetime.now())
|
||||
session["team_id"] = team.id
|
||||
flash("Team created.")
|
||||
return redirect(url_for('dashboard'))
|
||||
|
@ -98,7 +114,12 @@ def dashboard():
|
|||
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("dashboard.html", team_solves=team_solves, team_adjustments=team_adjustments, team_score=team_score)
|
||||
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)
|
||||
elif request.method == "POST":
|
||||
team_name = request.form["team_name"]
|
||||
team_email = request.form["team_email"]
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
ctf_name = "TJCTF"
|
||||
eligibility = "In order to be eligible for prizes, all members of your team must be in high school, and you must not have more than four team members."
|
||||
tagline = "a cybersecurity competition created by TJHSST students"
|
||||
competition_is_running = True
|
||||
cdn = True
|
||||
proxied_ip_header = "X-Forwarded-For"
|
||||
flag_rl = 10
|
||||
teams_on_graph = 10
|
||||
|
||||
# Don't touch these. Instead, copy secrets.example to secrets and edit that.
|
||||
import yaml
|
||||
from collections import namedtuple
|
||||
with open("secrets") as f:
|
||||
_secret = yaml.load(f)
|
||||
secret = namedtuple('SecretsDict', _secret.keys())(**_secret)
|
||||
|
|
18
ctftool
18
ctftool
|
@ -1,5 +1,6 @@
|
|||
#!/usr/bin/python3
|
||||
from database import *
|
||||
from datetime import datetime, timedelta
|
||||
import sys
|
||||
import yaml
|
||||
import random
|
||||
|
@ -26,7 +27,22 @@ elif operation == "gen-challenge":
|
|||
n = int(sys.argv[2])
|
||||
for i in range(n):
|
||||
name = str(random.randint(0, 999999999))
|
||||
chal = Challenge.create(name="Challenge {}".format(name), category="Generated", description="Free flags. it's the number in the problem name lorem ipsum dolor sit whatever i don't even care", points=random.randint(50, 400), flag=name)
|
||||
chal = Challenge.create(name="Challenge".format(name), category="Generated", description="Lorem ipsum, dolor sit amet. The flag is {}".format(name), points=random.randint(50, 400), flag=name)
|
||||
print("Challenge added with id {}".format(chal.id))
|
||||
|
||||
elif operation == "gen-team":
|
||||
n = int(sys.argv[2])
|
||||
chals = list(Challenge.select())
|
||||
ctz = datetime.now()
|
||||
diff = timedelta(minutes=5)
|
||||
for i in range(n):
|
||||
name = "Team {}".format(i + 1)
|
||||
t = Team.create(name=name, email="none@none.com", affiliation="Autogenerated", eligible=True, key="")
|
||||
t.key = "autogen{}".format(t.id)
|
||||
t.save()
|
||||
print("Team added with id {}".format(t.id))
|
||||
for i in random.sample(chals, 5):
|
||||
ChallengeSolve.create(team=t, challenge=i, time=ctz)
|
||||
ctz = ctz + (diff * random.randint(-10, 10))
|
||||
|
||||
# vim: syntax=python:ft=python
|
||||
|
|
|
@ -10,6 +10,7 @@ class Team(BaseModel):
|
|||
email = CharField()
|
||||
affiliation = CharField()
|
||||
eligible = BooleanField()
|
||||
first_login = BooleanField(default=True)
|
||||
key = CharField()
|
||||
|
||||
def solved(self, challenge):
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<title>{{ config.ctf_name }} :: {% block title %}Home{% endblock %}</title>
|
||||
{% if config.cdn %}
|
||||
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.1/css/materialize.min.css" />
|
||||
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/vis/4.9.0/vis.min.css" />
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||
{% else %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='materialize.min.css') }}" />
|
||||
|
@ -16,8 +17,10 @@
|
|||
<div class="container">
|
||||
<div class="nav-wrapper">
|
||||
<ul class="left">
|
||||
{% if config.competition_is_running and logged_in %}
|
||||
{% if config.competition_is_running %}
|
||||
{% if logged_in %}
|
||||
<li><a href="{{ url_for('challenges') }}">Challenges</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{{ url_for('scoreboard') }}">Scoreboard</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
@ -43,6 +46,7 @@
|
|||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.1/js/materialize.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.4.3/jquery.timeago.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.9.0/vis.min.js"></script>
|
||||
<script>$("abbr.time").timeago();</script>
|
||||
{% else %}
|
||||
<script src="{{ url_for('static', filename='jquery.min.js') }}"></script>
|
||||
|
@ -53,5 +57,7 @@
|
|||
Materialize.toast({{ message | tojson }}, 4000);
|
||||
{% endfor %}
|
||||
</script>
|
||||
{% block postscript %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -36,10 +36,10 @@
|
|||
<div class="col s12">
|
||||
<div class="card blue darken-1">
|
||||
<div class="card-content white-text">
|
||||
<span class="card-title">Login information</span>
|
||||
<p>Your team key is <code>{{ team.key }}</code>. Share this with your teammates,
|
||||
and keep it in a safe place. <strong>You need your team key in order to log in.
|
||||
</strong>If you lose it, an organizer can send it to your team email, which is shown below.</p>
|
||||
<span class="card-title">Team key: <code>{{ team.key }}</code></span>
|
||||
<p>Share this with your teammates, and keep it in a safe place. <strong>You need your team key
|
||||
in order to log in.</strong> If you lose it, an organizer can send it to your team email,
|
||||
which is shown below.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -127,4 +127,24 @@
|
|||
{% endif %}
|
||||
<p class="right"><strong class="bigger">Your score is {{ team_score }}.</strong></p>
|
||||
</section>
|
||||
<div id="firstlogin" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Welcome to {{ config.ctf_name }}!</h4>
|
||||
<p>Hi, {{ team.name }}! Welcome to {{ config.ctf_name }}, {{ config.tagline }}.</p>
|
||||
|
||||
<p>First off, please check the email you signed up with. You must confirm your account to solve problems. If needed, you can change the Team Email or resend confirmation on this page.</p>
|
||||
|
||||
<p>Your Team Key will be under Login Information. It was also sent to your team email. This is the only way to log in, so distribute it to your team members.</p>
|
||||
|
||||
<p>Once that's done, we recommend you read the Rules and FAQ. Then, do some Practice to warm up for the competition, or join the Chat to meet your fellow competitors and ask questions. The competition will begin soon enough, but in the meanwhile, explore the site!</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a href="#!" class="modal-action modal-close waves-effect waves-green btn-flat">Dismiss</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block postscript %}
|
||||
{% if first_login %}
|
||||
<script>$("#firstlogin").openModal();</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,6 +1,20 @@
|
|||
{% extends "base.html" %}
|
||||
{% block head %}
|
||||
<style>
|
||||
body {
|
||||
background-color: #1565C0
|
||||
}
|
||||
nav {
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Login</h2>
|
||||
<div class="row" style="margin-top: 30px;">
|
||||
<div class="col s8 offset-s2">
|
||||
<div class="card gray">
|
||||
<div class="card-content center">
|
||||
<span class="card-title black-text">Login</span>
|
||||
<form method="POST">
|
||||
<div class="input-field">
|
||||
<input id="team-key" name="team_key" type="text" />
|
||||
|
@ -9,4 +23,9 @@
|
|||
</div>
|
||||
<button class="btn waves-effect waves-light" type="submit">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
{% block head %}
|
||||
<script src='https://www.google.com/recaptcha/api.js'></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Register a Team</h2>
|
||||
<p>After registering, you will be directed to your team's dashboard. This will
|
||||
|
@ -26,8 +29,9 @@ team members.</strong></p>
|
|||
status before sending you prizes.</p>
|
||||
<input id="team-eligibility" name="team_eligibility" type="checkbox" />
|
||||
<label for="team-eligibility">Eligibility Certification</label>
|
||||
<input name="_csrf_token" type="hidden" value="{{ csrf_token() }}" />
|
||||
<br /><br />
|
||||
<div class="g-recaptcha" data-sitekey="{{ config.secret.recaptcha_key }}"></div>
|
||||
<input name="_csrf_token" type="hidden" value="{{ csrf_token() }}" />
|
||||
<button class="btn waves-effect waves-light" type="submit">Create team</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
@ -18,10 +18,12 @@
|
|||
}
|
||||
function hideIneligible() {
|
||||
$(".teamrow.ineligible").removeClass("visible");
|
||||
$("#toggle").html("Show ineligible teams");
|
||||
recalcRanks();
|
||||
}
|
||||
function showIneligible() {
|
||||
$(".teamrow.ineligible").addClass("visible");
|
||||
$("#toggle").html("Hide ineligible teams");
|
||||
recalcRanks();
|
||||
}
|
||||
function toggleIneligible() {
|
||||
|
@ -36,15 +38,35 @@
|
|||
{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Scoreboard</h2>
|
||||
<a href="javascript:toggleIneligible()">Toggle ineligible team display</a>
|
||||
<div id="chart"></div>
|
||||
<a href="javascript:toggleIneligible()" id="toggle">Hide ineligible teams</a>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Rank</th><th>Team</th><th>Affiliation</th><th>Score</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for eligible, teamid, team, affiliation, score in data %}
|
||||
<tr class="teamrow {% if not eligible %}in{% endif %}eligible {% if teamid == session.team_id %}blue lighten-3 {% endif %}visible"><td class="rank">{{ rank }}</td><td>{{ team }}</td><td>{{ affiliation }}</td><td>{{ score }}</td></tr>
|
||||
<tr class="teamrow {% if not eligible %}in{% endif %}eligible {% if eligible and teamid == session.team_id %}blue lighten-3 {% endif %}visible"><td class="rank">{{ rank }}</td><td>{{ team }}</td><td>{{ affiliation }}</td><td>{{ score }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% block postscript %}
|
||||
<script>
|
||||
var graphData = {{ graphdata | tojson }};
|
||||
var groupn = 0;
|
||||
var groups = new vis.DataSet();
|
||||
var items = [];
|
||||
graphData.forEach(function(team) {
|
||||
var teamName = team[0];
|
||||
var scoreEvents = team[1];
|
||||
groups.add({ id: groupn, content: teamName, options: { drawPoints: false, interpolation: false } });
|
||||
scoreEvents.forEach(function(evt) {
|
||||
items.push({ x: evt[0], y: evt[1], group: groupn });
|
||||
});
|
||||
groupn++;
|
||||
});
|
||||
var dataset = new vis.DataSet(items);
|
||||
var graph = new vis.Graph2d(document.getElementById('chart'), dataset, groups, {legend: true, dataAxis: {left: {range: {min: 0}}}});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
26
utils.py
26
utils.py
|
@ -1,6 +1,7 @@
|
|||
import random
|
||||
import config
|
||||
import json
|
||||
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
|
||||
|
@ -37,17 +38,38 @@ def competition_running_required(f):
|
|||
def calculate_scores():
|
||||
solves = ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge)
|
||||
adjustments = ScoreAdjustment.select()
|
||||
teams = Team.select(Team.id, Team, ChallengeSolve).join(ChallengeSolve)
|
||||
teams = Team.select()
|
||||
|
||||
team_solves = {team.id: [] for team in teams}
|
||||
team_mapping = {team.id: team for team in teams}
|
||||
scores = {team.id: 0 for team in teams}
|
||||
most_recent_solve = {team.id: max([i.time for i in team.solves]) for team in teams}
|
||||
for solve in solves:
|
||||
scores[solve.team_id] += solve.challenge.points
|
||||
team_solves[solve.team_id].append(solve)
|
||||
for adjustment in adjustments:
|
||||
scores[adjustment.team_id] += adjustment.value
|
||||
|
||||
most_recent_solve = {tid: max([i.time for i in team_solves[tid]]) for tid in team_solves if team_solves[tid]}
|
||||
scores = {i: j for i, j in scores.items() if i in most_recent_solve}
|
||||
return [(team_mapping[i[0]].eligible, i[0], team_mapping[i[0]].name, team_mapping[i[0]].affiliation, i[1]) for idx, i in enumerate(sorted(scores.items(), key=lambda k: (-k[1], most_recent_solve[k[0]])))]
|
||||
|
||||
def calculate_graph(scoredata):
|
||||
solves = list(ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge))
|
||||
adjustments = list(ScoreAdjustment.select())
|
||||
scoredata = [i for i in scoredata if i[0]] # Only eligible teams are on the score graph
|
||||
graph_data = []
|
||||
for eligible, tid, name, affiliation, score in scoredata[:config.teams_on_graph]:
|
||||
our_solves = [i for i in solves if i.team_id == tid]
|
||||
team_data = []
|
||||
s = sum([i.value for i in adjustments if i.team_id == tid])
|
||||
for i in sorted(our_solves, key=lambda k: k.time):
|
||||
team_data.append((str(i.time), s))
|
||||
s += i.challenge.points
|
||||
team_data.append((str(i.time), s))
|
||||
team_data.append((str(datetime.now()), score))
|
||||
graph_data.append((name, team_data))
|
||||
return graph_data
|
||||
|
||||
def get_complex(key):
|
||||
i = g.redis.get(key)
|
||||
if i is None:
|
||||
|
|
Loading…
Reference in New Issue