init CTFd source
Some checks are pending
Linting / Linting (3.11) (push) Waiting to run
Mirror core-theme / mirror (push) Waiting to run

This commit is contained in:
gkr
2025-12-25 09:39:21 +08:00
commit 2e06f92c64
1047 changed files with 150349 additions and 0 deletions

0
tests/teams/__init__.py Normal file
View File

245
tests/teams/test_auth.py Normal file
View File

@@ -0,0 +1,245 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Teams, Users, db
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_team,
gen_user,
login_as_user,
register_user,
)
def test_banned_team():
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
team = gen_team(app.db, banned=True)
user = Users.query.filter_by(id=2).first()
user.team_id = team.id
db.session.commit()
client = login_as_user(app)
routes = ["/", "/challenges", "/api/v1/challenges"]
for route in routes:
r = client.get(route)
assert r.status_code == 403
destroy_ctfd(app)
def test_teams_join_get():
"""Can a user get /teams/join"""
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
with login_as_user(app) as client:
r = client.get("/teams/join")
assert r.status_code == 200
destroy_ctfd(app)
def test_teams_join_post():
"""Can a user post /teams/join"""
app = create_ctfd(user_mode="teams")
with app.app_context():
gen_user(app.db, name="user")
gen_team(app.db, name="team")
with login_as_user(app) as client:
r = client.get("/teams/join")
assert r.status_code == 200
with client.session_transaction() as sess:
data = {
"name": "team",
"password": "password",
"nonce": sess.get("nonce"),
}
r = client.post("/teams/join", data=data)
assert r.status_code == 302
# Cannot join a team with an incorrect password
incorrect_data = data
incorrect_data["password"] = ""
r = client.post("/teams/join", data=incorrect_data)
assert r.status_code == 403
destroy_ctfd(app)
def test_teams_join_when_already_on_team():
"""Test that a user cannot join another team"""
app = create_ctfd(user_mode="teams")
with app.app_context():
gen_user(app.db, name="user")
gen_team(app.db, email="team1@examplectf.com", name="team1")
gen_team(app.db, email="team2@examplectf.com", name="team2")
with login_as_user(app) as client:
r = client.get("/teams/join")
assert r.status_code == 200
with client.session_transaction() as sess:
data = {
"name": "team1",
"password": "password",
"nonce": sess.get("nonce"),
}
r = client.post("/teams/join", data=data)
assert r.status_code == 302
# Try to join another team while on a team
r = client.get("/teams/join")
assert r.status_code == 200
with client.session_transaction() as sess:
data = {
"name": "team2",
"password": "password",
"nonce": sess.get("nonce"),
}
r = client.post("/teams/join", data=data)
assert r.status_code == 403
user = Users.query.filter_by(name="user").first()
assert user.team.name == "team1"
destroy_ctfd(app)
def test_team_login():
"""Can a user login as a team"""
app = create_ctfd(user_mode="teams")
with app.app_context():
user = gen_user(app.db, name="user")
team = gen_team(app.db)
user.team_id = team.id
team.members.append(user)
app.db.session.commit()
with login_as_user(app) as client:
r = client.get("/team")
assert r.status_code == 200
destroy_ctfd(app)
def test_team_join_ratelimited():
"""Test that team joins are ratelimited"""
app = create_ctfd(user_mode="teams")
with app.app_context():
gen_user(app.db, name="user")
gen_team(app.db, name="team")
with login_as_user(app) as client:
r = client.get("/teams/join")
assert r.status_code == 200
with client.session_transaction() as sess:
data = {
"name": "team",
"password": "wrong_password",
"nonce": sess.get("nonce"),
}
for _ in range(10):
r = client.post("/teams/join", data=data)
data["password"] = "password"
for _ in range(10):
r = client.post("/teams/join", data=data)
assert r.status_code == 429
assert Users.query.filter_by(id=2).first().team_id is None
destroy_ctfd(app)
def test_teams_new_get():
"""Can a user get /teams/new"""
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
with login_as_user(app) as client:
r = client.get("/teams/new")
assert r.status_code == 200
destroy_ctfd(app)
def test_teams_new_post():
"""Can a user post /teams/new"""
app = create_ctfd(user_mode="teams")
with app.app_context():
gen_user(app.db, name="user")
with login_as_user(app) as client:
with client.session_transaction() as sess:
data = {
"name": "team",
"password": "password",
"nonce": sess.get("nonce"),
}
r = client.post("/teams/new", data=data)
assert r.status_code == 302
# You can't create a team with a duplicate name
r = client.post("/teams/new", data=data)
assert r.status_code == 403
# You can't create a team with an empty name
incorrect_data = data
incorrect_data["name"] = ""
r = client.post("/teams/new", data=incorrect_data)
assert r.status_code == 403
destroy_ctfd(app)
def test_teams_new_post_when_already_on_team():
"""Test that a user cannot create a new team while on a team"""
app = create_ctfd(user_mode="teams")
with app.app_context():
gen_user(app.db, name="user")
with login_as_user(app) as client:
with client.session_transaction() as sess:
data = {
"name": "team1",
"password": "password",
"nonce": sess.get("nonce"),
}
r = client.post("/teams/new", data=data)
assert r.status_code == 302
# Try to create another team while on a team
r = client.get("/teams/new")
assert r.status_code == 200
with client.session_transaction() as sess:
data = {
"name": "team2",
"password": "password",
"nonce": sess.get("nonce"),
}
r = client.post("/teams/join", data=data)
assert r.status_code == 403
user = Users.query.filter_by(name="user").first()
assert user.team.name == "team1"
destroy_ctfd(app)
def test_teams_from_admin_hidden():
"""Test that teams created by admins in /teams/new are hidden by default"""
app = create_ctfd(user_mode="teams")
with app.app_context():
gen_user(app.db, name="user")
with login_as_user(app) as client:
with client.session_transaction() as sess:
data = {
"name": "team_user",
"password": "password",
"nonce": sess.get("nonce"),
}
r = client.post("/teams/new", data=data)
assert r.status_code == 302
team = Teams.query.filter_by(name="team_user").first()
assert team.hidden == False
with login_as_user(app, "admin") as client:
with client.session_transaction() as sess:
data = {
"name": "team_admin",
"password": "password",
"nonce": sess.get("nonce"),
}
r = client.post("/teams/new", data=data)
assert r.status_code == 302
team = Teams.query.filter_by(name="team_admin").first()
assert team.hidden == True
destroy_ctfd(app)

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.utils import set_config
from CTFd.utils.scores import get_standings
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_challenge,
gen_flag,
gen_team,
gen_user,
login_as_user,
register_user,
)
def test_challenge_team_submit():
"""Is a user's solved challenge reflected by other team members"""
app = create_ctfd(user_mode="teams")
with app.app_context():
user = gen_user(app.db)
second_user = gen_user(app.db, name="user", email="second@examplectf.com")
team = gen_team(app.db)
user.team_id = team.id
second_user.team_id = team.id
team.members.append(user)
team.members.append(second_user)
gen_challenge(app.db)
gen_flag(app.db, 1)
app.db.session.commit()
with login_as_user(app, name="user_name") as client:
flag = {"challenge_id": 1, "submission": "flag"}
client.post("/api/v1/challenges/attempt", json=flag)
with login_as_user(app) as second_client:
flag = {"challenge_id": 1, "submission": "flag"}
r = second_client.post("/api/v1/challenges/attempt", json=flag)
assert r.json["data"]["status"] == "already_solved"
standings = get_standings()
assert standings[0].name == "team_name"
assert standings[0].score == 100
destroy_ctfd(app)
def test_anonymous_users_view_public_challenges_without_team():
"""Test that if challenges are public, users without team can still view them"""
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
gen_challenge(app.db)
with app.test_client() as client:
r = client.get("/challenges")
assert r.status_code == 302
assert r.location.startswith("/login")
set_config("challenge_visibility", "public")
with app.test_client() as client:
r = client.get("/challenges")
assert r.status_code == 200
with login_as_user(app) as client:
r = client.get("/challenges")
assert r.status_code == 302
assert r.location.startswith("/team")
destroy_ctfd(app)

