Merge branch 'master' of github.com:IceCTF/new-platform

master
Glitch 2016-07-12 16:41:23 +00:00
commit 4c5f4cabf4
10 changed files with 304 additions and 139 deletions

126
app.py
View File

@ -5,7 +5,7 @@ from database import User, Team, TeamAccess, Challenge, ChallengeSolve, Challeng
from datetime import datetime
from peewee import fn
from utils import decorators, flag, cache, misc, captcha, email
from utils import decorators, flag, cache, misc, captcha, email, select
import utils.scoreboard
import config
@ -28,11 +28,12 @@ def make_info_available():
g.team = g.user.team
g.team_restricts = g.team.restricts.split(",")
except User.DoesNotExist:
session.pop("user_id")
return render_template("login.html")
@app.context_processor
def scoreboard_variables():
var = dict(config=config)
var = dict(config=config, select=select)
if "user_id" in session:
var["logged_in"] = True
var["user"] = g.user
@ -108,10 +109,10 @@ def register():
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")
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()
@ -141,6 +142,10 @@ def register():
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")
@ -149,6 +154,18 @@ def register():
flash("You're lying")
return render_template("register.html")
if 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")
confirmation_key = misc.generate_confirmation_key()
team=None
@ -177,14 +194,15 @@ def register():
email_confirmation_key=confirmation_key,
team=team)
user.setPassword(password)
user.save()
print(confirmation_key)
# print(confirmation_key)
# email.send_confirmation_email(user_email, confirmation_key)
email.send_confirmation_email(user_email, confirmation_key)
session["user_id"] = user.id
flash("Registration finished")
return redirect(url_for('team_dashboard'))
return redirect(url_for('user_dashboard'))
@app.route('/logout/')
def logout():
@ -220,45 +238,73 @@ def user_dashboard():
flash("You're changing your information too fast!")
return redirect(url_for('user_dashboard'))
team_name = request.form["team_name"].strip()
team_email = request.form["team_email"].strip()
affiliation = request.form["affiliation"].strip()
team_elig = "team_eligibility" in request.form
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()
if len(team_name) > 50 or not team_name:
flash("You must have a team name!")
tshirt_size = request.form["tshirt_size"].strip()
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 (team_email and "." in team_email and "@" in team_email):
flash("You must have a valid team email!")
if not email.is_valid_email(user_email):
flash("You're lying")
return redirect(url_for('user_dashboard'))
if not affiliation or len(affiliation) > 100:
affiliation = "No affiliation"
if not tshirt_size in select.TShirts:
flash("Invalid T-shirt size")
return redirect(url_for('user_dashboard'))
email_changed = (team_email != g.team.email)
if not background in select.BackgroundKeys:
flash("Invalid Background")
return redirect(url_for('user_dashboard'))
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
if not country in select.CountryKeys:
flash("Invalid Background")
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 email_changed:
if not email.is_valid_email(team_email):
flash("You're lying")
if password != "":
if password != confirm_password:
flash("Password does not match confirmation")
return redirect(url_for('user_dashboard'))
g.user.setPassword(password)
g.team.email_confirmation_key = misc.generate_confirmation_key()
g.team.email_confirmed = False
if email_changed:
g.user.email_confirmation_key = misc.generate_confirmation_key()
g.user.email_confirmed = False
email.send_confirmation_email(team_email, g.team.email_confirmation_key, g.team.key)
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.team.save()
g.user.save()
return redirect(url_for('user_dashboard'))
@ -276,7 +322,7 @@ def team_dashboard():
return redirect(url_for('team_dashboard'))
team_name = request.form["team_name"].strip()
affiliation = request.form["affiliation"].strip()
affiliation = request.form["team_affiliation"].strip()
if len(team_name) > 50 or not team_name:
flash("You must have a team name!")
@ -285,7 +331,13 @@ def team_dashboard():
if not affiliation or len(affiliation) > 100:
affiliation = "No affiliation"
email_changed = (team_email != g.team.email)
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
@ -433,13 +485,15 @@ def debug_app():
@app.before_request
def before_request():
g.connected = True
db.connect()
g.redis = redis.StrictRedis()
@app.teardown_request
def teardown_request(exc):
db.close()
g.redis.connection_pool.disconnect()
if getattr(g, 'connected', False):
db.close()
g.redis.connection_pool.disconnect()
# CSRF things

View File

