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 @@