396
tests/teams/test_fields.py Normal file
View File

@@ -0,0 +1,396 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import TeamFieldEntries, Teams, Users
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_field,
gen_team,
login_as_user,
register_user,
)
def test_new_fields_show_on_pages():
app = create_ctfd(user_mode="teams")
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(app.db, name="CustomField1", type="team")
with login_as_user(app) as client:
r = client.get("/teams/new")
assert "CustomField1" in r.get_data(as_text=True)
assert "CustomFieldDescription" in r.get_data(as_text=True)
r = client.get("/team")
assert "CustomField1" in r.get_data(as_text=True)
assert "CustomFieldDescription" in r.get_data(as_text=True)
r = client.patch(
"/api/v1/teams/me",
json={"fields": [{"field_id": 1, "value": "CustomFieldEntry"}]},
)
resp = r.get_json()
assert resp["success"] is True
assert resp["data"]["fields"][0]["value"] == "CustomFieldEntry"
assert resp["data"]["fields"][0]["description"] == "CustomFieldDescription"
assert resp["data"]["fields"][0]["name"] == "CustomField1"
assert resp["data"]["fields"][0]["field_id"] == 1
r = client.get("/team")
resp = r.get_data(as_text=True)
assert "CustomField1" in resp
assert "CustomFieldEntry" in resp
r = client.get("/teams/1")
resp = r.get_data(as_text=True)
assert "CustomField1" in resp
assert "CustomFieldEntry" in resp
destroy_ctfd(app)
def test_team_fields_required_on_creation():
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
gen_field(app.db, type="team")
with app.app_context():
with login_as_user(app) as client:
assert Teams.query.count() == 0
r = client.get("/teams/new")
resp = r.get_data(as_text=True)
assert "CustomField" in resp
assert "CustomFieldDescription" in resp
with client.session_transaction() as sess:
data = {
"name": "team",
"password": "password",
"nonce": sess.get("nonce"),
}
r = client.post("/teams/new", data=data)
assert "Please provide all required fields" in r.get_data(as_text=True)
assert Teams.query.count() == 0
with client.session_transaction() as sess:
data = {
"name": "team",
"password": "password",
"fields[1]": "CustomFieldEntry",
"nonce": sess.get("nonce"),
}
r = client.post("/teams/new", data=data)
assert r.status_code == 302
assert Teams.query.count() == 1
entry = TeamFieldEntries.query.filter_by(id=1).first()
assert entry.team_id == 1
assert entry.value == "CustomFieldEntry"
destroy_ctfd(app)
def test_team_fields_properties():
"""Test that custom fields for team can be set and editted"""
app = create_ctfd(user_mode="teams")
with app.app_context():
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,
)
register_user(app)
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
# 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")
resp = r.get_data(as_text=True)
assert "CustomField1" in resp
assert "CustomField2" in resp
assert "CustomField3" in resp
assert "CustomField4" not in resp
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"},
]
},
)
resp = r.get_json()
assert resp == {
"success": False,
"errors": {"fields": ["Field 'CustomField4' cannot be editted"]},
}
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
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
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)
def test_teams_boolean_checkbox_field():
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
gen_field(
app.db,
name="CustomField1",
type="team",
field_type="boolean",
required=False,
)
with login_as_user(app) as client:
r = client.get("/teams/new")
resp = r.get_data(as_text=True)
# We should have rendered a checkbox input
assert "checkbox" in resp
with client.session_transaction() as sess:
data = {
"name": "team",
"password": "password",
"nonce": sess.get("nonce"),
"fields[1]": "y",
}
client.post("/teams/new", data=data)
assert Teams.query.count() == 1
assert TeamFieldEntries.query.count() == 1
assert TeamFieldEntries.query.filter_by(id=1).first().value is True
with login_as_user(app) as client:
r = client.get("/team")
resp = r.get_data(as_text=True)
assert "CustomField1" in resp
assert "checkbox" in resp
r = client.patch(
"/api/v1/teams/me", json={"fields": [{"field_id": 1, "value": False}]}
)
assert r.status_code == 200
assert TeamFieldEntries.query.count() == 1
assert TeamFieldEntries.query.filter_by(id=1).first().value is False
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)