@ -149,15 +149,15 @@ def main():
subparser_problems = parser_problems.add_subparsers(help='Select one of the following actions')
parser_problems_scan = subparser_problems.add_parser('scan', help='Scan a path for problems')
parser_problems_scan.add_argument("path", nargs=1, help="Directory for the path")
parser_problems_scan.add_argument("path", help="Directory for the path")
parser_problems_scan.set_defaults(func=scan_challenges)
parser_problems_generate = subparser_problems.add_parser('generate', help='Generate some dummy problems')
parser_problems_generate.add_argument("challenge_count", nargs=1, type=int, help="number of problems to generate")
parser_problems_generate.add_argument("challenge_count", type=int, help="number of problems to generate")
parser_problems_generate.set_defaults(func=gen_challenges)
parser_problems_add = subparser_problems.add_parser('add', help='add a problem')
parser_problems_add.add_argument("file", nargs=1, help="file to use")
parser_problems_add.add_argument("file", help="file to use")
parser_problems_add.set_defaults(func=add_challenge)
parser_problems_list = subparser_problems.add_parser('list', help='List problems in the database')
@ -187,7 +187,7 @@ def main():
subparser_users = parser_user.add_subparsers(help="Select one of the following actions")
parser_users_generate = subparser_users.add_parser('generate', help='Generate some dummy teams')
parser_users_generate.add_argument("team_count", nargs=1, type=int, help="number of teams to generate")
parser_users_generate.add_argument("team_count", type=int, help="number of teams to generate")
parser_users_generate.set_defaults(func=gen_team)
parser_users_admin = subparser_users.add_parser('add-admin', help='Create a new administrator')

View File

@ -30,6 +30,7 @@ class User(BaseModel):
email_confirmed = BooleanField(default=False)
email_confirmation_key = CharField()
password = CharField(null = True)
background = CharField()
country = CharField()
tshirt_size = CharField(null = True)
gender = CharField(null = True)
@ -45,7 +46,7 @@ class User(BaseModel):
return bcrypt.checkpw(pw.encode("utf-8"), self.password)
def eligible(self):
return self.country == "Iceland"
return self.country == "ISL"
class TeamAccess(BaseModel):
team = ForeignKeyField(Team, related_name='accesses')

View File

@ -5,3 +5,4 @@ bcrypt
redis
pyyaml
oath
pycountry

View File

@ -75,17 +75,18 @@
</a></li>
<div class="divider" /></div>
{% if logged_in %}
<li><a href="{{ url_for('logout') }}">
<div class="side-icon"><i class="material-icons red-text">block</i></div>
<div class="side-text">Logout</div>
<li><a href="{{ url_for('team_dashboard') }}">
<div class="side-icon"><i class="material-icons teal-text">track_changes</i></div>
<div class="side-text">My Team</div>
</a></li>
<li><a href="{{ url_for('user_dashboard') }}">
<div class="side-icon"><i class="material-icons teal-text">account_circle</i></div>
<div class="side-text">{{ user.username }}</div>
</a></li>
<li><a href="{{ url_for('team_dashboard') }}">
<div class="side-icon"><i class="material-icons teal-text">account_circle</i></div>
<div class="side-text">{{ team.name }}</div>
<li><a href="{{ url_for('logout') }}">
<div class="side-icon"><i class="material-icons red-text">block</i></div>
<div class="side-text">Logout</div>
</a></li>
{% endif %}
{% if not logged_in %}

View File

@ -39,20 +39,13 @@
<div class="row no-bot">
<div class="input-field col s6">
<select required id="background" name="background">
<option val="elementary">Elementary School Student</option>
<option val="high">High School Student</option>
<option val="university" selected>University Student</option>
<option val="teacher">Teacher</option>
<option val="professional">Security Professional</option>
<option val="hobbyist">CTF Hobbyist(non-student)</option>
<option val="other">Other</option>
{{select.genoption(select.Backgrounds, selected="university")|safe}}
</select>
<label for="background">Background</label>
</div>
<div class="input-field col s6">
<select required name="country" id="country">
<option value="" disabled selected>Choose your option</option>
<option value="Iceland">Iceland</option>
{{select.genoption(select.Countries, header='<option value="" disabled selected>Select country</option>')|safe}}
</select>
<label>Country</label>
</div>
@ -60,19 +53,14 @@
<div class="row no-bot">
<div class="input-field col s6">
<select required name="tshirt_size" id="tshirt_size">
<option value="" disabled selected>Choose your option</option>
<option value="S">S</option>
<option value="M">M</option>
<option value="L">L</option>
<option value="XL">XL</option>
<option value="XXL">XXL</option>
{{select.genoption(select.TShirts, header='<option value="" disabled selected>Select country</option>')|safe}}
</select>
<label>Shirt Size</label>
</div>
<div class="input-field col s6">
<input name="gender" type="radio" id="male" />
<input name="gender" type="radio" id="male" value="M" />
<label for="male">Male</label>
<input name="gender" type="radio" id="female" />
<input name="gender" type="radio" id="female" value="F" />
<label for="female" style="margin-left: 1rem;">Female</label>
</div>
</div>

View File

