From 3f596d87b13d2417a37efe9fd32898e80f50bada Mon Sep 17 00:00:00 2001 From: Kevin Chung Date: Thu, 20 Oct 2016 23:31:24 -0400 Subject: [PATCH] Fixing a variety of glitches Fixing statistics reporting banned users as well Fixing url encoding and some error messages Ordering solves in the admin graphs Removing banned users (includes admins) from public teams page Fixing email regex in profile --- CTFd/admin.py | 26 ++++++++++--------- CTFd/auth.py | 16 +++++++----- CTFd/templates/original/admin/graphs.html | 24 +++++++++++++---- CTFd/templates/original/admin/statistics.html | 4 +-- CTFd/templates/original/profile.html | 2 +- CTFd/views.py | 10 +++---- 6 files changed, 50 insertions(+), 32 deletions(-) diff --git a/CTFd/admin.py b/CTFd/admin.py index b71562a3..ea4e860c 100644 --- a/CTFd/admin.py +++ b/CTFd/admin.py @@ -730,17 +730,18 @@ def admin_stats(): wrong_count = db.session.query(db.func.count(WrongKeys.id)).first()[0] solve_count = db.session.query(db.func.count(Solves.id)).first()[0] challenge_count = db.session.query(db.func.count(Challenges.id)).first()[0] - - solves_raw = db.func.count(Solves.chalid).label('solves_raw') - solves_sub = db.session.query(Solves.chalid, solves_raw) \ + + solves_sub = db.session.query(Solves.chalid, db.func.count(Solves.chalid).label('solves_cnt')) \ + .join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False) \ .group_by(Solves.chalid).subquery() - solves_cnt = coalesce(solves_sub.columns.solves_raw, 0).label('solves_cnt') - most_solved_chal = Challenges.query.add_columns(solves_cnt) \ - .outerjoin(solves_sub, solves_sub.columns.chalid == Challenges.id) \ - .order_by(solves_cnt.desc()).first() - least_solved_chal = Challenges.query.add_columns(solves_cnt) \ - .outerjoin(solves_sub, solves_sub.columns.chalid == Challenges.id) \ - .order_by(solves_cnt.asc()).first() + solves = db.session.query(solves_sub.columns.chalid, solves_sub.columns.solves_cnt, Challenges.name) \ + .join(Challenges, solves_sub.columns.chalid == Challenges.id).all() + solve_data = {} + for chal, count, name in solves: + solve_data[name] = count + + most_solved = max(solve_data, key=solve_data.get) + least_solved = min(solve_data, key=solve_data.get) db.session.expunge_all() db.session.commit() @@ -750,8 +751,9 @@ def admin_stats(): wrong_count=wrong_count, solve_count=solve_count, challenge_count=challenge_count, - most_solved=most_solved_chal, - least_solved=least_solved_chal + solve_data=solve_data, + most_solved=most_solved, + least_solved=least_solved ) diff --git a/CTFd/auth.py b/CTFd/auth.py index bbc14db4..f15b30b7 100644 --- a/CTFd/auth.py +++ b/CTFd/auth.py @@ -23,11 +23,11 @@ def confirm_user(data=None): if data and request.method == "GET": ## User is confirming email account try: s = Signer(app.config['SECRET_KEY']) - email = s.unsign(urllib.unquote(data.decode('base64'))) + email = s.unsign(urllib.unquote_plus(data.decode('base64'))) except BadSignature: return render_template('confirm.html', errors=['Your confirmation link seems wrong']) except: - return render_template('reset_password.html', errors=['Your link appears broken, please try again.']) + return render_template('confirm.html', errors=['Your link appears broken, please try again.']) team = Teams.query.filter_by(email=email).first() team.verified = True db.session.commit() @@ -57,9 +57,11 @@ def reset_password(data=None): if data is not None and request.method == "POST": try: s = TimedSerializer(app.config['SECRET_KEY']) - name = s.loads(urllib.unquote(data.decode('base64')), max_age=1800) + name = s.loads(urllib.unquote_plus(data.decode('base64')), max_age=1800) except BadTimeSignature: return render_template('reset_password.html', errors=['Your link has expired']) + except: + return render_template('reset_password.html', errors=['Your link appears broken, please try again.']) team = Teams.query.filter_by(name=name).first() team.password = bcrypt_sha256.encrypt(request.form['password'].strip()) db.session.commit() @@ -70,7 +72,7 @@ def reset_password(data=None): email = request.form['email'].strip() team = Teams.query.filter_by(email=email).first() if not team: - return render_template('reset_password.html', errors=['Check your email']) + return render_template('reset_password.html', errors=['If that account exists you will receive an email, please check your inbox']) s = TimedSerializer(app.config['SECRET_KEY']) token = s.dumps(team.name) text = """ @@ -78,11 +80,11 @@ Did you initiate a password reset? {0}/{1} -""".format(url_for('auth.reset_password', _external=True), token.encode('base64')) +""".format(url_for('auth.reset_password', _external=True), urllib.quote_plus(token.encode('base64'))) sendmail(email, text) - return render_template('reset_password.html', errors=['Check your email']) + return render_template('reset_password.html', errors=['If that account exists you will receive an email, please check your inbox']) return render_template('reset_password.html') @@ -175,7 +177,7 @@ def login(): return redirect(request.args.get('next')) return redirect(url_for('challenges.challenges_view')) else: # This user exists but the password is wrong - errors.append("That account doesn't seem to exist") + errors.append("Your username or password is incorrect") db.session.close() return render_template('login.html', errors=errors) else: # This user just doesn't exist diff --git a/CTFd/templates/original/admin/graphs.html b/CTFd/templates/original/admin/graphs.html index 94452430..f71708c7 100644 --- a/CTFd/templates/original/admin/graphs.html +++ b/CTFd/templates/original/admin/graphs.html @@ -59,15 +59,28 @@ } function solves_graph() { - $.get('{{ request.script_root }}/admin/graphs/solves', function(data){ + $.get('{{ request.script_root }}/admin/graphs/solves', function (data) { var solves = $.parseJSON(JSON.stringify(data)); var chals = []; var counts = []; var colors = []; + var annotations = []; var i = 1; - $.each(solves, function(key, value){ - chals.push(key); - counts.push(value); + solves_order = Object.keys(solves).sort(function (a, b) { + return solves[b] - solves[a] + }); + $.each(solves_order, function (key, value) { + chals.push(value); + counts.push(solves[value]); + var result = { + x: value, + y: solves[value], + text: solves[value], + xanchor: 'center', + yanchor: 'bottom', + showarrow: false + }; + annotations.push(result); colors.push(colorhash(i++)); }); @@ -80,7 +93,8 @@ }]; var layout = { - title: 'Score Counts' + title: 'Score Counts', + annotations: annotations }; Plotly.newPlot('solves-graph', data, layout); diff --git a/CTFd/templates/original/admin/statistics.html b/CTFd/templates/original/admin/statistics.html index 7745fb75..ffcc666e 100644 --- a/CTFd/templates/original/admin/statistics.html +++ b/CTFd/templates/original/admin/statistics.html @@ -11,10 +11,10 @@