View File

@@ -0,0 +1,91 @@
from CTFd.models import Teams
from CTFd.utils.scores import get_standings, get_team_standings
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_challenge,
gen_flag,
gen_team,
gen_user,
login_as_user,
)
def setup_app(app):
user1 = gen_user(app.db, name="user1", email="user1@examplectf.com")
team1 = gen_team(app.db, name="team1", email="team1@examplectf.com")
user1.team_id = team1.id
team1.members.append(user1)
team1.hidden = True
user2 = gen_user(app.db, name="user2", email="user2@examplectf.com")
team2 = gen_team(app.db, name="team2", email="team2@examplectf.com")
user2.team_id = team2.id
team2.members.append(user2)
gen_challenge(app.db)
gen_flag(app.db, 1)
app.db.session.commit()
with login_as_user(app, name="user1") as client:
flag = {"challenge_id": 1, "submission": "flag"}
client.post("/api/v1/challenges/attempt", json=flag)
with login_as_user(app, name="user2") as client:
flag = {"challenge_id": 1, "submission": "flag"}
client.post("/api/v1/challenges/attempt", json=flag)
def test_standings():
app = create_ctfd(user_mode="teams")
with app.app_context():
setup_app(app)
standings = get_standings()
assert standings[0].name == "team2"
assert standings[0].score == 100
destroy_ctfd(app)
def test_team_standings():
app = create_ctfd(user_mode="teams")
with app.app_context():
setup_app(app)
team_standings = get_team_standings()
first_team = Teams.query.filter_by(id=team_standings[0].team_id).first_or_404()
assert first_team.name == "team2"
assert first_team.score == 100
def test_admin_standings():
app = create_ctfd(user_mode="teams")
with app.app_context():
setup_app(app)
standings = get_standings(admin=True)
assert standings[0].name == "team1"
assert standings[0].score == 100
def test_admin_team_standings():
app = create_ctfd(user_mode="teams")
with app.app_context():
setup_app(app)
team_standings = get_team_standings(admin=True)
first_team = Teams.query.filter_by(id=team_standings[0].team_id).first_or_404()
assert first_team.name == "team1"
assert first_team.score == 100