@ -31,75 +31,87 @@
{% endblock %}
{% block content %}
<h2>{{ team.name }}</h2>
<h4>Team information</h4>
<p>Your score is currently {{ team_score }}. <a href="{{ url_for('challenges') }}">Go solve more challenges!</a></p>
<p>Your team email is <code>{{ team.email }}</code>, and you are affiliated with
{{ team.affiliation }}.</p>
<p>Your team is currently marked {{ "eligible" if team.eligible else "ineligible" }}.</p>
<section>
<div class="row">
<div class="col s12">
<div class="card blue darken-1">
<div class="card-content white-text">
<span class="card-title">Team key: <code>{{ team.key }}</code></span>
<p>Share this with your teammates, and keep it in a safe place.</p>
</div>
</div>
</div>
</div>
</section>
<section>
<h4>Edit information</h4>
<form method="POST">
<h4>Team information</h4>
<p>Your score is currently {{ team_score }}. <a href="{{ url_for('challenges') }}">Go solve more challenges!</a></p>
<p>Your team email is <code>{{ team.email }}</code>, and you are affiliated with
{{ team.affiliation }}.</p>
<p>Your team is currently marked {{ "eligible" if team.eligible() else "ineligible" }}.</p>
</section>
<section>
<h4>Edit information</h4>
<form method="POST">
<div class="input-field">
<label for="team-name">Team Name</label>
<input required maxlength="50" id="team-name" name="team_name" type="text" value="{{ team.name }}" />
<label for="team-name">Team Name</label>
<input required maxlength="50" id="team-name" name="team_name" type="text" value="{{ team.name }}" />
</div>
<div class="input-field">
<label for="affiliation">Affiliation</label>
<input required maxlength="100" id="affiliation" name="affiliation" type="text" value="{{ team.affiliation }}" />
<label for="affiliation">Affiliation</label>
<input required maxlength="100" id="affiliation" name="team_affiliation" type="text" value="{{ team.affiliation }}" />
</div>
<input name="_csrf_token" type="hidden" value="{{ csrf_token() }}" />
<br /><br />
<button class="btn waves-effect waves-light" type="submit">Update team</button>
</form>
</form>
</section>
<section>
<h4>Score calculation</h4>
{% if team_solves.count() %}
<h5>Solved problems</h5>
<div class="row">
<div class="col s10 offset-s1">
<h4>Score calculation</h4>
{% if team_solves.count() %}
<h5>Solved problems</h5>
<div class="row">
<div class="col s10 offset-s1">
<table>
<thead>
<tr><th>Name</th><th>Category</th><th>Time</th><th>Value</th></tr>
</thead>
<tbody>
{% for solve in team_solves %}
<tr>
<td>{{ solve.challenge.name }}</td>
<td>{{ solve.challenge.category }}</td>
<td><abbr class="time" title="{{ solve.time }}">{{ solve.time }}</abbr></td>
<td>{{ solve.challenge.points }}</td>
{% endfor %}
</tbody>
<thead>
<tr><th>Name</th><th>Category</th><th>Time</th><th>Value</th></tr>
</thead>
<tbody>
{% for solve in team_solves %}
<tr>
<td>{{ solve.challenge.name }}</td>
<td>{{ solve.challenge.category }}</td>
<td><abbr class="time" title="{{ solve.time }}">{{ solve.time }}</abbr></td>
<td>{{ solve.challenge.points }}</td>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% else %}
<p>No problems have been solved.</p>
{% endif %}
{% if team_adjustments.count() %}
<h5>Score adjustments</h5>
<div class="row">
<div class="col s10 offset-s1">
{% else %}
<p>No problems have been solved.</p>
{% endif %}
{% if team_adjustments.count() %}
<h5>Score adjustments</h5>
<div class="row">
<div class="col s10 offset-s1">
<table>
<thead>
<tr><th>Reason</th><th>Value</th></tr>
</thead>
<tbody>
{% for adj in team_adjustments %}
<tr>
<td>{{ adj.reason }}</td>
<td>{{ adj.value }}</td>
</tr>
{% endfor %}
</tbody>
<thead>
<tr><th>Reason</th><th>Value</th></tr>
</thead>
<tbody>
{% for adj in team_adjustments %}
<tr>
<td>{{ adj.reason }}</td>
<td>{{ adj.value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% else %}
<p>No score adjustments have been made.</p>
{% endif %}
{% else %}
<p>No score adjustments have been made.</p>
{% endif %}
</section>
{% endblock %}

View File

@ -32,27 +32,94 @@
{% block content %}
<h2>{{ user.username }}</h2>
{% if not user.email_confirmed %}
<div class="row">
<div class="col s12">
<div class="card red darken-2">
<div class="card-content white-text">
<span class="card-title">Email unconfirmed</span>
<p>It looks like you haven't confirmed your email yet. Check the email you used for registration;
the system should have sent you an email confirmation key. Paste it below:</p>
<form action="{{ url_for('confirm_email') }}" method="POST">
<div class="input-field">
<label for="confirmation-key" class="white-text">Confirmation key</label>
<input required id="confirmation-key" name="confirmation_key" type="text" />
</div>
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}" />
<button class="btn waves-effect waves-light" type="submit">Confirm email</button>
</form>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<div class="card red darken-2">
<div class="card-content white-text">
<span class="card-title">Email unconfirmed</span>
<p>It looks like you haven't confirmed your email yet. Check the email you used for registration;
the system should have sent you an email confirmation key. Paste it below:</p>
<form action="{{ url_for('confirm_email') }}" method="POST">
<div class="input-field">
<label for="confirmation-key" class="white-text">Confirmation key</label>
<input required id="confirmation-key" name="confirmation_key" type="text" />
</div>
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}" />
<button class="btn waves-effect waves-light" type="submit">Confirm email</button>
</form>
</div>
</div>
</div>
</div>
{% endif %}
<section>
<h4>Edit information</h4>
<form method="POST">
<div class="row no-bot">
<div class="input-field col s12">
<input class="validate" required maxlength="50" id="username" name="username" type="text" value="{{ user.username }}" />
<label for="team-name">Username</label>
</div>
</div>
<div class="row no-bot">
<div class="input-field col s12">
<input class="validate" required id="email" name="email" type="email" value="{{ user.email }}" />
<label for="email">Email</label>
</div>
</div>
<div class="row no-bot">
<div class="input-field col s6">
<input class="validate" id="password" name="password" type="password" />
<label for="password">Password</label>
</div>
<div class="input-field col s6">
<input class="validate" id="confirm_password" name="confirm_password" type="password" />
<label for="confirm_password">Confirm Password</label>
</div>
</div>
<div class="row no-bot">
<div class="input-field col s6">
<select required id="background" name="background">
{{select.genoption(select.Backgrounds, selected=user.background)|safe}}
</select>
<label for="background">Background</label>
</div>
<div class="input-field col s6">
<select required name="country" id="country">
{{select.genoption(select.Countries, selected=user.country)|safe}}
</select>
<label>Country</label>
</div>
</div>
<div class="row no-bot">
<div class="input-field col s6">
<select required name="tshirt_size" id="tshirt_size">
{{select.genoption(select.TShirts, selected=user.tshirt_size)|safe}}
</select>
<label>Shirt Size</label>
</div>
<div class="input-field col s6">
{% if user.gender == 'M' %}
<input name="gender" type="radio" id="male" value="M" checked="checked" />
<label for="male">Male</label>
<input name="gender" type="radio" id="female" value="F" />
<label for="female" style="margin-left: 1rem;">Female</label>
{% elif user.gender == 'F' %}
<input name="gender" type="radio" id="male" value="M" />
<label for="male">Male</label>
<input name="gender" type="radio" id="female" value="F" checked="checked" />
<label for="female" style="margin-left: 1rem;">Female</label>
{% endif %}
</div>
</div>
<div class="row no-bot">
<div class="col s12 m6">
<button class="btn waves-effect waves-light right register-submit" type="submit">Update information</button>
</div>
</div>
<input name="_csrf_token" type="hidden" value="{{ csrf_token() }}" />
</form>
</section>
<div id="firstlogin" class="modal">
<div class="modal-content">
<h4>Welcome to {{ config.ctf_name }}!</h4>

