WIP: Add registration password (#1946)

* Closes #1895 
* Add a registration password to account creation (ignoring SSO or API based account creation)
This commit is contained in:
Kevin Chung
2021-07-18 05:21:14 -04:00
committed by GitHub
parent fabdb291e2
commit f8f32042f8
6 changed files with 107 additions and 2 deletions

View File

@@ -197,6 +197,7 @@ def register():
website = request.form.get("website") website = request.form.get("website")
affiliation = request.form.get("affiliation") affiliation = request.form.get("affiliation")
country = request.form.get("country") country = request.form.get("country")
registration_code = request.form.get("registration_code", "")
name_len = len(name) == 0 name_len = len(name) == 0
names = Users.query.add_columns("name", "id").filter_by(name=name).first() names = Users.query.add_columns("name", "id").filter_by(name=name).first()
@@ -210,6 +211,13 @@ def register():
valid_email = validators.validate_email(email_address) valid_email = validators.validate_email(email_address)
team_name_email_check = validators.validate_email(name) team_name_email_check = validators.validate_email(name)
if get_config("registration_code"):
if (
registration_code.lower()
!= get_config("registration_code", default="").lower()
):
errors.append("The registration code you entered was incorrect")
# Process additional user fields # Process additional user fields
fields = {} fields = {}
for field in UserFields.query.all(): for field in UserFields.query.all():

View File

@@ -4,7 +4,12 @@ from wtforms.validators import InputRequired
from CTFd.forms import BaseForm from CTFd.forms import BaseForm
from CTFd.forms.fields import SubmitField from CTFd.forms.fields import SubmitField
from CTFd.forms.users import attach_custom_user_fields, build_custom_user_fields from CTFd.forms.users import (
attach_custom_user_fields,
attach_registration_code_field,
build_custom_user_fields,
build_registration_code_field,
)
def RegistrationForm(*args, **kwargs): def RegistrationForm(*args, **kwargs):
@@ -18,9 +23,10 @@ def RegistrationForm(*args, **kwargs):
def extra(self): def extra(self):
return build_custom_user_fields( return build_custom_user_fields(
self, include_entries=False, blacklisted_items=() self, include_entries=False, blacklisted_items=()
) ) + build_registration_code_field(self)
attach_custom_user_fields(_RegistrationForm) attach_custom_user_fields(_RegistrationForm)
attach_registration_code_field(_RegistrationForm)
return _RegistrationForm(*args, **kwargs) return _RegistrationForm(*args, **kwargs)

View File

@@ -2,6 +2,7 @@ from wtforms import BooleanField, PasswordField, SelectField, StringField
from wtforms.fields.html5 import EmailField from wtforms.fields.html5 import EmailField
from wtforms.validators import InputRequired from wtforms.validators import InputRequired
from CTFd.constants.config import Configs
from CTFd.forms import BaseForm from CTFd.forms import BaseForm
from CTFd.forms.fields import SubmitField from CTFd.forms.fields import SubmitField
from CTFd.models import UserFieldEntries, UserFields from CTFd.models import UserFieldEntries, UserFields
@@ -79,6 +80,36 @@ def attach_custom_user_fields(form_cls, **kwargs):
setattr(form_cls, f"fields[{field.id}]", input_field) setattr(form_cls, f"fields[{field.id}]", input_field)
def build_registration_code_field(form_cls):
"""
Build the appropriate field so we can render it via the extra property.
Add field_type so Jinja knows how to render it.
"""
if Configs.registration_code:
field = getattr(form_cls, "registration_code") # noqa B009
field.field_type = "text"
return [field]
else:
return []
def attach_registration_code_field(form_cls):
"""
If we have a registration code required, we attach it to the form similar
to attach_custom_user_fields
"""
if Configs.registration_code:
setattr( # noqa B010
form_cls,
"registration_code",
StringField(
"Registration Code",
description="Registration code required to create account",
validators=[InputRequired()],
),
)
class UserSearchForm(BaseForm): class UserSearchForm(BaseForm):
field = SelectField( field = SelectField(
"Search Field", "Search Field",

View File

@@ -32,6 +32,9 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link rounded-0" href="#settings" role="tab" data-toggle="tab">Settings</a> <a class="nav-link rounded-0" href="#settings" role="tab" data-toggle="tab">Settings</a>
</li> </li>
<li class="nav-item">
<a class="nav-link rounded-0" href="#security" role="tab" data-toggle="tab">Security</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link rounded-0" href="#email" role="tab" data-toggle="tab">Email</a> <a class="nav-link rounded-0" href="#email" role="tab" data-toggle="tab">Email</a>
</li> </li>
@@ -73,6 +76,8 @@
{% include "admin/configs/settings.html" %} {% include "admin/configs/settings.html" %}
{% include "admin/configs/security.html" %}
{% include "admin/configs/email.html" %} {% include "admin/configs/email.html" %}
{% include "admin/configs/time.html" %} {% include "admin/configs/time.html" %}

View File

@@ -0,0 +1,14 @@
<div role="tabpanel" class="tab-pane config-section active" id="security">
<form method="POST" autocomplete="off" class="w-100">
<div class="form-group">
<label for="ctf_name">
Registration Code
<small class="form-text text-muted">Registration Code required for account registration (SSO excluded)</small>
</label>
<input class="form-control" id='registration_code' name='registration_code' type='text' placeholder="Registration Code"
{% if registration_code is defined and registration_code != None %}value="{{ registration_code }}"{% endif %}>
</div>
<button type="submit" class="btn btn-md btn-primary float-right">Update</button>
</form>
</div>

View File

@@ -423,3 +423,44 @@ def test_banned_user():
r = client.get(route) r = client.get(route)
assert r.status_code == 403 assert r.status_code == 403
destroy_ctfd(app) destroy_ctfd(app)
def test_registration_code_required():
"""
Test that registration code configuration properly blocks logins
with missing and incorrect registration codes
"""
app = create_ctfd()
with app.app_context():
# Set a registration code
set_config("registration_code", "secret-sauce")
with app.test_client() as client:
# Load CSRF nonce
r = client.get("/register")
resp = r.get_data(as_text=True)
assert "Registration Code" in resp
with client.session_transaction() as sess:
data = {
"name": "user",
"email": "user1@examplectf.com",
"password": "password",
"nonce": sess.get("nonce"),
}
# Attempt registration without password
r = client.post("/register", data=data)
resp = r.get_data(as_text=True)
assert "The registration code you entered was incorrect" in resp
# Attempt registration with wrong password
data["registration_code"] = "wrong-sauce"
r = client.post("/register", data=data)
resp = r.get_data(as_text=True)
assert "The registration code you entered was incorrect" in resp
# Attempt registration with right password
data["registration_code"] = "secret-sauce"
r = client.post("/register", data=data)
assert r.status_code == 302
assert r.location.startswith("http://localhost/challenges")
destroy_ctfd(app)