138
tests/teams/test_hints.py Normal file
View File

@@ -0,0 +1,138 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.utils.scores import get_standings
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_award,
gen_challenge,
gen_hint,
gen_team,
gen_user,
login_as_user,
)
def test_hint_team_unlock():
"""Is a user's unlocked hint reflected on other team members"""
app = create_ctfd(user_mode="teams")
with app.app_context():
user = gen_user(app.db)
second_user = gen_user(app.db, name="user", email="second@examplectf.com")
team = gen_team(app.db)
user.team_id = team.id
second_user.team_id = team.id
team.members.append(user)
team.members.append(second_user)
chal = gen_challenge(app.db)
gen_hint(app.db, chal.id, content="hint", cost=1, type="standard")
# Give the points to the user that doesn't unlock
# Users that unlock hints should be able to unlock but cost their team points
gen_award(app.db, user_id=3, team_id=team.id)
app.db.session.commit()
with login_as_user(app, name="user_name") as client:
# Assert that we don't see a hint
r = client.get("/api/v1/hints/1")
assert r.get_json()["data"].get("content") is None
# Unlock the hint
client.post("/api/v1/unlocks", json={"target": 1, "type": "hints"})
# Assert that we see a hint
r = client.get("/api/v1/hints/1")
assert r.get_json()["data"].get("content")
with login_as_user(app) as second_client:
# Assert that we see a hint
r = second_client.get("/api/v1/hints/1")
assert r.get_json()["data"].get("content")
# Assert that we can't double unlock
r = second_client.post(
"/api/v1/unlocks", json={"target": 1, "type": "hints"}
)
assert r.status_code == 400
assert (
r.get_json()["errors"]["target"]
== "You've already unlocked this target"
)
# Assert that we see a hint
r = second_client.get("/api/v1/hints/1")
assert r.json["data"]["content"] == "hint"
# Verify standings
# We start with 100 points from the award.
# We lose a point because we unlock successfully once
standings = get_standings()
assert standings[0].name == "team_name"
assert standings[0].score == 99
destroy_ctfd(app)
def test_hint_team_unlocking_without_points():
"""Test that teams cannot enter negative point valuations from unlocking hints"""
app = create_ctfd(user_mode="teams")
with app.app_context():
user = gen_user(app.db)
second_user = gen_user(app.db, name="user", email="second@examplectf.com")
team = gen_team(app.db)
user.team_id = team.id
second_user.team_id = team.id
team.members.append(user)
team.members.append(second_user)
chal = gen_challenge(app.db)
gen_hint(app.db, chal.id, content="hint", cost=1, type="standard")
app.db.session.commit()
with login_as_user(app, name="user_name") as client:
# Assert that we don't see a hint
r = client.get("/api/v1/hints/1")
assert r.get_json()["data"].get("content") is None
# Attempt to unlock the hint
r = client.post("/api/v1/unlocks", json={"target": 1, "type": "hints"})
assert r.status_code == 400
assert (
r.get_json()["errors"]["score"]
== "You do not have enough points to unlock this hint"
)
destroy_ctfd(app)
def test_teams_dont_prevent_other_teams_from_unlocking_hints():
"""Unlocks from one user don't affect other users"""
app = create_ctfd(user_mode="teams")
with app.app_context():
chal = gen_challenge(app.db)
gen_hint(app.db, chal.id, content="This is a hint", cost=1, type="standard")
team1 = gen_team(app.db, name="team1", email="team1@examplectf.com")
team2 = gen_team(app.db, name="team2", email="team2@examplectf.com")
# Give users points with an award
gen_award(app.db, user_id=team1.captain_id)
gen_award(app.db, user_id=team2.captain_id)
captain1 = team1.captain.name
captain2 = team2.captain.name
app.db.session.commit()
# First team unlocks hint
with login_as_user(app, name=captain1) as client:
r = client.get("/api/v1/hints/1")
assert r.status_code == 200
r = client.post("/api/v1/unlocks", json={"target": 1, "type": "hints"})
assert r.status_code == 200
r = client.get("/api/v1/hints/1")
assert r.status_code == 200
# Second team unlocks hint
with login_as_user(app, name=captain2) as client:
r = client.get("/api/v1/hints/1")
assert r.status_code == 200
r = client.post("/api/v1/unlocks", json={"target": 1, "type": "hints"})
assert r.status_code == 200
r = client.get("/api/v1/hints/1")
assert r.status_code == 200
destroy_ctfd(app)

