Auth Improvments (#746)

* Create generic get_errors, get_infos; add MLC OAuth settings in config
* Use new get_errors function
selenium-screenshot-testing
Kevin Chung 2018-11-22 18:33:08 -05:00 committed by GitHub
parent a3bc7b3917
commit bf241eb1a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 115 additions and 30 deletions

View File

@ -24,6 +24,7 @@ from CTFd.utils import (
set_config,
)
from CTFd.cache import cache, clear_config
from CTFd.utils.helpers import get_errors
from CTFd.utils.exports import (
export_ctf as export_ctf_util,
import_ctf as import_ctf_util
@ -94,7 +95,7 @@ def plugin(plugin):
def import_ctf():
backup = request.files['backup']
segments = request.form.get('segments')
errors = []
errors = get_errors()
try:
import_ctf_util(backup)
except Exception as e:

View File

@ -1,11 +1,10 @@
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
from CTFd.utils.decorators import admins_only, ratelimit
from CTFd.models import db, Teams, Solves, Awards, Unlocks, Challenges, Fails, Flags, Tags, Files, Tracking, Pages, Configs
from passlib.hash import bcrypt_sha256
from sqlalchemy.sql import not_
from CTFd import utils
from CTFd.admin import admin
from CTFd.utils.helpers import get_errors, get_infos
from sqlalchemy.sql import not_
@admin.route('/admin/teams')
@ -16,7 +15,7 @@ def teams_listing():
if q:
field = request.args.get('field')
teams = []
errors = []
errors = get_errors()
if field == 'id':
if q.isnumeric():
teams = Teams.query.filter(Teams.id == q).order_by(Teams.id.asc()).all()

View File

@ -3,10 +3,10 @@ from CTFd.utils import get_config
from CTFd.utils.decorators import admins_only, ratelimit
from CTFd.utils.modes import USERS_MODE, TEAMS_MODE
from CTFd.models import db, Users, Challenges, Tracking
from sqlalchemy.sql import not_
from CTFd import utils
from CTFd.admin import admin
from CTFd.utils.helpers import get_errors, get_infos
from sqlalchemy.sql import not_
@admin.route('/admin/users')
@ -17,7 +17,7 @@ def users_listing():
if q:
field = request.args.get('field')
users = []
errors = []
errors = get_errors()
if field == 'id':
if q.isnumeric():
users = Users.query.filter(Users.id == q).order_by(Users.id.asc()).all()

View File

@ -1,4 +1,12 @@
from flask import current_app as app, render_template, request, redirect, url_for, session, Blueprint, abort
from flask import (
current_app as app,
render_template,
request,
redirect,
url_for,
session,
Blueprint,
)
from passlib.hash import bcrypt_sha256
from CTFd.models import db, Users, Teams
@ -13,6 +21,7 @@ from CTFd.utils.logging import log
from CTFd.utils.decorators.visibility import check_registration_visibility
from CTFd.utils.modes import TEAMS_MODE, USERS_MODE
from CTFd.utils.security.signing import serialize, unserialize, SignatureExpired, BadSignature, BadTimeSignature
from CTFd.utils.helpers import info_for, error_for, get_errors, get_infos
import base64
import requests
@ -96,7 +105,7 @@ def reset_password(data=None):
email_address = request.form['email'].strip()
team = Users.query.filter_by(email=email_address).first()
errors = []
errors = get_errors()
if config.can_send_mail() is False:
return render_template(
@ -123,8 +132,8 @@ def reset_password(data=None):
@check_registration_visibility
@ratelimit(method="POST", limit=10, interval=5)
def register():
errors = get_errors()
if request.method == 'POST':
errors = []
name = request.form['name']
email_address = request.form['email']
password = request.form['password']
@ -200,14 +209,14 @@ def register():
db.session.close()
return redirect(url_for('challenges.listing'))
else:
return render_template('register.html')
return render_template('register.html', errors=errors)
@auth.route('/login', methods=['POST', 'GET'])
@ratelimit(method="POST", limit=10, interval=5)
def login():
errors = get_errors()
if request.method == 'POST':
errors = []
name = request.form['name']
# Check if the user submitted an email address or a team name
@ -242,7 +251,7 @@ def login():
return render_template('login.html', errors=errors)
else:
db.session.close()
return render_template('login.html')
return render_template('login.html', errors=errors)
@auth.route('/oauth')
@ -257,6 +266,15 @@ def oauth_login():
scope = 'profile'
client_id = get_app_config('OAUTH_CLIENT_ID') or get_config('oauth_client_id')
if client_id is None:
error_for(
endpoint='auth.login',
message='OAuth Settings not configured. '
'Ask your CTF administrator to configure MajorLeagueCyber integration.'
)
return redirect(url_for('auth.login'))
redirect_url = "{endpoint}?response_type=code&client_id={client_id}&scope={scope}&state={state}".format(
endpoint=endpoint,
client_id=client_id,
@ -273,7 +291,8 @@ def oauth_redirect():
state = request.args.get('state')
if session['nonce'] != state:
log('logins', "[{date}] {ip} - OAuth State validation mismatch")
abort(500)
error_for(endpoint='auth.login', message='OAuth State validation mismatch.')
return redirect(url_for('auth.login'))
if oauth_code:
url = get_app_config('OAUTH_TOKEN_ENDPOINT') \
@ -341,10 +360,18 @@ def oauth_redirect():
return redirect(url_for('challenges.listing'))
else:
log('logins', "[{date}] {ip} - OAuth token retrieval failure")
abort(500)
error_for(
endpoint='auth.login',
message='OAuth token retrieval failure.'
)
return redirect(url_for('auth.login'))
else:
log('logins', "[{date}] {ip} - Received redirect without OAuth code")
abort(500)
error_for(
endpoint='auth.login',
message='Received redirect without OAuth code.'
)
return redirect(url_for('auth.login'))
@auth.route('/logout')

View File

@ -1,9 +1,8 @@
from flask import render_template, request, redirect, jsonify, url_for, session, Blueprint, abort
from CTFd.models import db, Challenges, Files, Solves, Fails, Flags, Tags, Teams, Awards, Hints, Unlocks
from CTFd.plugins.challenges import get_chal_class
from flask import (
render_template,
Blueprint,
)
from CTFd.utils.decorators import (
authed_only,
admins_only,
during_ctf_time_only,
require_verified_emails,
ratelimit,
@ -12,6 +11,7 @@ from CTFd.utils.decorators import (
from CTFd.utils.decorators.visibility import check_challenge_visibility
from CTFd.utils import config, text_type, user as current_user, get_config
from CTFd.utils.dates import ctftime, ctf_started, ctf_paused, ctf_ended, unix_time, unix_time_to_utc
from CTFd.utils.helpers import get_errors, get_infos
challenges = Blueprint('challenges', __name__)
@ -22,8 +22,8 @@ challenges = Blueprint('challenges', __name__)
@check_challenge_visibility
@require_team
def listing():
infos = []
errors = []
infos = get_infos()
errors = get_errors()
start = get_config('start') or 0
end = get_config('end') or 0

View File

@ -9,6 +9,7 @@ from CTFd.utils.user import get_current_user, authed, get_ip
from CTFd.utils.dates import unix_time_to_utc
from CTFd.utils.crypto import verify_password
from CTFd.utils.decorators.visibility import check_account_visibility, check_score_visibility
from CTFd.utils.helpers import get_errors, get_infos
teams = Blueprint('teams', __name__)
@ -65,7 +66,7 @@ def new():
elif request.method == 'POST':
teamname = request.form.get('name')
passphrase = request.form.get('password', '').strip()
errors = []
errors = get_errors()
user = get_current_user()
@ -127,7 +128,7 @@ def private():
@check_score_visibility
@require_team_mode
def public(team_id):
errors = []
errors = get_errors()
team = Teams.query.filter_by(id=team_id).first_or_404()
solves = team.get_solves()
awards = team.get_awards()

View File

@ -16,6 +16,9 @@
<li class="nav-item">
<a class="nav-link rounded-0" href="#accounts" role="tab" data-toggle="tab">Accounts</a>
</li>
<li class="nav-item">
<a class="nav-link rounded-0" href="#mlc" role="tab" data-toggle="tab">MajorLeagueCyber</a>
</li>
<li class="nav-item">
<a class="nav-link rounded-0" href="#settings" role="tab" data-toggle="tab">Settings</a>
</li>
@ -49,6 +52,8 @@
{% include "admin/configs/accounts.html" %}
{% include "admin/configs/mlc.html" %}
{% include "admin/configs/settings.html" %}
{% include "admin/configs/email.html" %}

View File

@ -0,0 +1,30 @@
<div role="tabpanel" class="tab-pane config-section" id="mlc">
<form method="POST" autocomplete="off" class="w-100">
<div class="form-group">
<label>
Client ID
<small class="form-text text-muted">
OAuth Client ID for <a href="https://majorleaguecyber.org/">MajorLeagueCyber</a> integration.
</small>
</label>
<input class="form-control" id='oauth_client_id' name='oauth_client_id' type='text'
placeholder=""
{% if oauth_client_id is defined and oauth_client_id != None %}value="{{ oauth_client_id }}"{% endif %}>
</div>
<div class="form-group">
<label>
Client Secret
<small class="form-text text-muted">
OAuth Client Secret for <a href="https://majorleaguecyber.org/">MajorLeagueCyber</a> integration.
</small>
</label>
<input class="form-control" id='oauth_client_secret' name='oauth_client_secret' type='text'
placeholder=""
{% if oauth_client_secret is defined and oauth_client_secret != None %}value="{{ oauth_client_secret }}"{% endif %}>
</div>
<button type="submit" class="btn btn-md btn-primary float-right">Update</button>
</form>
</div>

View File

@ -1 +1,23 @@
from flask import url_for, request
from flask import request, flash, get_flashed_messages
def info_for(endpoint, message):
flash(
message=message,
category=endpoint + '.infos'
)
def error_for(endpoint, message):
flash(
message=message,
category=endpoint + '.errors'
)
def get_infos():
return get_flashed_messages(category_filter=request.endpoint + '.infos')
def get_errors():
return get_flashed_messages(category_filter=request.endpoint + '.errors')

View File

@ -69,7 +69,7 @@ def test_user_can_unlock_hint():
r = client.get('/api/v1/hints/{}'.format(hint_id))
resp = r.get_json()
assert resp['data'].get('content')
assert resp['data'].get('content') == "This is a hint"
user = Users.query.filter_by(name="user1").first()
assert user.score == 5