mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-18 14:34:21 +01:00
Add a decorator for redirecting users if their profile isn't complete (#1933)
* Redirect users and teams whose profiles are incomplete to complete their profile * Closes #1926
This commit is contained in:
@@ -3,7 +3,11 @@ from flask import Blueprint, redirect, render_template, request, url_for
|
|||||||
from CTFd.constants.config import ChallengeVisibilityTypes, Configs
|
from CTFd.constants.config import ChallengeVisibilityTypes, Configs
|
||||||
from CTFd.utils.config import is_teams_mode
|
from CTFd.utils.config import is_teams_mode
|
||||||
from CTFd.utils.dates import ctf_ended, ctf_paused, ctf_started
|
from CTFd.utils.dates import ctf_ended, ctf_paused, ctf_started
|
||||||
from CTFd.utils.decorators import during_ctf_time_only, require_verified_emails
|
from CTFd.utils.decorators import (
|
||||||
|
during_ctf_time_only,
|
||||||
|
require_complete_profile,
|
||||||
|
require_verified_emails,
|
||||||
|
)
|
||||||
from CTFd.utils.decorators.visibility import check_challenge_visibility
|
from CTFd.utils.decorators.visibility import check_challenge_visibility
|
||||||
from CTFd.utils.helpers import get_errors, get_infos
|
from CTFd.utils.helpers import get_errors, get_infos
|
||||||
from CTFd.utils.user import authed, get_current_team
|
from CTFd.utils.user import authed, get_current_team
|
||||||
@@ -12,6 +16,7 @@ challenges = Blueprint("challenges", __name__)
|
|||||||
|
|
||||||
|
|
||||||
@challenges.route("/challenges", methods=["GET"])
|
@challenges.route("/challenges", methods=["GET"])
|
||||||
|
@require_complete_profile
|
||||||
@during_ctf_time_only
|
@during_ctf_time_only
|
||||||
@require_verified_emails
|
@require_verified_emails
|
||||||
@check_challenge_visibility
|
@check_challenge_visibility
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ 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, build_custom_user_fields
|
||||||
from CTFd.utils.countries import SELECT_COUNTRIES_LIST
|
from CTFd.utils.countries import SELECT_COUNTRIES_LIST
|
||||||
|
from CTFd.utils.user import get_current_user
|
||||||
|
|
||||||
|
|
||||||
def SettingsForm(*args, **kwargs):
|
def SettingsForm(*args, **kwargs):
|
||||||
@@ -21,14 +22,25 @@ def SettingsForm(*args, **kwargs):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def extra(self):
|
def extra(self):
|
||||||
|
fields_kwargs = _SettingsForm.get_field_kwargs()
|
||||||
return build_custom_user_fields(
|
return build_custom_user_fields(
|
||||||
self,
|
self,
|
||||||
include_entries=True,
|
include_entries=True,
|
||||||
fields_kwargs={"editable": True},
|
fields_kwargs=fields_kwargs,
|
||||||
field_entries_kwargs={"user_id": session["id"]},
|
field_entries_kwargs={"user_id": session["id"]},
|
||||||
)
|
)
|
||||||
|
|
||||||
attach_custom_user_fields(_SettingsForm, editable=True)
|
@staticmethod
|
||||||
|
def get_field_kwargs():
|
||||||
|
user = get_current_user()
|
||||||
|
field_kwargs = {"editable": True}
|
||||||
|
if user.filled_all_required_fields is False:
|
||||||
|
# Show all fields
|
||||||
|
field_kwargs = {}
|
||||||
|
return field_kwargs
|
||||||
|
|
||||||
|
field_kwargs = _SettingsForm.get_field_kwargs()
|
||||||
|
attach_custom_user_fields(_SettingsForm, **field_kwargs)
|
||||||
|
|
||||||
return _SettingsForm(*args, **kwargs)
|
return _SettingsForm(*args, **kwargs)
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from CTFd.forms import BaseForm
|
|||||||
from CTFd.forms.fields import SubmitField
|
from CTFd.forms.fields import SubmitField
|
||||||
from CTFd.models import TeamFieldEntries, TeamFields
|
from CTFd.models import TeamFieldEntries, TeamFields
|
||||||
from CTFd.utils.countries import SELECT_COUNTRIES_LIST
|
from CTFd.utils.countries import SELECT_COUNTRIES_LIST
|
||||||
|
from CTFd.utils.user import get_current_team
|
||||||
|
|
||||||
|
|
||||||
def build_custom_team_fields(
|
def build_custom_team_fields(
|
||||||
@@ -122,13 +123,22 @@ def TeamSettingsForm(*args, **kwargs):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def extra(self):
|
def extra(self):
|
||||||
|
fields_kwargs = _TeamSettingsForm.get_field_kwargs()
|
||||||
return build_custom_team_fields(
|
return build_custom_team_fields(
|
||||||
self,
|
self,
|
||||||
include_entries=True,
|
include_entries=True,
|
||||||
fields_kwargs={"editable": True},
|
fields_kwargs=fields_kwargs,
|
||||||
field_entries_kwargs={"team_id": self.obj.id},
|
field_entries_kwargs={"team_id": self.obj.id},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_field_kwargs():
|
||||||
|
team = get_current_team()
|
||||||
|
field_kwargs = {"editable": True}
|
||||||
|
if team.filled_all_required_fields is False:
|
||||||
|
# Show all fields
|
||||||
|
field_kwargs = {}
|
||||||
|
return field_kwargs
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Custom init to persist the obj parameter to the rest of the form
|
Custom init to persist the obj parameter to the rest of the form
|
||||||
@@ -138,7 +148,9 @@ def TeamSettingsForm(*args, **kwargs):
|
|||||||
if obj:
|
if obj:
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
|
|
||||||
attach_custom_team_fields(_TeamSettingsForm)
|
field_kwargs = _TeamSettingsForm.get_field_kwargs()
|
||||||
|
attach_custom_team_fields(_TeamSettingsForm, **field_kwargs)
|
||||||
|
|
||||||
return _TeamSettingsForm(*args, **kwargs)
|
return _TeamSettingsForm(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -367,6 +367,22 @@ class Users(db.Model):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filled_all_required_fields(self):
|
||||||
|
required_user_fields = {
|
||||||
|
u.id
|
||||||
|
for u in UserFields.query.with_entities(UserFields.id)
|
||||||
|
.filter_by(required=True)
|
||||||
|
.all()
|
||||||
|
}
|
||||||
|
submitted_user_fields = {
|
||||||
|
u.field_id
|
||||||
|
for u in UserFieldEntries.query.with_entities(UserFieldEntries.field_id)
|
||||||
|
.filter_by(user_id=self.id)
|
||||||
|
.all()
|
||||||
|
}
|
||||||
|
return required_user_fields.issubset(submitted_user_fields)
|
||||||
|
|
||||||
def get_fields(self, admin=False):
|
def get_fields(self, admin=False):
|
||||||
if admin:
|
if admin:
|
||||||
return self.field_entries
|
return self.field_entries
|
||||||
@@ -538,6 +554,22 @@ class Teams(db.Model):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filled_all_required_fields(self):
|
||||||
|
required_team_fields = {
|
||||||
|
u.id
|
||||||
|
for u in TeamFields.query.with_entities(TeamFields.id)
|
||||||
|
.filter_by(required=True)
|
||||||
|
.all()
|
||||||
|
}
|
||||||
|
submitted_team_fields = {
|
||||||
|
u.field_id
|
||||||
|
for u in TeamFieldEntries.query.with_entities(TeamFieldEntries.field_id)
|
||||||
|
.filter_by(team_id=self.id)
|
||||||
|
.all()
|
||||||
|
}
|
||||||
|
return required_team_fields.issubset(submitted_team_fields)
|
||||||
|
|
||||||
def get_fields(self, admin=False):
|
def get_fields(self, admin=False):
|
||||||
if admin:
|
if admin:
|
||||||
return self.field_entries
|
return self.field_entries
|
||||||
|
|||||||
@@ -259,22 +259,22 @@ class TeamSchema(ma.ModelSchema):
|
|||||||
# # Check that we have an existing field for this. May be unnecessary b/c the foriegn key should enforce
|
# # Check that we have an existing field for this. May be unnecessary b/c the foriegn key should enforce
|
||||||
field = TeamFields.query.filter_by(id=field_id).first_or_404()
|
field = TeamFields.query.filter_by(id=field_id).first_or_404()
|
||||||
|
|
||||||
|
# Get the existing field entry if one exists
|
||||||
|
entry = TeamFieldEntries.query.filter_by(
|
||||||
|
field_id=field.id, team_id=current_team.id
|
||||||
|
).first()
|
||||||
|
|
||||||
if field.required is True and value.strip() == "":
|
if field.required is True and value.strip() == "":
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
f"Field '{field.name}' is required", field_names=["fields"]
|
f"Field '{field.name}' is required", field_names=["fields"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if field.editable is False:
|
if field.editable is False and entry is not None:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
f"Field '{field.name}' cannot be editted",
|
f"Field '{field.name}' cannot be editted",
|
||||||
field_names=["fields"],
|
field_names=["fields"],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the existing field entry if one exists
|
|
||||||
entry = TeamFieldEntries.query.filter_by(
|
|
||||||
field_id=field.id, team_id=current_team.id
|
|
||||||
).first()
|
|
||||||
|
|
||||||
if entry:
|
if entry:
|
||||||
f["id"] = entry.id
|
f["id"] = entry.id
|
||||||
provided_ids.append(entry.id)
|
provided_ids.append(entry.id)
|
||||||
|
|||||||
@@ -245,22 +245,22 @@ class UserSchema(ma.ModelSchema):
|
|||||||
# # Check that we have an existing field for this. May be unnecessary b/c the foriegn key should enforce
|
# # Check that we have an existing field for this. May be unnecessary b/c the foriegn key should enforce
|
||||||
field = UserFields.query.filter_by(id=field_id).first_or_404()
|
field = UserFields.query.filter_by(id=field_id).first_or_404()
|
||||||
|
|
||||||
|
# Get the existing field entry if one exists
|
||||||
|
entry = UserFieldEntries.query.filter_by(
|
||||||
|
field_id=field.id, user_id=current_user.id
|
||||||
|
).first()
|
||||||
|
|
||||||
if field.required is True and value.strip() == "":
|
if field.required is True and value.strip() == "":
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
f"Field '{field.name}' is required", field_names=["fields"]
|
f"Field '{field.name}' is required", field_names=["fields"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if field.editable is False:
|
if field.editable is False and entry is not None:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
f"Field '{field.name}' cannot be editted",
|
f"Field '{field.name}' cannot be editted",
|
||||||
field_names=["fields"],
|
field_names=["fields"],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the existing field entry if one exists
|
|
||||||
entry = UserFieldEntries.query.filter_by(
|
|
||||||
field_id=field.id, user_id=current_user.id
|
|
||||||
).first()
|
|
||||||
|
|
||||||
if entry:
|
if entry:
|
||||||
f["id"] = entry.id
|
f["id"] = entry.id
|
||||||
provided_ids.append(entry.id)
|
provided_ids.append(entry.id)
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ from flask import abort, jsonify, redirect, request, url_for
|
|||||||
from CTFd.cache import cache
|
from CTFd.cache import cache
|
||||||
from CTFd.utils import config, get_config
|
from CTFd.utils import config, get_config
|
||||||
from CTFd.utils import user as current_user
|
from CTFd.utils import user as current_user
|
||||||
|
from CTFd.utils.config import is_teams_mode
|
||||||
from CTFd.utils.dates import ctf_ended, ctf_started, ctftime, view_after_ctf
|
from CTFd.utils.dates import ctf_ended, ctf_started, ctftime, view_after_ctf
|
||||||
from CTFd.utils.modes import TEAMS_MODE
|
from CTFd.utils.user import authed, get_current_team, get_current_user, is_admin
|
||||||
from CTFd.utils.user import authed, get_current_team, is_admin
|
|
||||||
|
|
||||||
|
|
||||||
def during_ctf_time_only(f):
|
def during_ctf_time_only(f):
|
||||||
@@ -143,7 +143,7 @@ def admins_only(f):
|
|||||||
def require_team(f):
|
def require_team(f):
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
def require_team_wrapper(*args, **kwargs):
|
def require_team_wrapper(*args, **kwargs):
|
||||||
if get_config("user_mode") == TEAMS_MODE:
|
if is_teams_mode():
|
||||||
team = get_current_team()
|
team = get_current_team()
|
||||||
if team is None:
|
if team is None:
|
||||||
if request.content_type == "application/json":
|
if request.content_type == "application/json":
|
||||||
@@ -186,3 +186,39 @@ def ratelimit(method="POST", limit=50, interval=300, key_prefix="rl"):
|
|||||||
return ratelimit_function
|
return ratelimit_function
|
||||||
|
|
||||||
return ratelimit_decorator
|
return ratelimit_decorator
|
||||||
|
|
||||||
|
|
||||||
|
def require_complete_profile(f):
|
||||||
|
from CTFd.utils.helpers import info_for
|
||||||
|
|
||||||
|
@functools.wraps(f)
|
||||||
|
def _require_complete_profile(*args, **kwargs):
|
||||||
|
if authed():
|
||||||
|
if is_admin():
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
user = get_current_user()
|
||||||
|
|
||||||
|
if user.filled_all_required_fields is False:
|
||||||
|
info_for(
|
||||||
|
"views.settings",
|
||||||
|
"Please fill out all required profile fields before continuing",
|
||||||
|
)
|
||||||
|
return redirect(url_for("views.settings"))
|
||||||
|
|
||||||
|
if is_teams_mode():
|
||||||
|
team = get_current_team()
|
||||||
|
|
||||||
|
if team and team.filled_all_required_fields is False:
|
||||||
|
# This is an abort because it's difficult for us to flash information on the teams page
|
||||||
|
return abort(
|
||||||
|
403,
|
||||||
|
description="Please fill in all required team profile fields",
|
||||||
|
)
|
||||||
|
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
# Fallback to whatever behavior the route defaults to
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
return _require_complete_profile
|
||||||
|
|||||||
@@ -304,6 +304,7 @@ def notifications():
|
|||||||
@authed_only
|
@authed_only
|
||||||
def settings():
|
def settings():
|
||||||
infos = get_infos()
|
infos = get_infos()
|
||||||
|
errors = get_errors()
|
||||||
|
|
||||||
user = get_current_user()
|
user = get_current_user()
|
||||||
name = user.name
|
name = user.name
|
||||||
@@ -336,6 +337,7 @@ def settings():
|
|||||||
tokens=tokens,
|
tokens=tokens,
|
||||||
prevent_name_change=prevent_name_change,
|
prevent_name_change=prevent_name_change,
|
||||||
infos=infos,
|
infos=infos,
|
||||||
|
errors=errors,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -99,16 +99,9 @@ def test_team_fields_required_on_creation():
|
|||||||
|
|
||||||
|
|
||||||
def test_team_fields_properties():
|
def test_team_fields_properties():
|
||||||
|
"""Test that custom fields for team can be set and editted"""
|
||||||
app = create_ctfd(user_mode="teams")
|
app = create_ctfd(user_mode="teams")
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
register_user(app)
|
|
||||||
team = gen_team(app.db)
|
|
||||||
user = Users.query.filter_by(id=2).first()
|
|
||||||
user.team_id = team.id
|
|
||||||
team = Teams.query.filter_by(id=1).first()
|
|
||||||
team.captain_id = 2
|
|
||||||
app.db.session.commit()
|
|
||||||
|
|
||||||
gen_field(
|
gen_field(
|
||||||
app.db,
|
app.db,
|
||||||
name="CustomField1",
|
name="CustomField1",
|
||||||
@@ -142,6 +135,8 @@ def test_team_fields_properties():
|
|||||||
editable=False,
|
editable=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
register_user(app)
|
||||||
|
|
||||||
with login_as_user(app) as client:
|
with login_as_user(app) as client:
|
||||||
r = client.get("/teams/new")
|
r = client.get("/teams/new")
|
||||||
resp = r.get_data(as_text=True)
|
resp = r.get_data(as_text=True)
|
||||||
@@ -150,6 +145,17 @@ def test_team_fields_properties():
|
|||||||
assert "CustomField3" in resp
|
assert "CustomField3" in resp
|
||||||
assert "CustomField4" in resp
|
assert "CustomField4" in resp
|
||||||
|
|
||||||
|
# Manually create team so that we can set the required profile field
|
||||||
|
with client.session_transaction() as sess:
|
||||||
|
data = {
|
||||||
|
"name": "team",
|
||||||
|
"password": "password",
|
||||||
|
"fields[1]": "custom_field_value",
|
||||||
|
"nonce": sess.get("nonce"),
|
||||||
|
}
|
||||||
|
r = client.post("/teams/new", data=data)
|
||||||
|
assert r.status_code == 302
|
||||||
|
|
||||||
r = client.get("/team")
|
r = client.get("/team")
|
||||||
resp = r.get_data(as_text=True)
|
resp = r.get_data(as_text=True)
|
||||||
assert "CustomField1" in resp
|
assert "CustomField1" in resp
|
||||||
@@ -249,3 +255,142 @@ def test_teams_boolean_checkbox_field():
|
|||||||
assert TeamFieldEntries.query.count() == 1
|
assert TeamFieldEntries.query.count() == 1
|
||||||
assert TeamFieldEntries.query.filter_by(id=1).first().value is False
|
assert TeamFieldEntries.query.filter_by(id=1).first().value is False
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|
||||||
|
|
||||||
|
def test_team_needs_all_required_fields():
|
||||||
|
"""Test that teams need to complete profiles before seeing challenges"""
|
||||||
|
app = create_ctfd(user_mode="teams")
|
||||||
|
with app.app_context():
|
||||||
|
# Create a user and team who haven't filled any of their fields
|
||||||
|
register_user(app)
|
||||||
|
team = gen_team(app.db)
|
||||||
|
user = Users.query.filter_by(id=2).first()
|
||||||
|
user.team_id = team.id
|
||||||
|
team = Teams.query.filter_by(id=1).first()
|
||||||
|
team.captain_id = 2
|
||||||
|
app.db.session.commit()
|
||||||
|
|
||||||
|
gen_field(
|
||||||
|
app.db,
|
||||||
|
name="CustomField1",
|
||||||
|
type="team",
|
||||||
|
required=True,
|
||||||
|
public=True,
|
||||||
|
editable=True,
|
||||||
|
)
|
||||||
|
gen_field(
|
||||||
|
app.db,
|
||||||
|
name="CustomField2",
|
||||||
|
type="team",
|
||||||
|
required=False,
|
||||||
|
public=True,
|
||||||
|
editable=True,
|
||||||
|
)
|
||||||
|
gen_field(
|
||||||
|
app.db,
|
||||||
|
name="CustomField3",
|
||||||
|
type="team",
|
||||||
|
required=False,
|
||||||
|
public=False,
|
||||||
|
editable=True,
|
||||||
|
)
|
||||||
|
gen_field(
|
||||||
|
app.db,
|
||||||
|
name="CustomField4",
|
||||||
|
type="team",
|
||||||
|
required=False,
|
||||||
|
public=False,
|
||||||
|
editable=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
with login_as_user(app) as client:
|
||||||
|
r = client.get("/teams/new")
|
||||||
|
resp = r.get_data(as_text=True)
|
||||||
|
assert "CustomField1" in resp
|
||||||
|
assert "CustomField2" in resp
|
||||||
|
assert "CustomField3" in resp
|
||||||
|
assert "CustomField4" in resp
|
||||||
|
|
||||||
|
# We can't view challenges because we have an incomplete team profile
|
||||||
|
r = client.get("/challenges")
|
||||||
|
assert r.status_code == 403
|
||||||
|
|
||||||
|
# When we go to our profile we should see all fields
|
||||||
|
r = client.get("/team")
|
||||||
|
resp = r.get_data(as_text=True)
|
||||||
|
assert "CustomField1" in resp
|
||||||
|
assert "CustomField2" in resp
|
||||||
|
assert "CustomField3" in resp
|
||||||
|
assert "CustomField4" in resp
|
||||||
|
|
||||||
|
# Set all non-required fields
|
||||||
|
r = client.patch(
|
||||||
|
"/api/v1/teams/me",
|
||||||
|
json={
|
||||||
|
"fields": [
|
||||||
|
# {"field_id": 1, "value": "CustomFieldEntry1"},
|
||||||
|
{"field_id": 2, "value": "CustomFieldEntry2"},
|
||||||
|
{"field_id": 3, "value": "CustomFieldEntry3"},
|
||||||
|
{"field_id": 4, "value": "CustomFieldEntry4"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
# We can't view challenges because we have an incomplete team profile
|
||||||
|
r = client.get("/challenges")
|
||||||
|
assert r.status_code == 403
|
||||||
|
|
||||||
|
# Set required fields
|
||||||
|
r = client.patch(
|
||||||
|
"/api/v1/teams/me",
|
||||||
|
json={"fields": [{"field_id": 1, "value": "CustomFieldEntry1"}]},
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
# We can view challenges now
|
||||||
|
r = client.get("/challenges")
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
# Attempts to edit a non-edittable field to field after completing profile
|
||||||
|
r = client.patch(
|
||||||
|
"/api/v1/teams/me",
|
||||||
|
json={"fields": [{"field_id": 4, "value": "CustomFieldEntry4"}]},
|
||||||
|
)
|
||||||
|
resp = r.get_json()
|
||||||
|
assert resp == {
|
||||||
|
"success": False,
|
||||||
|
"errors": {"fields": ["Field 'CustomField4' cannot be editted"]},
|
||||||
|
}
|
||||||
|
|
||||||
|
# I can edit edittable fields
|
||||||
|
r = client.patch(
|
||||||
|
"/api/v1/teams/me",
|
||||||
|
json={
|
||||||
|
"fields": [
|
||||||
|
{"field_id": 1, "value": "CustomFieldEntry1"},
|
||||||
|
{"field_id": 2, "value": "CustomFieldEntry2"},
|
||||||
|
{"field_id": 3, "value": "CustomFieldEntry3"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
# I should see the correct fields in the private team profile
|
||||||
|
r = client.get("/team")
|
||||||
|
resp = r.get_data(as_text=True)
|
||||||
|
assert "CustomField1" in resp
|
||||||
|
assert "CustomField2" in resp
|
||||||
|
assert (
|
||||||
|
"CustomField3" in resp
|
||||||
|
) # This is here because /team contains team settings
|
||||||
|
assert "CustomField4" not in resp
|
||||||
|
|
||||||
|
# I should see the correct fields in the public team profile
|
||||||
|
r = client.get("/teams/1")
|
||||||
|
resp = r.get_data(as_text=True)
|
||||||
|
assert "CustomField1" in resp
|
||||||
|
assert "CustomField2" in resp
|
||||||
|
assert "CustomField3" not in resp
|
||||||
|
assert "CustomField4" not in resp
|
||||||
|
destroy_ctfd(app)
|
||||||
|
|||||||
@@ -85,10 +85,9 @@ def test_fields_required_on_register():
|
|||||||
|
|
||||||
|
|
||||||
def test_fields_properties():
|
def test_fields_properties():
|
||||||
|
"""Test that users can set and edit custom fields"""
|
||||||
app = create_ctfd()
|
app = create_ctfd()
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
register_user(app)
|
|
||||||
|
|
||||||
gen_field(
|
gen_field(
|
||||||
app.db, name="CustomField1", required=True, public=True, editable=True
|
app.db, name="CustomField1", required=True, public=True, editable=True
|
||||||
)
|
)
|
||||||
@@ -110,6 +109,19 @@ def test_fields_properties():
|
|||||||
assert "CustomField3" in resp
|
assert "CustomField3" in resp
|
||||||
assert "CustomField4" in resp
|
assert "CustomField4" in resp
|
||||||
|
|
||||||
|
# Manually register user so that we can populate the required field
|
||||||
|
with client.session_transaction() as sess:
|
||||||
|
data = {
|
||||||
|
"name": "user",
|
||||||
|
"email": "user@examplectf.com",
|
||||||
|
"password": "password",
|
||||||
|
"fields[1]": "custom_field_value",
|
||||||
|
"nonce": sess.get("nonce"),
|
||||||
|
}
|
||||||
|
client.post("/register", data=data)
|
||||||
|
with client.session_transaction() as sess:
|
||||||
|
assert sess["id"]
|
||||||
|
|
||||||
with login_as_user(app) as client:
|
with login_as_user(app) as client:
|
||||||
r = client.get("/settings")
|
r = client.get("/settings")
|
||||||
resp = r.get_data(as_text=True)
|
resp = r.get_data(as_text=True)
|
||||||
@@ -203,3 +215,105 @@ def test_boolean_checkbox_field():
|
|||||||
assert UserFieldEntries.query.count() == 1
|
assert UserFieldEntries.query.count() == 1
|
||||||
assert UserFieldEntries.query.filter_by(id=1).first().value is False
|
assert UserFieldEntries.query.filter_by(id=1).first().value is False
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_needs_all_required_fields():
|
||||||
|
"""Test that users need to submit all required fields before viewing challenges"""
|
||||||
|
app = create_ctfd()
|
||||||
|
with app.app_context():
|
||||||
|
# Manually create a user who has no fields set
|
||||||
|
register_user(app)
|
||||||
|
|
||||||
|
# Create the fields that we want
|
||||||
|
gen_field(
|
||||||
|
app.db, name="CustomField1", required=True, public=True, editable=True
|
||||||
|
)
|
||||||
|
gen_field(
|
||||||
|
app.db, name="CustomField2", required=False, public=True, editable=True
|
||||||
|
)
|
||||||
|
gen_field(
|
||||||
|
app.db, name="CustomField3", required=False, public=False, editable=True
|
||||||
|
)
|
||||||
|
gen_field(
|
||||||
|
app.db, name="CustomField4", required=False, public=False, editable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# We can see all fields when we try to register
|
||||||
|
with app.test_client() as client:
|
||||||
|
r = client.get("/register")
|
||||||
|
resp = r.get_data(as_text=True)
|
||||||
|
assert "CustomField1" in resp
|
||||||
|
assert "CustomField2" in resp
|
||||||
|
assert "CustomField3" in resp
|
||||||
|
assert "CustomField4" in resp
|
||||||
|
|
||||||
|
# When we login with our manually made user
|
||||||
|
# we should see all fields because we are missing a required field
|
||||||
|
with login_as_user(app) as client:
|
||||||
|
r = client.get("/settings")
|
||||||
|
resp = r.get_data(as_text=True)
|
||||||
|
assert "CustomField1" in resp
|
||||||
|
assert "CustomField2" in resp
|
||||||
|
assert "CustomField3" in resp
|
||||||
|
assert "CustomField4" in resp
|
||||||
|
|
||||||
|
r = client.get("/challenges")
|
||||||
|
assert r.status_code == 302
|
||||||
|
assert r.location.startswith("http://localhost/settings")
|
||||||
|
|
||||||
|
# Populate the non-required fields
|
||||||
|
r = client.patch(
|
||||||
|
"/api/v1/users/me",
|
||||||
|
json={
|
||||||
|
"fields": [
|
||||||
|
{"field_id": 2, "value": "CustomFieldEntry2"},
|
||||||
|
{"field_id": 3, "value": "CustomFieldEntry3"},
|
||||||
|
{"field_id": 4, "value": "CustomFieldEntry4"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
# I should still be restricted from seeing challenges
|
||||||
|
r = client.get("/challenges")
|
||||||
|
assert r.status_code == 302
|
||||||
|
assert r.location.startswith("http://localhost/settings")
|
||||||
|
|
||||||
|
# I should still see all fields b/c I don't have a complete profile
|
||||||
|
r = client.get("/settings")
|
||||||
|
resp = r.get_data(as_text=True)
|
||||||
|
assert "CustomField1" in resp
|
||||||
|
assert "CustomField2" in resp
|
||||||
|
assert "CustomField3" in resp
|
||||||
|
assert "CustomField4" in resp
|
||||||
|
|
||||||
|
# Populate the required fields
|
||||||
|
r = client.patch(
|
||||||
|
"/api/v1/users/me",
|
||||||
|
json={"fields": [{"field_id": 1, "value": "CustomFieldEntry1"}]},
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
# I can now go to challenges
|
||||||
|
r = client.get("/challenges")
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
# I should only see edittable fields
|
||||||
|
r = client.get("/settings")
|
||||||
|
resp = r.get_data(as_text=True)
|
||||||
|
assert "CustomField1" in resp
|
||||||
|
assert "CustomField2" in resp
|
||||||
|
assert "CustomField3" in resp
|
||||||
|
assert "CustomField4" not in resp
|
||||||
|
|
||||||
|
# I can't edit a non-editable field
|
||||||
|
r = client.patch(
|
||||||
|
"/api/v1/users/me",
|
||||||
|
json={"fields": [{"field_id": 4, "value": "CustomFieldEntry4"}]},
|
||||||
|
)
|
||||||
|
resp = r.get_json()
|
||||||
|
assert resp == {
|
||||||
|
"success": False,
|
||||||
|
"errors": {"fields": ["Field 'CustomField4' cannot be editted"]},
|
||||||
|
}
|
||||||
|
destroy_ctfd(app)
|
||||||
|
|||||||
Reference in New Issue
Block a user