View File

@@ -0,0 +1,81 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from freezegun import freeze_time
from CTFd.exceptions import TeamTokenExpiredException, TeamTokenInvalidException
from CTFd.models import Teams, Users
from CTFd.utils import set_config
from tests.helpers import create_ctfd, destroy_ctfd, gen_team, gen_user, login_as_user
def test_team_invite_codes():
app = create_ctfd(user_mode="teams")
with app.app_context():
team1 = gen_team(app.db, name="team1", email="team1@examplectf.com")
with freeze_time("2017-10-7 00:00:00"):
invite_code = team1.get_invite_code()
team = Teams.load_invite_code(invite_code)
assert team.id == team1.id
with freeze_time("2017-10-8 00:00:01"):
try:
team = Teams.load_invite_code(invite_code)
except TeamTokenExpiredException:
# This token should be expired and we shouldn't get a team object back
pass
else:
print("Token should have expired")
raise Exception
# Change team's password
team.password = "new_test_password"
app.db.session.commit()
with freeze_time("2017-10-7 00:00:00"):
try:
team = Teams.load_invite_code(invite_code)
except TeamTokenInvalidException:
pass
else:
print("Token should have been invalidated by password change")
raise Exception
destroy_ctfd(app)
def test_api_user_facing_invite_tokens():
app = create_ctfd(user_mode="teams")
with app.app_context():
team1 = gen_team(app.db, name="team1", email="team1@examplectf.com")
user = team1.captain
with login_as_user(app, name=user.name) as captain:
r = captain.post("/api/v1/teams/me/members", json="")
invite_code = r.get_json()["data"]["code"]
assert invite_code
new_user = gen_user(app.db)
with login_as_user(app, name=new_user.name) as user:
url = f"/teams/invite?code={invite_code}"
user.get(url)
with user.session_transaction() as sess:
data = {
"nonce": sess.get("nonce"),
}
r = user.post(url, data=data)
assert r.status_code == 302
assert r.location.endswith("/challenges")
assert Users.query.filter_by(id=new_user.id).first().team_id == team1.id
# Test team size limits
set_config("team_size", 1)
new_user2 = gen_user(app.db, name="new_user2", email="new_user2@examplectf.com")
with login_as_user(app, name=new_user2.name) as user:
url = f"/teams/invite?code={invite_code}"
user.get(url)
with user.session_transaction() as sess:
data = {
"nonce": sess.get("nonce"),
}
r = user.post(url, data=data)
assert r.status_code == 403
assert "has already reached the team size limit" in r.get_data(as_text=True)
destroy_ctfd(app)

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.utils.scores import get_standings
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_challenge,
gen_flag,
gen_team,
gen_user,
login_as_user,
)
def test_scoreboard_team_score():
"""Is a user's submitted flag reflected on the team's score on /scoreboard"""
app = create_ctfd(user_mode="teams")
with app.app_context():
user = gen_user(app.db, name="user")
team = gen_team(app.db)
user.team_id = team.id
team.members.append(user)
gen_challenge(app.db)
gen_flag(app.db, 1)
app.db.session.commit()
with login_as_user(app) as client:
flag = {"challenge_id": 1, "submission": "flag"}
client.post("/api/v1/challenges/attempt", json=flag)
standings = get_standings()
assert standings[0].name == "team_name"
assert standings[0].score == 100
destroy_ctfd(app)