{{ solve_count }} right keys submitted

{{ challenge_count }} challenges

{% if most_solved %} -

Most solved: {{ most_solved[0].name }} with {{ most_solved[1] }} solves

+

Most solved: {{ most_solved }} with {{ solve_data[most_solved] }} solves

{% endif %} {% if least_solved %} -

Least solved: {{ least_solved[0].name }} with {{ least_solved[1] }} solves

+

Least solved: {{ least_solved }} with {{ solve_data[least_solved] }} solves

{% endif %} diff --git a/CTFd/templates/original/profile.html b/CTFd/templates/original/profile.html index 9fd28a51..990737a2 100644 --- a/CTFd/templates/original/profile.html +++ b/CTFd/templates/original/profile.html @@ -45,7 +45,7 @@ Please check your email to confirm your email address.

- To have the confirmation email reset please click + To have the confirmation email resent please click here. diff --git a/CTFd/views.py b/CTFd/views.py index da494f6d..373cc7d9 100644 --- a/CTFd/views.py +++ b/CTFd/views.py @@ -126,11 +126,11 @@ def teams(page): page_end = results_per_page * ( page - 1 ) + results_per_page if get_config('verify_emails'): - count = Teams.query.filter_by(verified=True).count() - teams = Teams.query.filter_by(verified=True).slice(page_start, page_end).all() + count = Teams.query.filter_by(verified=True, banned=False).count() + teams = Teams.query.filter_by(verified=True, banned=False).slice(page_start, page_end).all() else: - count = Teams.query.count() - teams = Teams.query.slice(page_start, page_end).all() + count = Teams.query.filter_by(banned=False).count() + teams = Teams.query.filter_by(banned=False).slice(page_start, page_end).all() pages = int(count / results_per_page) + (count % results_per_page > 0) return render_template('teams.html', teams=teams, team_pages=pages, curr_page=page) @@ -174,7 +174,7 @@ def profile(): name_len = len(request.form['name']) == 0 emails = Teams.query.filter_by(email=email).first() - valid_email = re.match("[^@]+@[^@]+\.[^@]+", email) + valid_email = re.match(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", email) if ('password' in request.form.keys() and not len(request.form['password']) == 0) and \ (not bcrypt_sha256.verify(request.form.get('confirm').strip(), user.password)):