a few things:
- more admin features! - ctftool scan: scan a directory for problems - scoreboard stuffmaster
parent
b875100d5e
commit
834acad34c
3
app.py
3
app.py
|
@ -34,8 +34,9 @@ def scoreboard_variables():
|
|||
return var
|
||||
|
||||
# Blueprints
|
||||
from modules import api
|
||||
from modules import api, admin
|
||||
app.register_blueprint(api.api)
|
||||
app.register_blueprint(admin.admin)
|
||||
|
||||
# Publically accessible things
|
||||
|
||||
|
|
28
ctftool
28
ctftool
|
@ -1,18 +1,23 @@
|
|||
#!/usr/bin/python3
|
||||
from database import *
|
||||
from datetime import datetime, timedelta
|
||||
import getpass
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import yaml
|
||||
import random
|
||||
import utils
|
||||
import utils.admin
|
||||
|
||||
operation = sys.argv[1]
|
||||
if operation == "create-tables":
|
||||
db.create_tables([Team, TeamAccess, Challenge, ChallengeSolve, ChallengeFailure, ScoreAdjustment])
|
||||
db.create_tables([Team, TeamAccess, Challenge, ChallengeSolve, ChallengeFailure, ScoreAdjustment, AdminUser])
|
||||
print("Tables created")
|
||||
|
||||
elif operation == "drop-tables":
|
||||
if input("Are you sure? Type yes to continue: ") == "yes":
|
||||
db.drop_tables([Team, TeamAccess, Challenge, ChallengeSolve, ChallengeFailure, ScoreAdjustment])
|
||||
db.drop_tables([Team, TeamAccess, Challenge, ChallengeSolve, ChallengeFailure, ScoreAdjustment, AdminUser])
|
||||
print("Done")
|
||||
else:
|
||||
print("Okay, nothing happened.")
|
||||
|
@ -47,4 +52,23 @@ elif operation == "gen-team":
|
|||
ctz -= diff
|
||||
ChallengeSolve.create(team=Team.get(Team.id == (i % n) + 1), challenge=chal, time=ctz)
|
||||
|
||||
elif operation == "add-admin":
|
||||
username = input("Username: ")
|
||||
password = getpass.getpass().encode()
|
||||
pwhash = utils.admin.create_password(password)
|
||||
AdminUser.create(username=username, password=pwhash)
|
||||
print("AdminUser created")
|
||||
|
||||
elif operation == "scan":
|
||||
path = sys.argv[2]
|
||||
dirs = [j for j in [os.path.join(path, i) for i in os.listdir(path)] if os.path.isdir(j)]
|
||||
print(dirs)
|
||||
n = 0
|
||||
for d in dirs:
|
||||
if os.path.exists(os.path.join(d, "problem.yml")):
|
||||
with open(os.path.join(d, "problem.yml")) as f:
|
||||
n += 1
|
||||
Challenge.create(**yaml.load(f))
|
||||
print(n, "challenges loaded")
|
||||
|
||||
# vim: syntax=python:ft=python
|
||||
|
|
|
@ -32,6 +32,7 @@ class TeamAccess(BaseModel):
|
|||
class Challenge(BaseModel):
|
||||
name = CharField()
|
||||
category = CharField()
|
||||
author = CharField()
|
||||
description = TextField()
|
||||
points = IntegerField()
|
||||
flag = CharField()
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
from flask import Blueprint, render_template, request, session, redirect, url_for, flash
|
||||
from database import AdminUser, Team, Challenge, ChallengeSolve, ChallengeFailure, ScoreAdjustment
|
||||
import utils
|
||||
import utils.admin
|
||||
import utils.scoreboard
|
||||
admin = Blueprint("admin", "admin", url_prefix="/admin")
|
||||
|
||||
@admin.route("/")
|
||||
def admin_root():
|
||||
if "admin" in session:
|
||||
return redirect(url_for(".admin_dashboard"))
|
||||
else:
|
||||
return redirect(url_for(".admin_login"))
|
||||
|
||||
@admin.route("/login/", methods=["GET", "POST"])
|
||||
def admin_login():
|
||||
if request.method == "GET":
|
||||
return render_template("admin/login.html")
|
||||
|
||||
elif request.method == "POST":
|
||||
username = request.form["username"]
|
||||
password = request.form["password"]
|
||||
try:
|
||||
user = AdminUser.get(AdminUser.username == username)
|
||||
result = utils.admin.verify_password(user, password)
|
||||
if result:
|
||||
session["admin"] = user.username
|
||||
return redirect(url_for(".admin_dashboard"))
|
||||
except AdminUser.DoesNotExist:
|
||||
pass
|
||||
flash("Invalid username or password.")
|
||||
return render_template("admin/login.html")
|
||||
|
||||
@admin.route("/dashboard/")
|
||||
def admin_dashboard():
|
||||
teams = Team.select()
|
||||
solves = ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge)
|
||||
adjustments = ScoreAdjustment.select()
|
||||
scoredata = utils.scoreboard.get_all_scores(teams, solves, adjustments)
|
||||
lastsolvedata = utils.scoreboard.get_last_solves(teams, solves)
|
||||
return render_template("admin/dashboard.html", teams=teams, scoredata=scoredata, lastsolvedata=lastsolvedata)
|
||||
|
||||
@admin.route("/team/<int:tid>/")
|
||||
def admin_show_team(tid):
|
||||
team = Team.get(Team.id == tid)
|
||||
return render_template("admin/team.html", team=team)
|
||||
|
||||
@admin.route("/logout/")
|
||||
def admin_logout():
|
||||
del session["admin"]
|
||||
return redirect(url_for('.admin_login'))
|
|
@ -0,0 +1,57 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>{{ config.ctf_name }} Admin :: {% 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') }}" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='icons.css') }}" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='vis.min.css') }}" />
|
||||
{% endif %}
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% if "admin" in session %}
|
||||
<div class="navbar-fixed">
|
||||
<nav class="blue darken-3">
|
||||
<div class="container">
|
||||
<div class="nav-wrapper">
|
||||
<ul class="left">
|
||||
<li><a href="{{ url_for('admin.admin_dashboard') }}">Dashboard</a></li>
|
||||
</ul>
|
||||
<a href="#" class="center brand-logo">{{ config.ctf_name }} Admin</a>
|
||||
<ul class="right">
|
||||
<li><a href="{{ url_for('admin.admin_logout') }}">Logout ({{ session.admin }})</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="container" style="margin-top: 20px;">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
{% if config.cdn %}
|
||||
<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>
|
||||
{% else %}
|
||||
<script src="{{ url_for('static', filename='jquery.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='materialize.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='timeago.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='vis.min.js') }}"></script>
|
||||
{% endif %}
|
||||
<script src="{{ url_for('static', filename='api.js') }}"></script>
|
||||
<script>$("abbr.time").timeago();</script>
|
||||
<script id="toasts" type="text/javascript">
|
||||
{% for message in get_flashed_messages() %}
|
||||
Materialize.toast({{ message | tojson }}, 4000);
|
||||
{% endfor %}
|
||||
</script>
|
||||
{% block postscript %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,21 @@
|
|||
{% extends "admin/base.html" %}
|
||||
{% block content %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Team</th><th>Affiliation</th><th>Eligible</th><th>Last solve</th><th>Score</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for team in teams %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('admin.admin_show_team', tid=team.id) }}">{{ team.name }}</a>
|
||||
</td>
|
||||
<td>{{ team.affiliation }}</td>
|
||||
<td>{{ "Eligible" if team.eligible else "Ineligible" }}</td>
|
||||
<td><abbr class="time" title="{{ lastsolvedata[team.id] }}">{{ lastsolvedata[team.id] }}</abbr></td>
|
||||
<td>{{ scoredata[team.id] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
|
@ -0,0 +1,17 @@
|
|||
{% extends "admin/base.html" %}
|
||||
{% block title %}Login{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Login</h2>
|
||||
<form method="POST">
|
||||
<div class="input-field">
|
||||
<input id="username" name="username" type="text" />
|
||||
<label for="username">Username</label>
|
||||
</div>
|
||||
<div class="input-field">
|
||||
<input id="password" name="password" type="password" />
|
||||
<label for="password">Password</label>
|
||||
</div>
|
||||
<input name="_csrf_token" type="hidden" value="{{ csrf_token() }}" />
|
||||
<button class="btn waves-effect waves-light" type="submit">Login</button>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
|||
{% extends "admin/base.html" %}
|
||||
{% block content %}
|
||||
<h2>{{ team.name }}</h2>
|
||||
<p>This team is <strong>{{ "eligible" if team.eligible else "not eligible" }}</strong>.</p>
|
||||
<p>This team's affiliation is <strong>{{ team.affiliation }}</strong></p>
|
||||
<h3>Email</h3>
|
||||
<p>This team's email is <strong>{{ team.email }} ({{ "confirmed" if team.email_confirmed else "unconfirmed" }})</strong>.</p>
|
||||
{% if not team.email_confirmed or 1 %}
|
||||
<p>This team's confirmation key is <code>{{ team.email_confirmation_key }}</code>.
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -34,7 +34,8 @@ function toggle() {
|
|||
<span class="right">{{ challenge.category }} <b>·</b> {{ challenge.points }} pt</span>
|
||||
</div>
|
||||
<div class="collapsible-body">
|
||||
<p>{{ challenge.description }}</p>
|
||||
<p>{{ challenge.description | safe }}<br />
|
||||
The challenge author is {{ challenge.author }}; talk to them for hints if you're stuck.</p>
|
||||
{% if challenge in solved %}
|
||||
<p>You've solved this challenge!</p>
|
||||
{% else %}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
import bcrypt
|
||||
def create_password(pw):
|
||||
return bcrypt.hashpw(pw, bcrypt.gensalt())
|
||||
|
||||
def verify_password(user, pw):
|
||||
return bcrypt.hashpw(pw.encode(), user.password.encode()) == user.password.encode()
|
|
@ -5,6 +5,23 @@ from .cache import get_complex, set_complex
|
|||
|
||||
import config
|
||||
|
||||
def get_all_scores(teams, solves, adjustments):
|
||||
scores = {team.id: 0 for team in teams}
|
||||
for solve in solves:
|
||||
scores[solve.team_id] += solve.challenge.points
|
||||
|
||||
for adjustment in adjustments:
|
||||
scores[adjustment.team_id] += adjustment.value
|
||||
|
||||
return scores
|
||||
|
||||
def get_last_solves(teams, solves):
|
||||
last = {team.id: datetime(1970, 1, 1) for team in teams}
|
||||
for solve in solves:
|
||||
if solve.time > last[solve.team_id]:
|
||||
last[solve.team_id] = solve.time
|
||||
return last
|
||||
|
||||
def calculate_scores():
|
||||
solves = ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge)
|
||||
adjustments = ScoreAdjustment.select()
|
||||
|
|
Loading…
Reference in New Issue