251
tests/teams/test_teams.py Normal file
View File

@@ -0,0 +1,251 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Teams, Users
from CTFd.utils import set_config
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_award,
gen_team,
gen_user,
login_as_user,
register_user,
)
def test_teams_get():
"""Can a user get /teams"""
app = create_ctfd(user_mode="teams")
with app.app_context():
with app.test_client() as client:
set_config("account_visibility", "public")
r = client.get("/teams")
assert r.status_code == 200
set_config("account_visibility", "private")
r = client.get("/teams")
assert r.status_code == 302
set_config("account_visibility", "admins")
r = client.get("/teams")
assert r.status_code == 404
destroy_ctfd(app)
def test_accessing_hidden_teams():
"""Hidden teams should not give any data from /teams or /api/v1/teams"""
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
register_user(app, name="visible_user", email="visible_user@examplectf.com")
with login_as_user(app, name="visible_user") as client:
user = Users.query.filter_by(id=2).first()
team = gen_team(app.db, name="visible_team", hidden=True)
team.members.append(user)
user.team_id = team.id
app.db.session.commit()
assert client.get("/teams/1").status_code == 404
assert client.get("/api/v1/teams/1").status_code == 404
assert client.get("/api/v1/teams/1/solves").status_code == 404
assert client.get("/api/v1/teams/1/fails").status_code == 404
assert client.get("/api/v1/teams/1/awards").status_code == 404
destroy_ctfd(app)
def test_hidden_teams_visibility():
"""Hidden teams should not show up on /teams or /api/v1/teams or /api/v1/scoreboard"""
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
with login_as_user(app) as client:
user = Users.query.filter_by(id=2).first()
user_id = user.id
team = gen_team(app.db, name="visible_team", hidden=True)
team_id = team.id
team_name = team.name
team.members.append(user)
user.team_id = team.id
app.db.session.commit()
r = client.get("/teams")
response = r.get_data(as_text=True)
# Only search in body content
body_start = response.find("<body>")
body_end = response.find("</body>")
response = response[body_start:body_end]
assert team_name not in response
r = client.get("/api/v1/teams")
response = r.get_json()
assert team_name not in response
gen_award(app.db, user_id, team_id=team_id)
r = client.get("/scoreboard")
response = r.get_data(as_text=True)
# Only search in body content
body_start = response.find("<body>")
body_end = response.find("</body>")
response = response[body_start:body_end]
assert team_name not in response
r = client.get("/api/v1/scoreboard")
response = r.get_json()
assert team_name not in response
# Team should re-appear after disabling hiding
# Use an API call to cause a cache clear
with login_as_user(app, name="admin") as admin:
r = admin.patch("/api/v1/teams/1", json={"hidden": False})
assert r.status_code == 200
r = client.get("/teams")
response = r.get_data(as_text=True)
assert team_name in response
r = client.get("/api/v1/teams")
response = r.get_data(as_text=True)
assert team_name in response
r = client.get("/api/v1/scoreboard")
response = r.get_data(as_text=True)
assert team_name in response
destroy_ctfd(app)
def test_teams_get_user_mode():
"""Can a user get /teams if user mode"""
app = create_ctfd(user_mode="users")
with app.app_context():
register_user(app)
with login_as_user(app) as client:
r = client.get("/teams")
assert r.status_code == 404
destroy_ctfd(app)
def test_team_get():
"""Can a user get /team"""
app = create_ctfd(user_mode="teams")
with app.app_context():
user = gen_user(app.db)
team = gen_team(app.db)
team.members.append(user)
user.team_id = team.id
app.db.session.commit()
with login_as_user(app, name="user_name", password="password") as client:
r = client.get("/team")
assert r.status_code == 200
destroy_ctfd(app)
def test_teams_id_get():
"""Can a user get /teams/<int:team_id>"""
app = create_ctfd(user_mode="teams")
with app.app_context():
user = gen_user(app.db)
team = gen_team(app.db)
team.members.append(user)
user.team_id = team.id
app.db.session.commit()
with login_as_user(app, name="user_name", password="password") as client:
r = client.get("/teams/1")
assert r.status_code == 200
destroy_ctfd(app)
def test_team_size_limit():
"""Only team_size amount of members can join a team"""
app = create_ctfd(user_mode="teams")
with app.app_context():
set_config("team_size", 1)
# Create a team with only one member
team = gen_team(app.db, member_count=1)
team_id = team.id
register_user(app)
with login_as_user(app) as client:
r = client.get("/teams/join")
assert r.status_code == 200
# User should be blocked from joining
with client.session_transaction() as sess:
data = {
"name": "team_name",
"password": "password",
"nonce": sess.get("nonce"),
}
r = client.post("/teams/join", data=data)
resp = r.get_data(as_text=True)
assert len(Teams.query.filter_by(id=team_id).first().members) == 1
assert "already reached the team size limit of 1" in resp
# Can the user join after the size has been bumped
set_config("team_size", 2)
r = client.post("/teams/join", data=data)
resp = r.get_data(as_text=True)
assert len(Teams.query.filter_by(id=team_id).first().members) == 2
destroy_ctfd(app)
def test_num_teams_limit():
"""Only num_teams teams can be created"""
app = create_ctfd(user_mode="teams")
with app.app_context():
set_config("num_teams", 1)
# Create a team
gen_team(app.db, member_count=1)
register_user(app)
with login_as_user(app) as client:
r = client.get("/teams/new")
assert r.status_code == 403
# team should be blocked from creation
with client.session_transaction() as sess:
data = {
"name": "team1",
"password": "password",
"nonce": sess.get("nonce"),
}
r = client.post("/teams/new", data=data)
resp = r.get_data(as_text=True)
assert Teams.query.count() == 1
assert "Reached the maximum number of teams" in resp
# Can the team be created after the num has been bumped
set_config("num_teams", 2)
r = client.post("/teams/new", data=data)
resp = r.get_data(as_text=True)
assert Teams.query.count() == 2
destroy_ctfd(app)
def test_team_creation_disable():
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
with login_as_user(app) as client:
# Team creation page should be available
r = client.get("/teams/new")
assert r.status_code == 200
# Disable team creation in config
set_config("team_creation", False)
# Can't access the public team creation page
r = client.get("/teams/new")
assert r.status_code == 403
# User should be blocked from creating teams as well
with client.session_transaction() as sess:
data = {
"name": "team_name",
"password": "password",
"nonce": sess.get("nonce"),
}
r = client.post("/teams/new", data=data)
assert r.status_code == 403
destroy_ctfd(app)