Optionally allow unregistered users to view challenges

Add a Config entry `view_challenges_unregistered` to indicate whether
unregistered users can view challenges. Add the setting to the admin config
page.

Add can_view_challenges() to utils to test if a user is either authed, or the
configuration allow unauthenticated users to view the challenges.

Return a HTTP 401 Unauthorized error when the /chals/solves API can't provide
results for an unauthenticated user. This is needed because the client side
code in `chalboard.js` doesn't know if it's logged in or not and requests this
anyway. (And AJAX doesn't handle redirects very well.) Alternately the client
could actually know if they're logged in and not make needless API calls.

When an unregistered user attempts to submit a flag, it will also fail. The
user will be redirected to a login page.
This commit is contained in:
Blake Burkhart
2015-01-07 22:11:31 -06:00
parent c5c3126bb4
commit 2972cf506d
5 changed files with 38 additions and 11 deletions

View File

@@ -52,7 +52,12 @@ def init_admin(app):
start = None start = None
end = None end = None
print repr(start), repr(end) try:
view_challenges_unregistered = bool(request.form.get('view_challenges_unregistered', None))
except (ValueError, TypeError):
view_challenges_unregistered = None
print repr(start), repr(end), repr(view_challenges_unregistered)
db_start = Config.query.filter_by(key='start').first() db_start = Config.query.filter_by(key='start').first()
db_start.value = start db_start.value = start
@@ -60,14 +65,19 @@ def init_admin(app):
db_end = Config.query.filter_by(key='end').first() db_end = Config.query.filter_by(key='end').first()
db_end.value = end db_end.value = end
db_view_challenges_unregistered = Config.query.filter_by(key='view_challenges_unregistered').first()
db_view_challenges_unregistered.value = view_challenges_unregistered
db.session.add(db_start) db.session.add(db_start)
db.session.add(db_end) db.session.add(db_end)
db.session.add(db_view_challenges_unregistered)
db.session.commit() db.session.commit()
return redirect('/admin/config') return redirect('/admin/config')
start = Config.query.filter_by(key="start").first().value start = Config.query.filter_by(key="start").first().value
end = Config.query.filter_by(key="end").first().value end = Config.query.filter_by(key="end").first().value
return render_template('admin/config.html', start=start, end=end) view_challenges_unregistered = (Config.query.filter_by(key='view_challenges_unregistered').first().value == '1')
return render_template('admin/config.html', start=start, end=end, view_challenges_unregistered=view_challenges_unregistered)
@app.route('/admin/pages', defaults={'route': None}, methods=['GET', 'POST']) @app.route('/admin/pages', defaults={'route': None}, methods=['GET', 'POST'])
@app.route('/admin/pages/<route>', methods=['GET', 'POST']) @app.route('/admin/pages/<route>', methods=['GET', 'POST'])

View File

@@ -1,6 +1,6 @@
from flask import current_app as app, render_template, request, redirect, abort, jsonify, json as json_mod, url_for, session from flask import current_app as app, render_template, request, redirect, abort, jsonify, json as json_mod, url_for, session
from CTFd.utils import ctftime, authed, unix_time, get_kpm from CTFd.utils import ctftime, authed, unix_time, get_kpm, can_view_challenges
from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys
import time import time
@@ -12,7 +12,7 @@ def init_challenges(app):
def challenges(): def challenges():
if not ctftime(): if not ctftime():
return redirect('/') return redirect('/')
if authed(): if can_view_challenges():
return render_template('chals.html') return render_template('chals.html')
else: else:
return redirect(url_for('login', next="challenges")) return redirect(url_for('login', next="challenges"))
@@ -21,7 +21,7 @@ def init_challenges(app):
def chals(): def chals():
if not ctftime(): if not ctftime():
return redirect('/') return redirect('/')
if authed(): if can_view_challenges():
chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all() chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all()
json = {'game':[]} json = {'game':[]}
@@ -37,7 +37,7 @@ def init_challenges(app):
@app.route('/chals/solves') @app.route('/chals/solves')
def chals_per_solves(): def chals_per_solves():
if authed(): if can_view_challenges():
solves = Solves.query.add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all() solves = Solves.query.add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all()
json = {} json = {}
for chal, count in solves: for chal, count in solves:
@@ -52,7 +52,7 @@ def init_challenges(app):
if authed(): if authed():
solves = Solves.query.filter_by(teamid=session['id']).all() solves = Solves.query.filter_by(teamid=session['id']).all()
else: else:
return redirect('/login') abort(401)
else: else:
solves = Solves.query.filter_by(teamid=teamid).all() solves = Solves.query.filter_by(teamid=teamid).all()
db.session.close() db.session.close()

View File

@@ -83,6 +83,9 @@ def ctftime():
return False return False
def can_view_challenges():
return authed() or (Config.query.filter_by(key="view_challenges_unregistered").first().value == '1');
def unix_time(dt): def unix_time(dt):
epoch = datetime.datetime.utcfromtimestamp(0) epoch = datetime.datetime.utcfromtimestamp(0)
delta = dt - epoch delta = dt - epoch

View File

@@ -63,12 +63,16 @@ def init_views(app):
start = Config('start', None) start = Config('start', None)
end = Config('end', None) end = Config('end', None)
## Challenges cannot be viewed by unregistered users
view_challenges_unregistered = Config('view_challenges_unregistered', None)
setup = Config('setup', True) setup = Config('setup', True)
db.session.add(admin) db.session.add(admin)
db.session.add(page) db.session.add(page)
db.session.add(start) db.session.add(start)
db.session.add(end) db.session.add(end)
db.session.add(view_challenges_unregistered)
db.session.add(setup) db.session.add(setup)
db.session.commit() db.session.commit()
app.setup = False app.setup = False

View File

@@ -6,11 +6,21 @@
<h1>Config</h1> <h1>Config</h1>
<form method="POST"> <form method="POST">
<input name='nonce' type='hidden' value="{{ nonce }}"> <input name='nonce' type='hidden' value="{{ nonce }}">
<strong>Start Date:</strong>
<input name='start' type='text' placeholder="Start Date (UTC timestamp)" {% if start is defined and start != None %}value="{{ start }}"{% endif %}>
<strong>End Date:</strong> <div class="row">
<input name='end' type='text' placeholder="End Date (UTC timestamp)" {% if end is defined and end != None %}value="{{ end }}"{% endif %}> <label for="start">Start Date:</label>
<input id='start' name='start' type='text' placeholder="Start Date (UTC timestamp)" {% if start is defined and start != None %}value="{{ start }}"{% endif %}>
</div>
<div class="row">
<label for="end">End Date:</label>
<input id='end' name='end' type='text' placeholder="End Date (UTC timestamp)" {% if end is defined and end != None %}value="{{ end }}"{% endif %}>
</div>
<div class="row">
<input id="view_challenges_unregistered" name="view_challenges_unregistered" type="checkbox" {% if view_challenges_unregistered %}checked{% endif %}>
<label for="view_challenges_unregistered">Unregistered users can view challenges</label>
</div>
<button class="radius" type='submit'>Submit</button> <button class="radius" type='submit'>Submit</button>
</form> </form>