View File

@ -37,7 +37,7 @@ def calculate_scores():
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}
# 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):
solves = list(ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge).order_by(ChallengeSolve.time))
@ -55,4 +55,3 @@ def calculate_graph(scoredata):
team_data.append((str(datetime.now()), score))
graph_data.append((name, team_data))
return graph_data

42
utils/select.py Normal file
View File

@ -0,0 +1,42 @@
import pycountry
TShirts = [
"XS",
"S",
"M",
"L",
"XL",
"XXL"
]
Backgrounds = [
("elementary", "Elementary School Student"),
("high", "High School Student"),
("university", "University Student"),
("teacher", "Teacher"),
("professional", "Security Professional"),
("hobbyist", "CTF Hobbyist(non-student)"),
("other", "Other")
]
BackgroundKeys = [x[0] for x in Backgrounds]
Countries = [(country.alpha3, country.name) for country in pycountry.countries]
Countries = (sorted(Countries, key=lambda x: "0" if x[1] == "Iceland" else x[1]))
CountryKeys = [x[0] for x in Countries]
def genoption(arr, selected=None, header=None):
s = ""
if header is not None:
s += header
for val in arr:
if isinstance(val, tuple):
val, name = val
else:
name = val
if selected is not None and val == selected:
s += ('<option value="%s" selected>%s</option>' % (val, name))
else:
s += ('<option value="%s">%s</option>' % (val, name))
return s