mirror of https://github.com/JohnHammond/CTFd.git
Auth Improvments (#746)
* Create generic get_errors, get_infos; add MLC OAuth settings in config * Use new get_errors functionselenium-screenshot-testing
parent
a3bc7b3917
commit
bf241eb1a5
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
45
CTFd/auth.py
45
CTFd/auth.py
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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" %}
|
||||
|
|
|
@ -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>
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue