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/api/__init__.py Normal file
View File

105
tests/api/test_tokens.py Normal file
View File

@@ -0,0 +1,105 @@
import datetime
import os
from io import BytesIO
from CTFd.exceptions import UserNotFoundException, UserTokenExpiredException
from CTFd.models import Files, Tokens, Users
from CTFd.utils.security.auth import generate_user_token, lookup_user_token
from tests.helpers import create_ctfd, destroy_ctfd, gen_token, gen_user
def test_generate_user_token():
app = create_ctfd()
with app.app_context():
user = gen_user(app.db)
token = generate_user_token(user, expiration=None)
assert token.user_id == user.id
assert token.expiration > datetime.datetime.utcnow()
assert Tokens.query.count() == 1
destroy_ctfd(app)
def test_lookup_user_token():
app = create_ctfd()
with app.app_context():
user = gen_user(app.db)
# Good Token
token = gen_token(app.db, user_id=user.id)
user = lookup_user_token(token.value)
assert user.id == token.user_id
# Expired Token
expiration = datetime.datetime.utcnow() + datetime.timedelta(days=-1)
token = gen_token(app.db, user_id=user.id, expiration=expiration)
try:
lookup_user_token(token.value)
except UserTokenExpiredException:
pass
except Exception as e:
raise e
# Nonexistant token
try:
lookup_user_token("wat")
except UserNotFoundException:
pass
except Exception as e:
raise e
destroy_ctfd(app)
def test_user_token_access():
app = create_ctfd()
with app.app_context():
with app.test_client() as client:
r = client.get("/api/v1/users/me", json="")
assert r.status_code == 403
with app.test_client() as client:
user = gen_user(app.db, name="user2", email="user2@examplectf.com")
expiration = datetime.datetime.utcnow() + datetime.timedelta(days=-1)
token = generate_user_token(user, expiration=expiration)
headers = {"Authorization": "token " + token.value}
r = client.get("/api/v1/users/me", headers=headers, json="")
assert r.status_code == 401
with app.test_client() as client:
headers = {"Authorization": "token invalid_token"}
r = client.get("/api/v1/users/me", headers=headers, json="")
assert r.status_code == 401
with app.test_client() as client:
user = gen_user(app.db, name="user1", email="user1@examplectf.com")
token = generate_user_token(user, expiration=None)
headers = {"Authorization": "token " + token.value}
r = client.get("/api/v1/users/me", headers=headers, json="")
assert r.status_code == 200
resp = r.get_json()
assert resp["data"]["email"] == "user1@examplectf.com"
assert resp["data"]["name"] == "user1"
destroy_ctfd(app)
def test_token_api_file_upload():
"""Test that tokens can upload files with multipart/form-data content type"""
app = create_ctfd()
with app.app_context():
admin = Users.query.filter_by(id=1).first()
token = generate_user_token(admin, expiration=None)
with app.test_client() as client:
headers = {"Authorization": "token " + token.value}
r = client.post(
"/api/v1/files",
headers=headers,
content_type="multipart/form-data",
data={
"file": (BytesIO(b"test file content"), "test.txt"),
},
)
assert r.status_code == 200
f = Files.query.filter_by(id=1).first()
filepath = os.path.join(app.config["UPLOAD_FOLDER"] + "/" + f.location)
with open(filepath) as f:
assert f.read() == "test file content"
os.remove(filepath)
destroy_ctfd(app)

0
tests/api/v1/__init__.py Normal file
View File

View File

@@ -0,0 +1,171 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Users
from CTFd.utils import set_config
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_challenge,
gen_solve,
login_as_user,
register_user,
)
def test_api_challenges_admins_can_bypass_requirements():
"""Test that admins can bypass requirements checks with admin capabilities and view-admin"""
app = create_ctfd()
with app.app_context():
# Create challenges
prereq_id = gen_challenge(app.db).id
chal_obj = gen_challenge(app.db)
chal_obj.requirements = {"prerequisites": [prereq_id]}
register_user(app)
# Confirm that regular users cannot see prerequisites
with login_as_user(app) as client:
# Locked challenges aren't shown
r = client.get("/api/v1/challenges")
assert r.status_code == 200
data = r.get_json()["data"]
assert len(data) == 1
assert data[0]["id"] == 1
# Not even with tricks
r = client.get("/api/v1/challenges?view=admin")
assert r.status_code == 200
data = r.get_json()["data"]
assert len(data) == 1
assert data[0]["id"] == 1
# Not even with forced browsing
r = client.get("/api/v1/challenges/2")
assert r.status_code == 403
# Confirm that admins
with login_as_user(app, name="admin") as admin:
# Admins see as regular users
r = admin.get("/api/v1/challenges")
assert r.status_code == 200
data = r.get_json()["data"]
assert len(data) == 1
assert data[0]["id"] == 1
# Now admins can see all challenges
r = admin.get("/api/v1/challenges?view=admin")
assert r.status_code == 200
data = r.get_json()["data"]
assert len(data) == 2
assert data[0]["id"] == 1
assert data[1]["id"] == 2
# Admins can force browse to challenges
r = admin.get("/api/v1/challenges/2")
assert r.status_code == 200
assert r.get_json()["data"]
destroy_ctfd(app)
def test_api_challenges_challenge_with_requirements():
"""Does the challenge list API show challenges with requirements met?"""
app = create_ctfd()
with app.app_context():
prereq_id = gen_challenge(app.db).id
chal_obj = gen_challenge(app.db)
chal_obj.requirements = {"prerequisites": [prereq_id]}
chal_id = chal_obj.id
# Create a new user which will solve the prerequisite
register_user(app)
# Confirm that only the prerequisite challenge is listed initially
with login_as_user(app) as client:
r = client.get("/api/v1/challenges")
assert r.status_code == 200
(chal_data,) = r.get_json()["data"]
assert chal_data["id"] == prereq_id
# Generate a solve and then confirm the second challenge is visible
gen_solve(app.db, user_id=2, challenge_id=prereq_id)
with login_as_user(app) as client:
r = client.get("/api/v1/challenges")
assert r.status_code == 200
data = r.get_json()["data"]
assert len(data) == 2
chal_ids = {c["id"] for c in r.get_json()["data"]}
assert chal_ids == {prereq_id, chal_id}
destroy_ctfd(app)
def test_api_challenges_challenge_with_requirements_hidden_user():
"""Does the challenge list API show gated challenges to a hidden user?"""
app = create_ctfd()
with app.app_context():
prereq_id = gen_challenge(app.db).id
chal_obj = gen_challenge(app.db)
chal_obj.requirements = {"prerequisites": [prereq_id]}
chal_id = chal_obj.id
# Create a new user which will solve the prerequisite and hide them
register_user(app)
Users.query.get(2).hidden = True
app.db.session.commit()
# Confirm that only the prerequisite challenge is listed initially
with login_as_user(app) as client:
r = client.get("/api/v1/challenges")
assert r.status_code == 200
(chal_data,) = r.get_json()["data"]
assert chal_data["id"] == prereq_id
# Generate a solve and then confirm the second challenge is visible
gen_solve(app.db, user_id=2, challenge_id=prereq_id)
with login_as_user(app) as client:
r = client.get("/api/v1/challenges")
assert r.status_code == 200
data = r.get_json()["data"]
assert len(data) == 2
chal_ids = {c["id"] for c in r.get_json()["data"]}
assert chal_ids == {prereq_id, chal_id}
destroy_ctfd(app)
def test_api_challenges_challenge_with_requirements_banned_user():
"""Does the challenge list API show gated challenges to a banned user?"""
app = create_ctfd()
with app.app_context():
prereq_id = gen_challenge(app.db).id
chal_obj = gen_challenge(app.db)
chal_obj.requirements = {"prerequisites": [prereq_id]}
# Create a new user which will solve the prerequisite and ban them
register_user(app)
Users.query.get(2).banned = True
app.db.session.commit()
# Generate a solve just in case and confirm the API 403s
gen_solve(app.db, user_id=2, challenge_id=prereq_id)
with login_as_user(app) as client:
assert client.get("/api/v1/challenges").status_code == 403
destroy_ctfd(app)
def test_api_challenges_challenge_with_requirements_no_user():
"""Does the challenge list API show gated challenges to the public?"""
app = create_ctfd()
with app.app_context():
set_config("challenge_visibility", "public")
prereq_id = gen_challenge(app.db).id
chal_obj = gen_challenge(app.db)
chal_obj.requirements = {"prerequisites": [prereq_id]}
# Create a new user which will solve the prerequisite
register_user(app)
# Confirm that only the prerequisite challenge is listed publicly
with app.test_client() as client:
r = client.get("/api/v1/challenges")
assert r.status_code == 200
initial_data = r.get_json()["data"]
(chal_data,) = initial_data
assert chal_data["id"] == prereq_id
# Fix up the solve count for later comparison with `initial_data`
chal_data["solves"] += 1
# Generate a solve and then confirm the response is unchanged
gen_solve(app.db, user_id=2, challenge_id=prereq_id)
with app.test_client() as client:
r = client.get("/api/v1/challenges")
assert r.status_code == 200
assert r.get_json()["data"] == initial_data
destroy_ctfd(app)

View File

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Users
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_challenge,
login_as_user,
register_user,
simulate_user_activity,
)
def test_api_statistics_score_distribution():
app = create_ctfd()
with app.app_context():
# Handle zero data case
client = login_as_user(app, name="admin", password="password")
r = client.get("/api/v1/statistics/scores/distribution")
resp = r.get_json()
assert resp["data"]["brackets"] == {}
# Add user data
register_user(app)
user = Users.query.filter_by(email="user@examplectf.com").first()
simulate_user_activity(app.db, user=user)
# Test again
r = client.get("/api/v1/statistics/scores/distribution")
resp = r.get_json()
assert resp["data"]["brackets"]
destroy_ctfd(app)
def test_browse_admin_submissions():
"""Test that an admin can create a challenge properly"""
app = create_ctfd()
with app.app_context():
gen_challenge(db=app.db)
admin = login_as_user(app, name="admin", password="password")
r = admin.get(
"/api/v1/statistics/challenges/category?function=sum&target=value"
)
resp = r.get_json()
assert resp["data"]
assert r.status_code == 200
destroy_ctfd(app)

View File

View File

@@ -0,0 +1,90 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Users
from CTFd.utils import set_config
from tests.helpers import create_ctfd, destroy_ctfd, gen_award, gen_team, login_as_user
def test_api_team_place_score_hidden_if_scores_hidden():
"""/api/v1/teams/me should not reveal team place if scores aren't visible"""
app = create_ctfd(user_mode="teams")
with app.app_context():
gen_team(app.db)
app.db.session.commit()
gen_award(app.db, user_id=2, team_id=1)
u = Users.query.filter_by(id=2).first()
with login_as_user(app, name=u.name) as client:
r = client.get("/api/v1/teams/me", json="")
resp = r.get_json()
assert resp["data"]["place"] == "1st"
assert resp["data"]["score"] == 100
set_config("score_visibility", "hidden")
with login_as_user(app, name=u.name) as client:
r = client.get("/api/v1/teams/me", json="")
resp = r.get_json()
# Teams can see their own score but they cannot see their place
# This is because a team can always sum up their own score but
# they cannot determine their place without social information
assert resp["data"]["place"] is None
assert resp["data"]["score"] == 100
set_config("score_visibility", "admins")
with login_as_user(app, name=u.name) as client:
r = client.get("/api/v1/teams/me", json="")
resp = r.get_json()
# The same behavior as above applies even under admins only score mode
# The rationale is the same. Teams can always sum their own score
assert resp["data"]["place"] is None
assert resp["data"]["score"] == 100
with login_as_user(app, name="admin") as client:
r = client.get("/api/v1/teams/1", json="")
resp = r.get_json()
print(resp)
assert resp["data"]["place"] == "1st"
assert resp["data"]["score"] == 100
destroy_ctfd(app)
def test_api_public_team_place_score_hidden_if_scores_hidden():
"""/api/v1/teams/<team_id> should not reveal team place if scores aren't visible"""
app = create_ctfd(user_mode="teams")
with app.app_context():
gen_team(app.db)
app.db.session.commit()
gen_award(app.db, user_id=2, team_id=1)
u = Users.query.filter_by(id=2).first()
with login_as_user(app, name=u.name) as client:
r = client.get("/api/v1/teams/1", json="")
resp = r.get_json()
assert resp["data"]["place"] == "1st"
assert resp["data"]["place"] is not None
set_config("score_visibility", "hidden")
with login_as_user(app, name=u.name) as client:
r = client.get("/api/v1/teams/1", json="")
resp = r.get_json()
assert resp["data"]["place"] is None
assert resp["data"]["score"] is None
set_config("score_visibility", "admins")
with login_as_user(app, name=u.name) as client:
r = client.get("/api/v1/teams/1", json="")
resp = r.get_json()
assert resp["data"]["place"] is None
assert resp["data"]["score"] is None
with login_as_user(app, name="admin") as client:
r = client.get("/api/v1/teams/1", json="")
resp = r.get_json()
assert resp["data"]["place"] == "1st"
assert resp["data"]["score"] is not None
destroy_ctfd(app)

View File

@@ -0,0 +1,152 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Awards, Solves, Submissions, Unlocks, Users
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_team,
gen_user,
login_as_user,
simulate_user_activity,
)
def test_api_team_get_members():
"""Can a user get /api/v1/teams/<team_id>/members only if admin"""
app = create_ctfd(user_mode="teams")
with app.app_context():
gen_team(app.db)
app.db.session.commit()
gen_user(app.db, name="user_name")
with login_as_user(app, name="user_name") as client:
r = client.get("/api/v1/teams/1/members", json="")
assert r.status_code == 403
with login_as_user(app, name="admin") as client:
r = client.get("/api/v1/teams/1/members", json="")
assert r.status_code == 200
resp = r.get_json()
# The following data is sorted b/c in Postgres data isn't necessarily returned ordered.
assert sorted(resp["data"]) == sorted([2, 3, 4, 5])
destroy_ctfd(app)
def test_api_team_remove_members():
"""Can a user remove /api/v1/teams/<team_id>/members only if admin"""
app = create_ctfd(user_mode="teams")
with app.app_context():
team = gen_team(app.db)
assert len(team.members) == 4
app.db.session.commit()
gen_user(app.db, name="user1")
with login_as_user(app, name="user1") as client:
r = client.delete("/api/v1/teams/1/members", json={"user_id": 2})
assert r.status_code == 403
with login_as_user(app, name="admin") as client:
r = client.delete("/api/v1/teams/1/members", json={"user_id": 2})
assert r.status_code == 200
resp = r.get_json()
# The following data is sorted b/c in Postgres data isn't necessarily returned ordered.
assert sorted(resp["data"]) == sorted([3, 4, 5])
r = client.delete("/api/v1/teams/1/members", json={"user_id": 2})
resp = r.get_json()
assert "User is not part of this team" in resp["errors"]["id"]
assert r.status_code == 400
destroy_ctfd(app)
def test_api_removing_members_deletes_information():
"""If an admin removes a user, their score information should also be removed"""
app = create_ctfd(user_mode="teams")
with app.app_context():
team = gen_team(app.db)
assert len(team.members) == 4
app.db.session.commit()
user = Users.query.filter_by(id=2).first()
simulate_user_activity(app.db, user)
assert Solves.query.filter_by(user_id=2).count() == 1
assert Submissions.query.filter_by(user_id=2).count() == 6
assert Awards.query.filter_by(user_id=2).count() == 1
assert Unlocks.query.filter_by(user_id=2).count() == 1
with login_as_user(app, name="admin") as client:
r = client.delete("/api/v1/teams/1/members", json={"user_id": 2})
assert r.status_code == 200
user = Users.query.filter_by(id=2).first()
assert Solves.query.filter_by(user_id=2).count() == 0
assert Submissions.query.filter_by(user_id=2).count() == 0
assert Awards.query.filter_by(user_id=2).count() == 0
assert Unlocks.query.filter_by(user_id=2).count() == 0
destroy_ctfd(app)
def test_api_admin_can_change_captain():
"""Can admins/captains change captains for teams"""
app = create_ctfd(user_mode="teams")
with app.app_context():
user1 = gen_user(app.db, name="user1", email="user1@examplectf.com") # ID 2
user2 = gen_user(app.db, name="user2", email="user2@examplectf.com") # ID 3
team = gen_team(app.db)
team.members.append(user1)
team.members.append(user2)
team.captain_id = 2
user1.team_id = team.id
user2.team_id = team.id
app.db.session.commit()
# I am not the captain
with login_as_user(app, name="user2") as client:
r = client.patch("/api/v1/teams/1", json={"captain_id": 3})
assert r.status_code == 403
# Look at me, I'm the captain now
with login_as_user(app, name="user1") as client:
r = client.patch("/api/v1/teams/1", json={"captain_id": 3})
# We should still receive a 403 because admins are the only people who can change captains for specific teams
assert r.status_code == 403
# Escalate to admin
with login_as_user(app, name="admin") as client:
r = client.patch("/api/v1/teams/1", json={"captain_id": 3})
resp = r.get_json()
assert resp["data"]["captain_id"] == 3
assert r.status_code == 200
destroy_ctfd(app)
def test_api_users_can_change_captain_on_self_team():
"""Can admins/captains change captains for their own team"""
app = create_ctfd(user_mode="teams")
with app.app_context():
user1 = gen_user(app.db, name="user1", email="user1@examplectf.com") # ID 2
user2 = gen_user(app.db, name="user2", email="user2@examplectf.com") # ID 3
team = gen_team(app.db)
team.members.append(user1)
team.members.append(user2)
team.captain_id = 2
user1.team_id = team.id
user2.team_id = team.id
app.db.session.commit()
# I am not the captain
with login_as_user(app, name="user2") as client:
r = client.patch("/api/v1/teams/me", json={"captain_id": 3})
assert r.status_code == 403
# Look at me, I'm the captain now
with login_as_user(app, name="user1") as client:
r = client.patch("/api/v1/teams/me", json={"captain_id": 3})
resp = r.get_json()
assert resp["data"]["captain_id"] == 3
assert r.status_code == 200
destroy_ctfd(app)

View File

@@ -0,0 +1,92 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Teams, Users, db
from CTFd.utils.crypto import verify_password
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_team,
login_as_user,
register_user,
)
def test_api_can_query_by_team_emails():
"""Can an admin user query /api/v1/teams using a teams's email address"""
app = create_ctfd(user_mode="teams")
with app.app_context():
gen_team(app.db, email="team@findme.com")
register_user(app, name="testuser", email="user@findme.com")
with login_as_user(app, "testuser") as client:
r = client.get("/api/v1/teams?field=email&q=findme", json=True)
assert r.status_code == 400
assert r.get_json()["errors"].get("field")
with login_as_user(app, "admin") as client:
r = client.get("/api/v1/teams?field=email&q=findme", json=True)
assert r.status_code == 200
assert r.get_json()["data"][0]["id"] == 1
assert r.get_json()["data"][0]["name"] == "team_name"
destroy_ctfd(app)
def test_api_team_can_update_password_if_none_not_if_set():
app = create_ctfd(user_mode="teams")
with app.app_context():
# Create a user with a null password. Use raw SQL to bypass SQLAlchemy validates
gen_team(app.db, name="testteam", email="team@examplectf.com")
db.session.execute("UPDATE teams SET password=NULL WHERE name='testteam'")
team = Teams.query.filter_by(id=1).first()
db.session.commit()
assert team.password is None
# Login and test that we are authed
captain = Users.query.filter_by(id=2).first()
normal_user = Users.query.filter_by(id=3).first()
with login_as_user(app, captain.name) as client:
r = client.get("/api/v1/teams/me", json=True)
assert r.get_json()["data"]["id"] == team.id
assert r.status_code == 200
# Patch the team's password from NULL
team = Teams.query.filter_by(name="testteam").first()
assert team.password is None
data = {"password": "12345", "confirm": "password"}
r = client.patch("/api/v1/teams/me", json=data)
assert r.status_code == 200
# Verify password is now set
team = Teams.query.filter_by(name="testteam").first()
assert verify_password(plaintext="12345", ciphertext=team.password)
# Verify that password cannot be changed without valid password
data = {"password": "noset", "confirm": "noset"}
r = client.patch("/api/v1/teams/me", json=data)
resp = r.get_json()
assert resp["errors"]["confirm"] == ["Your previous password is incorrect"]
assert r.status_code == 400
# Verify that a normal user cannot change the team password
with login_as_user(app, normal_user.name) as client:
# Try changing the password for the team
data = {"password": "newpassword", "confirm": "12345"}
r = client.patch("/api/v1/teams/me", json=data)
assert r.status_code == 403
# Verify that team password has not changed
team = Teams.query.filter_by(name="testteam").first()
assert verify_password(plaintext="12345", ciphertext=team.password)
# Create a new team
new_team = gen_team(app.db, name="newteam", email="newteam@examplectf.com")
new_captain = Users.query.filter_by(id=new_team.captain_id).first()
# Verify that the captain from the new team cannot change the password of the original team
with login_as_user(app, new_captain.name) as client:
data = {"password": "newpassword", "confirm": "12345"}
r = client.patch("/api/v1/teams/1", json=data)
assert r.status_code == 403
# Verify that old test team password has not changed
team = Teams.query.filter_by(name="testteam").first()
assert verify_password(plaintext="12345", ciphertext=team.password)

121
tests/api/v1/test_awards.py Normal file
View File

@@ -0,0 +1,121 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Awards
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_award,
gen_team,
login_as_user,
register_user,
)
def test_api_awards_access_non_admin():
"""Can a user post /api/v1/awards if not admin"""
app = create_ctfd()
with app.app_context():
with app.test_client() as client:
r = client.post("/api/v1/awards", json="")
assert r.status_code == 403
# test_api_award_get_non_admin
"""Can a user get /api/v1/awards/<award_id> if not admin"""
r = client.get("/api/v1/awards/1", json="")
assert r.status_code == 403
# test_api_award_delete_non_admin
"""Can a user delete /api/v1/awards/<award_id> if not admin"""
r = client.delete("/api/v1/awards/1", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_awards_post_admin():
"""Can a user post /api/v1/awards if admin"""
app = create_ctfd()
with app.app_context():
register_user(app)
with login_as_user(app, "admin") as client:
r = client.post(
"/api/v1/awards",
json={
"name": "Name",
"value": "100",
"category": "Cate",
"description": "Desc",
"user_id": 2,
},
)
assert r.status_code == 200
assert r.get_json()["success"] is True
r = client.post("/api/v1/awards", json="")
assert r.status_code == 400
destroy_ctfd(app)
def test_api_awards_post_admin_teams_mode():
"""Can a user post /api/v1/awards if admin in team mode"""
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
with login_as_user(app, "admin") as client:
r = client.post(
"/api/v1/awards",
json={
"name": "Name",
"value": "100",
"category": "Cate",
"description": "Desc",
"user_id": 2,
},
)
# This should fail because the user doesn't have a team
assert r.status_code == 400
assert "team_id" in r.get_json()["errors"].keys()
assert r.get_json()["success"] is False
gen_team(app.db)
r = client.post(
"/api/v1/awards",
json={
"name": "Name",
"value": "100",
"category": "Cate",
"description": "Desc",
"user_id": 3,
},
)
# This should pass as we should auto determine the user's team
assert r.status_code == 200
assert r.get_json()["success"] is True
award = Awards.query.filter_by(id=1).first()
assert award.user_id == 3
assert award.team_id == 1
destroy_ctfd(app)
def test_api_award_get_admin():
"""Can a user get /api/v1/awards/<award_id> if admin"""
app = create_ctfd()
with app.app_context():
gen_award(app.db, 1)
with login_as_user(app, "admin") as client:
r = client.get("/api/v1/awards/1", json="")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_award_delete_admin():
"""Can a user delete /api/v1/awards/<award_id> if admin"""
app = create_ctfd()
with app.app_context():
gen_award(app.db, 1)
with login_as_user(app, "admin") as client:
r = client.delete("/api/v1/awards/1", json="")
assert r.status_code == 200
destroy_ctfd(app)

View File

@@ -0,0 +1,122 @@
from CTFd.models import Brackets, Users
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_bracket,
login_as_user,
register_user,
)
def test_brackets_get_api():
"""Test that brackets API GET endpiont is behaving propertly"""
app = create_ctfd()
with app.app_context():
gen_bracket(app.db, name="players1")
with app.test_client() as client:
client.get("/register")
with client.session_transaction() as sess:
data = {
"name": "user",
"email": "user@examplectf.com",
"password": "password",
"bracket_id": 1,
"nonce": sess.get("nonce"),
}
client.post("/register", data=data)
client = login_as_user(app, raise_for_error=True)
r = client.get("/api/v1/brackets?type=users")
resp = r.get_json()
print(resp)
assert r.status_code == 200
assert resp["data"][0]["name"] == "players1"
assert resp["data"][0]["description"] == "players who are part of the test"
destroy_ctfd(app)
def test_brackets_post_api():
"""Test that brackets API POST endpiont is behaving propertly"""
app = create_ctfd()
with app.app_context():
data = {
"name": "testplayers",
"description": "Test players bracket",
"type": "users",
}
register_user(app)
with login_as_user(app) as client:
r = client.post("/api/v1/brackets", json=data)
assert r.status_code == 403
assert Brackets.query.count() == 0
with login_as_user(app, name="admin") as client:
r = client.post("/api/v1/brackets", json=data)
assert r.status_code == 200
assert Brackets.query.count() == 1
destroy_ctfd(app)
def test_brackets_patch_api():
"""Test that brackets API PATCH endpiont is behaving propertly"""
app = create_ctfd()
with app.app_context():
gen_bracket(app.db, name="players1")
assert Brackets.query.count() == 1
register_user(app, bracket_id=1)
with login_as_user(app) as client:
r = client.patch("/api/v1/brackets/1", json={"name": "newplayers"})
assert r.status_code == 403
assert Brackets.query.filter_by(id=1).first().name == "players1"
with login_as_user(app, name="admin") as client:
r = client.patch("/api/v1/brackets/1", json={"name": "newplayers"})
assert r.status_code == 200
assert Brackets.query.filter_by(id=1).first().name == "newplayers"
destroy_ctfd(app)
def test_brackets_delete_api():
"""Test that brackets API DELETE endpiont is behaving propertly"""
app = create_ctfd()
with app.app_context():
gen_bracket(app.db, name="players1")
assert Brackets.query.count() == 1
register_user(app, bracket_id=1)
with login_as_user(app) as client:
r = client.delete("/api/v1/brackets/1", json="")
assert r.status_code == 403
assert Brackets.query.count() == 1
with login_as_user(app, name="admin") as client:
r = client.delete("/api/v1/brackets/1", json="")
print(r.get_json())
assert r.status_code == 200
assert Brackets.query.count() == 0
destroy_ctfd(app)
def test_user_bracket_changing():
"""Test that admins can change user's brackets via the API"""
app = create_ctfd()
with app.app_context():
gen_bracket(app.db, name="players1")
gen_bracket(app.db, name="players2")
with app.test_client() as client:
client.get("/register")
with client.session_transaction() as sess:
data = {
"name": "user",
"email": "user@examplectf.com",
"password": "password",
"bracket_id": 1,
"nonce": sess.get("nonce"),
}
client.post("/register", data=data)
with login_as_user(app, name="admin") as client:
assert Users.query.filter_by(id=2).first().bracket_id == 1
r = client.patch("/api/v1/users/2", json={"bracket_id": 2})
assert r.status_code == 200
assert Users.query.filter_by(id=2).first().bracket_id == 2
destroy_ctfd(app)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Comments
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_challenge,
gen_comment,
login_as_user,
register_user,
)
def test_api_post_comments():
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
with login_as_user(app, "admin") as admin:
r = admin.post(
"/api/v1/comments",
json={
"content": "this is a challenge comment",
"type": "challenge",
"challenge_id": 1,
},
)
# Check that POST response has comment data
assert r.status_code == 200
resp = r.get_json()
assert resp["data"]["content"] == "this is a challenge comment"
assert "this is a challenge comment" in resp["data"]["html"]
assert resp["data"]["type"] == "challenge"
# Check that the comment shows up in the list of comments for the given challenge
r = admin.get("/api/v1/comments?challenge_id=1", json="")
assert r.status_code == 200
resp = r.get_json()
assert resp["data"][0]["content"] == "this is a challenge comment"
assert "this is a challenge comment" in resp["data"][0]["html"]
assert resp["data"][0]["type"] == "challenge"
destroy_ctfd(app)
def test_api_post_comments_with_invalid_author_id():
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
register_user(app)
with login_as_user(app, "admin") as admin:
r = admin.post(
"/api/v1/comments",
json={
"content": "this is a challenge comment",
"type": "challenge",
"challenge_id": 1,
"author_id": 2,
},
)
# Check that POST response has comment data
assert r.status_code == 200
resp = r.get_json()
assert resp["data"]["author_id"] == 1
destroy_ctfd(app)
def test_api_get_comments():
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
with login_as_user(app, "admin") as admin:
gen_comment(
app.db,
content="this is a challenge comment",
author_id=1,
challenge_id=1,
)
r = admin.get("/api/v1/comments", json="")
# Check that the comment shows up in the list of all comments
assert r.status_code == 200
resp = r.get_json()
assert resp["data"][0]["content"] == "this is a challenge comment"
assert "this is a challenge comment" in resp["data"][0]["html"]
assert resp["data"][0]["type"] == "challenge"
destroy_ctfd(app)
def test_api_delete_comments():
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
with login_as_user(app, "admin") as admin:
gen_comment(
app.db,
content="this is a challenge comment",
author_id=1,
challenge_id=1,
)
assert Comments.query.count() == 1
# Check that the comment can be deleted
r = admin.delete("/api/v1/comments/1", json="")
assert r.status_code == 200
resp = r.get_json()
assert Comments.query.count() == 0
assert resp["success"] is True
destroy_ctfd(app)

219
tests/api/v1/test_config.py Normal file
View File

@@ -0,0 +1,219 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Teams, Users
from CTFd.utils import get_config, set_config
from tests.helpers import create_ctfd, destroy_ctfd, gen_team, login_as_user
def test_api_configs_get_non_admin():
"""Can a user get /api/v1/configs if not admin"""
app = create_ctfd()
with app.app_context():
with app.test_client() as client:
r = client.get("/api/v1/configs")
assert r.status_code == 302
# test_api_configs_post_non_admin
"""Can a user post /api/v1/configs if not admin"""
r = client.post("/api/v1/configs", json="")
assert r.status_code == 403
# test_api_configs_patch_non_admin
"""Can a user patch /api/v1/configs if not admin"""
r = client.patch("/api/v1/configs", json="")
assert r.status_code == 403
# test_api_config_get_non_admin
"""Can a user get /api/v1/configs/<config_key> if not admin"""
r = client.get("/api/v1/configs/ctf_name")
assert r.status_code == 302
# test_api_config_patch_non_admin
"""Can a user patch /api/v1/configs/<config_key> if not admin"""
r = client.patch("/api/v1/configs/ctf_name", json="")
assert r.status_code == 403
# test_api_config_delete_non_admin
"""Can a user delete /api/v1/configs/<config_key> if not admin"""
r = client.delete("/api/v1/configs/ctf_name", json="")
assert r.status_code == 403
assert get_config("ctf_name") == "CTFd"
destroy_ctfd(app)
def test_api_configs_get_admin():
"""Can a user get /api/v1/configs if admin"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as admin:
r = admin.get("/api/v1/configs")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_configs_post_admin():
"""Can a user post /api/v1/configs if admin"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as admin:
r = admin.post("/api/v1/configs", json={"value": "9.9.9", "key": "test"})
assert r.status_code == 200
assert get_config("test") == "9.9.9"
destroy_ctfd(app)
def test_api_configs_patch_admin():
"""Can a user patch /api/v1/configs if admin"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as admin:
r = admin.patch("/api/v1/configs", json={"ctf_name": "Changed_Name"})
assert r.status_code == 200
assert get_config("ctf_name") == "Changed_Name"
destroy_ctfd(app)
def test_api_config_get_admin():
"""Can a user get /api/v1/configs/<config_key> if admin"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as admin:
r = admin.get("/api/v1/configs/ctf_name")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_config_patch_admin():
"""Can a user patch /api/v1/configs/<config_key> if admin"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as admin:
r = admin.patch("/api/v1/configs/ctf_name", json={"value": "Changed_Name"})
assert r.status_code == 200
assert get_config("ctf_name") == "Changed_Name"
destroy_ctfd(app)
def test_api_config_delete_admin():
"""Can a user delete /api/v1/configs/<config_key> if admin"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as admin:
set_config("temp_config", "testing")
r = admin.get("/api/v1/configs/temp_config", json="")
data = r.get_json()
assert r.status_code == 200
assert data["data"]["value"] == "testing"
r = admin.delete("/api/v1/configs/temp_config", json="")
assert r.status_code == 200
assert get_config("temp_config") is None
destroy_ctfd(app)
def test_config_value_types():
"""Test that we properly receive values according to schema"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as admin:
# Test new configs error out if too long
long_text = "a" * 65536
r = admin.post(
"/api/v1/configs", json={"key": "new_ctf_config", "value": long_text}
)
data = r.get_json()
assert data["errors"]["value"][0] == "new_ctf_config config is too long"
assert r.status_code == 400
r = admin.post(
"/api/v1/configs", json={"key": "new_ctf_config", "value": "test"}
)
assert r.status_code == 200
assert get_config("new_ctf_config") == "test"
# Test strings too long error out
r = admin.patch("/api/v1/configs", json={"ctf_footer": long_text})
data = r.get_json()
assert data["errors"]["value"][0] == "ctf_footer config is too long"
assert r.status_code == 400
# Test regular length strings
r = admin.patch(
"/api/v1/configs",
json={"ctf_footer": "// regular length string"},
)
assert r.status_code == 200
assert get_config("ctf_footer") == "// regular length string"
# Test booleans can be received
r = admin.patch("/api/v1/configs", json={"view_after_ctf": True})
assert r.status_code == 200
assert bool(get_config("view_after_ctf")) == True
# Test None can be received
assert get_config("mail_username") is None
r = admin.patch("/api/v1/configs", json={"mail_username": "testusername"})
assert r.status_code == 200
assert get_config("mail_username") == "testusername"
r = admin.patch("/api/v1/configs", json={"mail_username": None})
assert r.status_code == 200
assert get_config("mail_username") is None
# Test integers can be received
r = admin.patch("/api/v1/configs", json={"mail_port": 12345})
assert r.status_code == 200
assert get_config("mail_port") == 12345
# Test specific config key
r = admin.patch(
"/api/v1/configs/long_config_test", json={"value": long_text}
)
data = r.get_json()
assert data["errors"]["value"][0] == "long_config_test config is too long"
assert r.status_code == 400
assert get_config("long_config_test") is None
r = admin.patch(
"/api/v1/configs/config_test", json={"value": "config_value_test"}
)
assert r.status_code == 200
assert get_config("config_test") == "config_value_test"
r = admin.patch(
"/api/v1/configs/mail_username", json={"value": "testusername"}
)
assert r.status_code == 200
assert get_config("mail_username") == "testusername"
destroy_ctfd(app)
def test_teams_are_removed_after_migrating_from_team_mode_to_user_mode():
"""Are teams, and user.team relations removed when migrating from team mode to user mode"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as admin:
r = admin.patch("/api/v1/configs", json={"user_mode": "teams"})
assert r.status_code == 200
assert get_config("user_mode") == "teams"
gen_team(app.db)
assert Users.query.count() == 5
assert Teams.query.count() == 1
r = admin.patch("/api/v1/configs", json={"user_mode": "users"})
assert r.status_code == 200
assert get_config("user_mode") == "users"
with admin.session_transaction() as sess:
data = {
"user_mode": "users",
"submissions": "true",
"nonce": sess.get("nonce"),
}
r = admin.post("/admin/reset", data=data)
assert r.status_code == 302
assert Users.query.count() == 5
assert Teams.query.count() == 0
for user in Users.query.all():
assert user.team is None
destroy_ctfd(app)

44
tests/api/v1/test_csrf.py Normal file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask.testing import FlaskClient
from tests.helpers import create_ctfd, destroy_ctfd, login_as_user
def test_api_csrf_failure():
"""Test that API requests require the CSRF-Token header"""
app = create_ctfd()
app.test_client_class = FlaskClient
with app.app_context():
with login_as_user(app, "admin") as client:
r = client.post(
"/api/v1/challenges",
json={
"name": "chal",
"category": "cate",
"description": "desc",
"value": "100",
"state": "hidden",
"type": "standard",
},
)
assert r.status_code == 403
with client.session_transaction() as sess:
nonce = sess.get("nonce")
r = client.post(
"/api/v1/challenges",
headers={"CSRF-Token": nonce},
json={
"name": "chal",
"category": "cate",
"description": "desc",
"value": "100",
"state": "hidden",
"type": "standard",
},
)
assert r.status_code == 200
destroy_ctfd(app)

View File

@@ -0,0 +1,49 @@
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_challenge,
login_as_user,
register_user,
)
def test_api_export_csv():
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
data = {
"type": "csv",
"args": {"table": "challenges"},
}
with login_as_user(app, name="admin", password="password") as client:
r = client.post("/api/v1/exports/raw", json=data)
assert r.status_code == 200
assert r.headers["Content-Type"].startswith("text/csv")
assert "chal_name" in r.get_data(as_text=True)
# Test that regular users cannot access the endpoint
register_user(app)
with login_as_user(app) as client:
response = client.post("/api/v1/exports/raw", json=data)
assert response.status_code == 403
destroy_ctfd(app)
def test_api_export():
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
data = {}
with login_as_user(app, name="admin", password="password") as client:
r = client.post("/api/v1/exports/raw", json=data)
assert r.status_code == 200
assert r.headers["Content-Type"].startswith("application/zip")
# Test that regular users cannot access the endpoint
register_user(app)
with login_as_user(app) as client:
response = client.post("/api/v1/exports/raw", json=data)
assert response.status_code == 403
destroy_ctfd(app)

416
tests/api/v1/test_fields.py Normal file
View File

@@ -0,0 +1,416 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Fields, TeamFieldEntries, Teams, UserFieldEntries, Users
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_field,
gen_team,
login_as_user,
register_user,
)
def test_api_custom_fields():
app = create_ctfd()
with app.app_context():
register_user(app)
gen_field(app.db, name="CustomField1")
gen_field(app.db, name="CustomField2")
with login_as_user(app) as user:
r = user.get("/api/v1/configs/fields", json="")
assert r.status_code == 403
with login_as_user(app, name="admin") as admin:
r = admin.get("/api/v1/configs/fields", json="")
resp = r.get_json()
assert resp == {
"success": True,
"data": [
{
"public": True,
"required": True,
"type": "user",
"editable": True,
"id": 1,
"field_type": "text",
"description": "CustomFieldDescription",
"name": "CustomField1",
},
{
"public": True,
"required": True,
"type": "user",
"editable": True,
"id": 2,
"field_type": "text",
"description": "CustomFieldDescription",
"name": "CustomField2",
},
],
}
r = admin.post(
"/api/v1/configs/fields",
json={
"public": True,
"required": True,
"editable": True,
"id": 2,
"type": "user",
"field_type": "text",
"description": "CustomFieldDescription",
"name": "CustomField3",
},
)
assert r.status_code == 200
r = admin.get("/api/v1/configs/fields", json="")
resp = r.get_json()
assert resp == {
"success": True,
"data": [
{
"public": True,
"required": True,
"type": "user",
"editable": True,
"id": 1,
"field_type": "text",
"description": "CustomFieldDescription",
"name": "CustomField1",
},
{
"public": True,
"required": True,
"type": "user",
"editable": True,
"id": 2,
"field_type": "text",
"description": "CustomFieldDescription",
"name": "CustomField2",
},
{
"public": True,
"required": True,
"editable": True,
"id": 3,
"type": "user",
"field_type": "text",
"description": "CustomFieldDescription",
"name": "CustomField3",
},
],
}
r = admin.patch(
"/api/v1/configs/fields/3",
json={
"public": False,
"required": False,
"editable": False,
"id": 4,
"type": "user",
"field_type": "text",
"description": "CustomFieldDescription",
"name": "PatchedCustomField3",
},
)
assert r.status_code == 200
assert r.get_json()["data"] == {
"public": False,
"required": False,
"editable": False,
"id": 3,
"type": "user",
"field_type": "text",
"description": "CustomFieldDescription",
"name": "PatchedCustomField3",
}
r = admin.get("/api/v1/configs/fields/3", json="")
assert r.status_code == 200
assert r.get_json()["data"] == {
"public": False,
"required": False,
"editable": False,
"id": 3,
"type": "user",
"field_type": "text",
"description": "CustomFieldDescription",
"name": "PatchedCustomField3",
}
r = admin.delete("/api/v1/configs/fields/3", json="")
assert r.status_code == 200
r = admin.get("/api/v1/configs/fields/3", json="")
assert r.status_code == 404
destroy_ctfd(app)
def test_api_self_fields_permissions():
app = create_ctfd()
with app.app_context():
gen_field(app.db, name="CustomField1", public=False, editable=False)
gen_field(app.db, name="CustomField2", public=True, editable=True)
with app.test_client() as client:
client.get("/register")
with client.session_transaction() as sess:
data = {
"name": "user",
"email": "user@examplectf.com",
"password": "password",
"nonce": sess.get("nonce"),
"fields[1]": "CustomValue1",
"fields[2]": "CustomValue2",
}
r = client.post("/register", data=data)
with client.session_transaction() as sess:
assert sess["id"]
with login_as_user(app) as user, login_as_user(app, name="admin") as admin:
r = user.get("/api/v1/users/me")
resp = r.get_json()
assert resp["data"]["fields"] == [
{
"value": "CustomValue2",
"name": "CustomField2",
"description": "CustomFieldDescription",
"type": "text",
"field_id": 2,
}
]
r = admin.get("/api/v1/users/2")
resp = r.get_json()
assert len(resp["data"]["fields"]) == 2
field = Fields.query.filter_by(id=1).first()
field.public = True
app.db.session.commit()
r = user.get("/api/v1/users/me")
resp = r.get_json()
assert len(resp["data"]["fields"]) == 2
destroy_ctfd(app)
def test_partial_field_update():
app = create_ctfd()
with app.app_context():
register_user(app)
gen_field(app.db, name="CustomField1")
gen_field(app.db, name="CustomField2")
with login_as_user(app) as user:
r = user.patch(
"/api/v1/users/me",
json={
"fields": [
{"field_id": 1, "value": "CustomValue1"},
{"field_id": 2, "value": "CustomValue2"},
]
},
)
assert r.status_code == 200
assert UserFieldEntries.query.count() == 2
r = user.patch(
"/api/v1/users/me",
json={"fields": [{"field_id": 2, "value": "NewCustomValue2"}]},
)
assert r.status_code == 200
assert UserFieldEntries.query.count() == 2
assert (
UserFieldEntries.query.filter_by(field_id=1, user_id=2).first().value
== "CustomValue1"
)
assert (
UserFieldEntries.query.filter_by(field_id=2, user_id=2).first().value
== "NewCustomValue2"
)
with login_as_user(app, name="admin") as admin:
r = admin.patch(
"/api/v1/users/2",
json={"fields": [{"field_id": 2, "value": "AdminNewCustomValue2"}]},
)
assert r.status_code == 200
assert UserFieldEntries.query.count() == 2
assert (
UserFieldEntries.query.filter_by(field_id=1, user_id=2).first().value
== "CustomValue1"
)
assert (
UserFieldEntries.query.filter_by(field_id=2, user_id=2).first().value
== "AdminNewCustomValue2"
)
destroy_ctfd(app)
def test_api_team_self_fields_permissions():
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
app.db.session.commit()
team = Teams.query.filter_by(id=1).first()
team.captain_id = 2
app.db.session.commit()
gen_field(
app.db, name="CustomField1", type="team", public=False, editable=False
)
gen_field(app.db, name="CustomField2", type="team", public=True, editable=True)
app.db.session.add(
TeamFieldEntries(type="team", value="CustomValue1", team_id=1, field_id=1)
)
app.db.session.add(
TeamFieldEntries(type="team", value="CustomValue2", team_id=1, field_id=2)
)
app.db.session.commit()
assert len(team.field_entries) == 2
with login_as_user(app) as user, login_as_user(app, name="admin") as admin:
r = user.get("/api/v1/teams/me")
resp = r.get_json()
assert resp["data"]["fields"] == [
{
"value": "CustomValue2",
"name": "CustomField2",
"description": "CustomFieldDescription",
"type": "text",
"field_id": 2,
}
]
assert len(resp["data"]["fields"]) == 1
# Admin gets data and should see all fields
r = admin.get("/api/v1/teams/1")
resp = r.get_json()
assert len(resp["data"]["fields"]) == 2
r = user.patch(
"/api/v1/teams/me",
json={
"fields": [
{"field_id": 1, "value": "NewCustomValue1"},
{"field_id": 2, "value": "NewCustomValue2"},
]
},
)
assert r.get_json() == {
"success": False,
"errors": {"fields": ["Field 'CustomField1' cannot be editted"]},
}
assert r.status_code == 400
assert (
TeamFieldEntries.query.filter_by(id=1).first().value == "CustomValue1"
)
assert (
TeamFieldEntries.query.filter_by(id=2).first().value == "CustomValue2"
)
# After making the field public the user should see both fields
field = Fields.query.filter_by(id=1).first()
field.public = True
app.db.session.commit()
r = user.get("/api/v1/teams/me")
resp = r.get_json()
assert len(resp["data"]["fields"]) == 2
# Captain should be able to edit their values after it's made editable
field = Fields.query.filter_by(id=1).first()
field.editable = True
app.db.session.commit()
r = user.patch(
"/api/v1/teams/me",
json={
"fields": [
{"field_id": 1, "value": "NewCustomValue1"},
{"field_id": 2, "value": "NewCustomValue2"},
]
},
)
print(r.get_json())
assert r.status_code == 200
assert (
TeamFieldEntries.query.filter_by(id=1).first().value
== "NewCustomValue1"
)
assert (
TeamFieldEntries.query.filter_by(id=2).first().value
== "NewCustomValue2"
)
destroy_ctfd(app)
def test_team_partial_field_update():
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")
gen_field(app.db, name="CustomField2", type="team")
with login_as_user(app) as user:
r = user.patch(
"/api/v1/teams/me",
json={
"fields": [
{"field_id": 1, "value": "CustomValue1"},
{"field_id": 2, "value": "CustomValue2"},
]
},
)
assert r.status_code == 200
assert TeamFieldEntries.query.count() == 2
r = user.patch(
"/api/v1/teams/me",
json={"fields": [{"field_id": 2, "value": "NewCustomValue2"}]},
)
assert r.status_code == 200
assert TeamFieldEntries.query.count() == 2
assert (
TeamFieldEntries.query.filter_by(field_id=1, team_id=1).first().value
== "CustomValue1"
)
assert (
TeamFieldEntries.query.filter_by(field_id=2, team_id=1).first().value
== "NewCustomValue2"
)
with login_as_user(app, name="admin") as admin:
r = admin.patch(
"/api/v1/teams/1",
json={"fields": [{"field_id": 2, "value": "AdminNewCustomValue2"}]},
)
assert r.status_code == 200
assert TeamFieldEntries.query.count() == 2
assert (
TeamFieldEntries.query.filter_by(field_id=1, team_id=1).first().value
== "CustomValue1"
)
assert (
TeamFieldEntries.query.filter_by(field_id=2, team_id=1).first().value
== "AdminNewCustomValue2"
)
destroy_ctfd(app)

242
tests/api/v1/test_files.py Normal file
View File

@@ -0,0 +1,242 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import pathlib
import shutil
from io import BytesIO
from CTFd.models import ChallengeFiles, Challenges, Files
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_challenge,
gen_file,
login_as_user,
)
def test_api_files_get_non_admin():
app = create_ctfd()
with app.app_context():
chal = gen_challenge(app.db)
gen_file(
app.db,
location="0bf1a55a5cd327c07af15df260979668/bird.swf",
challenge_id=chal.id,
)
with app.test_client() as client:
# test_api_files_get_non_admin
"""Can a user get /api/v1/files if not admin"""
r = client.get("/api/v1/files", json="")
assert r.status_code == 403
# test_api_files_post_non_admin
"""Can a user post /api/v1/files if not admin"""
r = client.post("/api/v1/files")
assert r.status_code == 403
# test_api_file_get_non_admin
"""Can a user get /api/v1/files/<file_id> if not admin"""
r = client.get("/api/v1/files/1", json="")
assert r.status_code == 403
# test_api_file_delete_non_admin
"""Can a user delete /api/v1/files/<file_id> if not admin"""
r = client.delete("/api/v1/files/1", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_files_get_admin():
"""Can a user get /api/v1/files if admin"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as client:
r = client.get("/api/v1/files", json="")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_files_post_admin():
"""Can a user post /api/v1/files if admin"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, name="admin") as client:
with client.session_transaction() as sess:
nonce = sess.get("nonce")
r = client.post(
"/api/v1/files",
content_type="multipart/form-data",
data={
"file": (BytesIO(b"test file content"), "test.txt"),
"nonce": nonce,
},
)
assert r.status_code == 200
f = Files.query.filter_by(id=1).first()
assert f.sha1sum == "9032bbc224ed8b39183cb93b9a7447727ce67f9d"
os.remove(os.path.join(app.config["UPLOAD_FOLDER"] + "/" + f.location))
destroy_ctfd(app)
def test_api_file_get_admin():
"""Can a user get /api/v1/files/<file_id> if admin"""
app = create_ctfd()
with app.app_context():
chal = gen_challenge(app.db)
f = gen_file(
app.db,
location="0bf1a55a5cd327c07af15df260979668/bird.swf",
challenge_id=chal.id,
)
assert Files.query.count() == 1
assert ChallengeFiles.query.count() == 1
assert f in chal.files
with login_as_user(app, "admin") as client:
r = client.get("/api/v1/files/1", json="")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_file_delete_admin():
"""Can a user delete /api/v1/files/<file_id> if admin"""
app = create_ctfd()
with app.app_context():
chal = gen_challenge(app.db)
path = os.path.join(
app.config["UPLOAD_FOLDER"], "0bf1a55a5cd327c07af15df260979668", "bird.swf"
)
try:
# Create a fake file
os.makedirs(os.path.dirname(path))
open(path, "a").close()
f = gen_file(
app.db,
location="0bf1a55a5cd327c07af15df260979668/bird.swf",
challenge_id=chal.id,
)
assert Files.query.count() == 1
assert ChallengeFiles.query.count() == 1
assert f in chal.files
# Make sure the file was created
assert os.path.exists(path)
with login_as_user(app, "admin") as client:
r = client.delete("/api/v1/files/1", json="")
assert r.status_code == 200
assert Files.query.count() == 0
assert ChallengeFiles.query.count() == 0
chal = Challenges.query.filter_by(id=1).first()
assert f not in chal.files
# Make sure the API call deleted the file
assert os.path.exists(path) is False
finally:
# Always make sure the file is deleted
shutil.rmtree(os.path.dirname(path), ignore_errors=True)
destroy_ctfd(app)
def test_api_file_custom_location():
"""
Test file uploading with custom location
"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, name="admin") as client:
with client.session_transaction() as sess:
nonce = sess.get("nonce")
r = client.post(
"/api/v1/files",
content_type="multipart/form-data",
data={
"file": (BytesIO(b"test file content"), "test.txt"),
"location": "testing/asdf.txt",
"nonce": nonce,
},
)
assert r.status_code == 200
f = Files.query.filter_by(id=1).first()
assert f.sha1sum == "9032bbc224ed8b39183cb93b9a7447727ce67f9d"
assert f.location == "testing/asdf.txt"
r = client.get("/files/" + f.location)
assert r.get_data(as_text=True) == "test file content"
r = client.get("/api/v1/files/1")
response = r.get_json()
assert (
response["data"]["sha1sum"]
== "9032bbc224ed8b39183cb93b9a7447727ce67f9d"
)
assert response["data"]["location"] == "testing/asdf.txt"
# Test deletion
r = client.delete("/api/v1/files/1", json="")
assert r.status_code == 200
assert Files.query.count() == 0
target = pathlib.Path(app.config["UPLOAD_FOLDER"]) / f.location
assert target.exists() is False
# Test invalid locations
invalid_paths = [
"testing/prefix/asdf.txt",
"/testing/asdf.txt",
"asdf.txt",
]
for path in invalid_paths:
r = client.post(
"/api/v1/files",
content_type="multipart/form-data",
data={
"file": (BytesIO(b"test file content"), "test.txt"),
"location": path,
"nonce": nonce,
},
)
assert r.status_code == 400
destroy_ctfd(app)
def test_api_file_overwrite_by_location():
"""
Test file overwriting with a specific location
"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, name="admin") as client:
with client.session_transaction() as sess:
nonce = sess.get("nonce")
r = client.post(
"/api/v1/files",
content_type="multipart/form-data",
data={
"file": (BytesIO(b"test file content"), "test.txt"),
"location": "testing/asdf.txt",
"nonce": nonce,
},
)
assert r.status_code == 200
f = Files.query.filter_by(id=1).first()
r = client.get("/files/" + f.location)
assert r.get_data(as_text=True) == "test file content"
r = client.post(
"/api/v1/files",
content_type="multipart/form-data",
data={
"file": (BytesIO(b"testing new uploaded file content"), "test.txt"),
"location": "testing/asdf.txt",
"nonce": nonce,
},
)
assert r.status_code == 200
f = Files.query.filter_by(id=1).first()
r = client.get("/files/" + f.location)
assert f.sha1sum == "0ee7eb85ac0b8d8ae03f3080589157cde553b13f"
assert r.get_data(as_text=True) == "testing new uploaded file content"
destroy_ctfd(app)

240
tests/api/v1/test_flags.py Normal file
View File

@@ -0,0 +1,240 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Flags
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_challenge,
gen_flag,
login_as_user,
)
def test_api_flags_get_non_admin():
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
gen_flag(app.db, 1)
with app.test_client() as client:
# test_api_flags_get_non_admin
"""Can a user get /api/v1/flags if not admin"""
r = client.get("/api/v1/flags", json="")
assert r.status_code == 403
# test_api_flags_post_non_admin
"""Can a user post /api/v1/flags if not admin"""
r = client.post("/api/v1/flags")
assert r.status_code == 403
# test_api_flag_types_get_non_admin
"""Can a user get /api/v1/flags/types[/<type_name>] if not admin"""
r = client.get("/api/v1/flags/types", json="")
assert r.status_code == 403
# test_api_flag_get_non_admin
"""Can a user get /api/v1/flags/<flag_id> if not admin"""
r = client.get("/api/v1/flags/1", json="")
assert r.status_code == 403
# test_api_flag_patch_non_admin
"""Can a user patch /api/v1/flags/<flag_id> if not admin"""
r = client.patch("/api/v1/flags/1", json="")
assert r.status_code == 403
# test_api_flag_delete_non_admin
"""Can a user delete /api/v1/flags/<flag_id> if not admin"""
r = client.delete("/api/v1/flags/1", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_flags_get_admin():
"""Can a user get /api/v1/flags if admin"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as client:
r = client.get("/api/v1/flags", json="")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_flags_post_admin():
"""Can a user post /api/v1/flags if admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
with login_as_user(app, name="admin") as client:
r = client.post(
"/api/v1/flags",
json={"content": "flag", "type": "static", "challenge": 1},
)
assert r.status_code == 200
destroy_ctfd(app)
def test_api_flag_types_get_admin():
"""Can a user get /api/v1/flags/types[/<type_name>] if admin"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as client:
r = client.get("/api/v1/flags/types", json="")
assert r.status_code == 200
r = client.get("/api/v1/flags/types/static", json="")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_flag_get_admin():
"""Can a user get /api/v1/flags/<flag_id> if admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
gen_flag(app.db, 1)
with login_as_user(app, "admin") as client:
r = client.get("/api/v1/flags/1", json="")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_flag_patch_admin():
"""Can a user patch /api/v1/flags/<flag_id> if admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
gen_flag(app.db, 1)
with login_as_user(app, "admin") as client:
r = client.patch(
"/api/v1/flags/1",
json={"content": "flag_edit", "data": "", "type": "static", "id": "1"},
)
assert r.status_code == 200
assert r.get_json()["data"]["content"] == "flag_edit"
destroy_ctfd(app)
def test_api_flag_delete_admin():
"""Can a user patch /api/v1/flags/<flag_id> if admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
gen_flag(app.db, 1)
with login_as_user(app, "admin") as client:
r = client.delete("/api/v1/flags/1", json="")
assert r.status_code == 200
assert r.get_json().get("data") is None
destroy_ctfd(app)
def test_flag_content_stripped_on_create_and_update():
"""Test that flag content is stripped of whitespace on create and update"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
with login_as_user(app, "admin") as client:
# Create flag with whitespace
r = client.post(
"/api/v1/flags",
json={
"content": " flag_with_spaces ",
"type": "static",
"challenge": 1,
},
)
assert r.status_code == 200
data = r.get_json()["data"]
assert data["content"] == "flag_with_spaces"
flag_id = data["id"]
f = Flags.query.filter_by(id=flag_id).first()
assert f.content == "flag_with_spaces"
# Update flag with whitespace
r = client.patch(
f"/api/v1/flags/{flag_id}",
json={"content": " updated_flag ", "type": "static"},
)
assert r.status_code == 200
data = r.get_json()["data"]
assert data["content"] == "updated_flag"
f = Flags.query.filter_by(id=flag_id).first()
assert f.content == "updated_flag"
destroy_ctfd(app)
def test_flag_content_stripped_on_create_and_update_regex():
"""Test that regex flag content is stripped of whitespace on create and update"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
with login_as_user(app, "admin") as client:
# Create regex flag with whitespace
r = client.post(
"/api/v1/flags",
json={
"content": " ^flag\\d+$ ",
"type": "regex",
"challenge": 1,
},
)
assert r.status_code == 200
data = r.get_json()["data"]
assert data["content"] == "^flag\\d+$"
flag_id = data["id"]
f = Flags.query.filter_by(id=flag_id).first()
assert f.content == "^flag\\d+$"
# Update regex flag with whitespace
r = client.patch(
f"/api/v1/flags/{flag_id}",
json={"content": " ^updated_flag\\d+$ ", "type": "regex"},
)
assert r.status_code == 200
data = r.get_json()["data"]
assert data["content"] == "^updated_flag\\d+$"
f = Flags.query.filter_by(id=flag_id).first()
assert f.content == "^updated_flag\\d+$"
def test_flag_content_not_stripped_on_other_types():
"""Test that flag content is not stripped for non-static and non-regex types"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
with login_as_user(app, "admin") as client:
# Create flag with a custom type
r = client.post(
"/api/v1/flags",
json={
"content": " custom_flag ",
"type": "custom",
"challenge": 1,
},
)
assert r.status_code == 200
data = r.get_json()["data"]
# Should not be stripped
assert data["content"] == " custom_flag "
flag_id = data["id"]
f = Flags.query.filter_by(id=flag_id).first()
assert f.content == " custom_flag "
# Update flag with whitespace
r = client.patch(
f"/api/v1/flags/{flag_id}",
json={"content": " updated_custom_flag ", "type": "custom"},
)
assert r.status_code == 200
data = r.get_json()["data"]
assert data["content"] == " updated_custom_flag "
f = Flags.query.filter_by(id=flag_id).first()
assert f.content == " updated_custom_flag "

149
tests/api/v1/test_hints.py Normal file
View File

@@ -0,0 +1,149 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Hints
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_challenge,
gen_hint,
login_as_user,
register_user,
)
def test_api_hint_get_non_admin():
app = create_ctfd()
with app.app_context():
register_user(app)
with login_as_user(app) as client:
# test_api_hint_get_non_admin
"""Can the users get /api/v1/hints if not admin"""
r = client.get("/api/v1/hints", json="")
assert r.status_code == 403
assert Hints.query.count() == 0
# test_api_hint_post_non_admin
"""Can the users post /api/v1/hints if not admin"""
r = client.post("/api/v1/hints", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_hint_get_admin():
"""Can the users get /api/v1/hints if admin"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, name="admin") as client:
r = client.get("/api/v1/hints", json="")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_hint_post_admin():
"""Can the users post /api/v1/hints if admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
with login_as_user(app, name="admin") as client:
r = client.post(
"/api/v1/hints", json={"content": "hint", "cost": "1", "challenge": 1}
)
assert r.status_code == 200
assert Hints.query.count() == 1
destroy_ctfd(app)
def test_admins_can_preview_hints():
"""Test that admins are able to bypass restrictions and preview hints with ?preview=true"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
gen_hint(app.db, challenge_id=1, cost=100)
client = login_as_user(app, name="admin", password="password")
r = client.get("/api/v1/hints/1")
assert r.status_code == 200
hint = r.get_json()
assert hint.get("content") is None
r = client.get("/api/v1/hints/1?preview=true")
assert r.status_code == 200
hint = r.get_json()
assert hint["data"]["content"] == "This is a hint"
destroy_ctfd(app)
def test_users_cannot_preview_hints():
"""Test that users aren't able to preview hints"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
gen_hint(app.db, challenge_id=1, cost=100)
register_user(app)
client = login_as_user(app)
r = client.get("/api/v1/hints/1")
assert r.status_code == 200
hint = r.get_json()
assert hint.get("content") is None
r = client.get("/api/v1/hints/1?preview=true")
assert r.status_code == 200
hint = r.get_json()
assert hint["data"].get("content") is None
destroy_ctfd(app)
def test_admin_cannot_unlock_hint_with_prerequisite():
"""
Test that admins cannot unlock hints that have a prerequisite unless the prerequisite is unlocked.
Allow admin preview access with ?preview=true
"""
app = create_ctfd()
with app.app_context():
# Create a challenge and two hints, where hint2 requires hint1 as a prerequisite
chal = gen_challenge(app.db)
hint1 = gen_hint(app.db, challenge_id=chal.id, content="First hint")
hint1_id = hint1.id
hint2 = gen_hint(
app.db,
challenge_id=chal.id,
content="Second hint",
)
hint2.requirements = {"prerequisites": [1]}
hint2_id = hint2.id
app.db.session.commit()
# Login as admin
client = login_as_user(app, name="admin")
# Try to access the second hint without unlocking the prerequisite
r = client.get(f"/api/v1/hints/{hint2_id}")
assert r.status_code == 403
data = r.get_json()
assert "requirements" in data.get("errors", {})
# Try to access with preview=true (should succeed for admin)
r = client.get(f"/api/v1/hints/{hint2_id}?preview=true")
assert r.status_code == 200
data = r.get_json()
assert data["data"]["content"] == "Second hint"
# Unlock the first hint
r = client.post("/api/v1/unlocks", json={"target": hint1_id, "type": "hints"})
assert r.status_code == 200
# Now try to access the second hint (should fail b/c missing unlock)
r = client.get(f"/api/v1/hints/{hint2_id}")
data = r.get_json()
assert data["data"].get("content") is None
# Unlock the second hint
r = client.post("/api/v1/unlocks", json={"target": hint2_id, "type": "hints"})
assert r.status_code == 200
# Now try to access the second hint (should succeed)
r = client.get(f"/api/v1/hints/{hint2_id}")
assert r.status_code == 200
data = r.get_json()
assert data["data"]["content"] == "Second hint"
destroy_ctfd(app)

View File

@@ -0,0 +1,81 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Notifications
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_challenge,
gen_notification,
login_as_user,
register_user,
)
def test_api_notifications_get():
app = create_ctfd()
with app.app_context():
register_user(app)
gen_notification(app.db)
with login_as_user(app) as client:
# test_api_notifications_get
"""Can the users get /api/v1/notifications"""
r = client.get("/api/v1/notifications", json="")
assert r.status_code == 200
assert len(r.get_json()["data"]) == 1
# test_api_get_notification_detail
r = client.get("/api/v1/notifications/1", json="")
assert r.status_code == 200
resp = r.get_json()
assert resp["data"]["title"] == "title"
assert resp["data"]["content"] == "content"
# test_api_notifications_post_non_admin
"""Can the users post /api/v1/notifications if not admin"""
r = client.post("/api/v1/notifications", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_notifications_post_admin():
"""Can the users post /api/v1/notifications if admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
with login_as_user(app, name="admin") as client:
r = client.post(
"/api/v1/notifications", json={"title": "title", "content": "content"}
)
assert r.status_code == 200
destroy_ctfd(app)
def test_api_delete_notifications_by_admin():
"""Test that an admin can delete notifications"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
gen_notification(app.db)
assert Notifications.query.count() == 1
with login_as_user(app, name="admin") as client:
r = client.delete("/api/v1/notifications/1", json="")
assert r.status_code == 200
assert r.get_json()["success"] is True
assert Notifications.query.count() == 0
destroy_ctfd(app)
def test_api_delete_notifications_by_user():
"""Test that a non-admin cannot delete notifications"""
app = create_ctfd()
with app.app_context():
register_user(app)
gen_challenge(app.db)
gen_notification(app.db)
assert Notifications.query.count() == 1
with login_as_user(app) as client:
r = client.delete("/api/v1/notifications/1", json="")
assert r.status_code == 403
assert Notifications.query.count() == 1
destroy_ctfd(app)

126
tests/api/v1/test_pages.py Normal file
View File

@@ -0,0 +1,126 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_challenge,
gen_page,
login_as_user,
)
def test_api_pages_get_non_admin():
"""Can a user get /api/v1/pages if not admin"""
app = create_ctfd()
with app.app_context():
with app.test_client() as client:
gen_page(app.db, title="title", route="/route", content="content")
r = client.get("/api/v1/pages", json="")
assert r.status_code == 403
# test_api_pages_post_non_admin
"""Can a user post /api/v1/pages if not admin"""
r = client.post("/api/v1/pages")
assert r.status_code == 403
# test_api_page_get_non_admin
"""Can a user get /api/v1/pages/<page_id> if not admin"""
r = client.get("/api/v1/pages/2", json="")
assert r.status_code == 403
# test_api_page_patch_non_admin
r = client.patch("/api/v1/pages/2", json="")
assert r.status_code == 403
# test_api_page_delete_non_admin
"""Can a user delete /api/v1/pages/<page_id> if not admin"""
r = client.delete("/api/v1/pages/2", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_pages_get_admin():
"""Can a user get /api/v1/pages if admin"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as client:
r = client.get("/api/v1/pages", json="")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_pages_post_admin():
"""Can a user post /api/v1/pages if admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
with login_as_user(app, name="admin") as client:
with client.session_transaction() as sess:
nonce = sess.get("nonce")
r = client.post(
"/api/v1/pages",
json={
"title": "testing_page_title",
"route": "/route",
"content": "testing_page_content",
"nonce": nonce,
"auth_required": False,
},
)
r = client.get("/")
assert r.status_code == 200
assert "testing_page_title" in r.get_data(as_text=True)
r = client.get("/route")
assert r.status_code == 200
assert "testing_page_content" in r.get_data(as_text=True)
destroy_ctfd(app)
def test_api_page_get_admin():
"""Can a user get /api/v1/pages/<page_id> if admin"""
app = create_ctfd()
with app.app_context():
gen_page(app.db, title="title", route="/route", content="content")
with login_as_user(app, "admin") as client:
r = client.get("/api/v1/pages/2", json="")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_page_patch_admin():
"""Can a user patch /api/v1/pages/<page_id> if admin"""
app = create_ctfd()
with app.app_context():
gen_page(app.db, title="title", route="/route", content="content")
with login_as_user(app, "admin") as client:
with client.session_transaction() as sess:
nonce = sess.get("nonce")
r = client.patch(
"/api/v1/pages/2",
json={
"title": "Title",
"route": "/route",
"content": "content_edit",
"id": "2",
"nonce": nonce,
"auth_required": False,
},
)
assert r.status_code == 200
assert r.get_json()["data"]["content"] == "content_edit"
destroy_ctfd(app)
def test_api_page_delete_admin():
"""Can a user patch /api/v1/pages/<page_id> if admin"""
app = create_ctfd()
with app.app_context():
gen_page(app.db, title="title", route="/route", content="content")
with login_as_user(app, "admin") as client:
r = client.delete("/api/v1/pages/2", json="")
assert r.status_code == 200
assert r.get_json().get("data") is None
destroy_ctfd(app)

View File

@@ -0,0 +1,193 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import jsonify
from CTFd.cache import clear_standings
from CTFd.models import Users
from CTFd.utils.scoreboard import get_scoreboard_detail
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_award,
gen_challenge,
gen_flag,
gen_solve,
gen_team,
gen_user,
login_as_user,
register_user,
)
def test_scoreboard_is_cached():
"""Test that /api/v1/scoreboard is properly cached and cleared"""
app = create_ctfd()
with app.app_context():
# create user1
register_user(app, name="user1", email="user1@examplectf.com")
# create challenge
chal = gen_challenge(app.db, value=100)
gen_flag(app.db, challenge_id=chal.id, content="flag")
chal_id = chal.id
# create a solve for the challenge for user1. (the id is 2 because of the admin)
gen_solve(app.db, user_id=2, challenge_id=chal_id)
# Initial get_scoreboard_detail cache key version
saved = app.cache.get("CTFd.utils.scoreboard.get_scoreboard_detail_memver")
with login_as_user(app, "user1") as client:
# Check basic scoreboard data
assert app.cache.get("view/api.scoreboard_scoreboard_list") is None
client.get("/api/v1/scoreboard")
assert app.cache.get("view/api.scoreboard_scoreboard_list")
# Check detailed scoreboard data
orig = jsonify(get_scoreboard_detail.uncached(count=10)).get_json()
assert (
app.cache.get("CTFd.utils.scoreboard.get_scoreboard_detail_memver")
== saved
)
cached = client.get("/api/v1/scoreboard/top/10").get_json()
assert cached["data"] == orig
assert app.cache.get("CTFd.utils.scoreboard.get_scoreboard_detail_memver")
# Empty standings and check that the cached data is gone
clear_standings()
assert app.cache.get("view/api.scoreboard_scoreboard_list") is None
# Clearing an entire function bumps flask-cachings version identify instead of setting it to null
new = app.cache.get("CTFd.utils.scoreboard.get_scoreboard_detail_memver")
assert new != saved
destroy_ctfd(app)
def test_scoreboard_tie_break_ordering_with_awards():
"""
Test that scoreboard tie break ordering respects the addition of awards
"""
app = create_ctfd()
with app.app_context():
# create user1
register_user(app, name="user1", email="user1@examplectf.com")
# create user2
register_user(app, name="user2", email="user2@examplectf.com")
chal = gen_challenge(app.db, value=100)
gen_flag(app.db, challenge_id=chal.id, content="flag")
chal = gen_challenge(app.db, value=200)
gen_flag(app.db, challenge_id=chal.id, content="flag")
# create solves for the challenges. (the user_ids are off by 1 because of the admin)
gen_solve(app.db, user_id=2, challenge_id=1)
gen_solve(app.db, user_id=3, challenge_id=2)
with login_as_user(app, "user1") as client:
r = client.get("/api/v1/scoreboard")
resp = r.get_json()
assert len(resp["data"]) == 2
assert resp["data"][0]["name"] == "user2"
assert resp["data"][0]["score"] == 200
assert resp["data"][1]["name"] == "user1"
assert resp["data"][1]["score"] == 100
# Give user1 an award for 100 points.
# At this point user2 should still be ahead
gen_award(app.db, user_id=2, value=100)
with login_as_user(app, "user1") as client:
r = client.get("/api/v1/scoreboard")
resp = r.get_json()
assert len(resp["data"]) == 2
assert resp["data"][0]["name"] == "user2"
assert resp["data"][0]["score"] == 200
assert resp["data"][1]["name"] == "user1"
assert resp["data"][1]["score"] == 200
destroy_ctfd(app)
def test_scoreboard_tie_break_ordering_with_awards_under_teams():
"""
Test that team mode scoreboard tie break ordering respects the addition of awards
"""
app = create_ctfd(user_mode="teams")
with app.app_context():
gen_team(app.db, name="team1", email="team1@examplectf.com")
gen_team(app.db, name="team2", email="team2@examplectf.com")
chal = gen_challenge(app.db, value=100)
gen_flag(app.db, challenge_id=chal.id, content="flag")
chal = gen_challenge(app.db, value=200)
gen_flag(app.db, challenge_id=chal.id, content="flag")
# create solves for the challenges. (the user_ids are off by 1 because of the admin)
gen_solve(app.db, user_id=2, team_id=1, challenge_id=1)
gen_solve(app.db, user_id=6, team_id=2, challenge_id=2)
user = Users.query.filter_by(id=2).first()
with login_as_user(app, user.name) as client:
r = client.get("/api/v1/scoreboard")
resp = r.get_json()
print(resp)
assert len(resp["data"]) == 2
assert resp["data"][0]["name"] == "team2"
assert resp["data"][0]["score"] == 200
assert resp["data"][1]["name"] == "team1"
assert resp["data"][1]["score"] == 100
# Give a user on the team an award for 100 points.
# At this point team2 should still be ahead
gen_award(app.db, user_id=3, team_id=1, value=100)
with login_as_user(app, user.name) as client:
r = client.get("/api/v1/scoreboard")
resp = r.get_json()
print(resp)
assert len(resp["data"]) == 2
assert resp["data"][0]["name"] == "team2"
assert resp["data"][0]["score"] == 200
assert resp["data"][1]["name"] == "team1"
assert resp["data"][1]["score"] == 200
destroy_ctfd(app)
def test_scoreboard_detail_returns_different_counts():
"""
Test that /api/v1/scoreboard/top/10 and /api/v1/scoreboard/top/1
return different amounts of values even when cached
"""
app = create_ctfd()
with app.app_context():
# Create multiple users
for i in range(2, 13):
gen_user(app.db, name=f"user{i}", email=f"user{i}@examplectf.com")
# Create a challenge
chal = gen_challenge(app.db, value=100)
gen_flag(app.db, challenge_id=chal.id, content="flag")
# Generate solves for the challenge for multiple users
for user_id in range(2, 13): # User IDs start from 2 (admin is 1)
gen_solve(app.db, user_id=user_id, challenge_id=chal.id)
with login_as_user(app, name="user2") as client:
# Fetch top 10 scores
top_10_resp = client.get("/api/v1/scoreboard/top/10").get_json()
assert len(top_10_resp["data"]) == 10
# Fetch top 1 score
top_1_resp = client.get("/api/v1/scoreboard/top/1").get_json()
assert len(top_1_resp["data"]) == 1
# Ensure the results are different
assert top_10_resp["data"] != top_1_resp["data"]
# Fetch scores again
assert top_10_resp == client.get("/api/v1/scoreboard/top/10").get_json()
assert top_1_resp == client.get("/api/v1/scoreboard/top/1").get_json()
destroy_ctfd(app)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,230 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Discards, Fails, Solves
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_challenge,
gen_fail,
gen_solve,
gen_team,
login_as_user,
register_user,
)
def test_api_submissions_get_non_admin():
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
gen_solve(app.db, user_id=1)
with app.test_client() as client:
# test_api_submissions_get_non_admin
"""Can a user get /api/v1/submissions if not admin"""
r = client.get("/api/v1/submissions", json="")
assert r.status_code == 403
# test_api_submissions_post_non_admin
"""Can a user post /api/v1/submissions if not admin"""
r = client.post("/api/v1/submissions")
assert r.status_code == 403
# test_api_submission_get_non_admin
"""Can a user get /api/v1/submissions/<submission_id> if not admin"""
r = client.get("/api/v1/submissions/1", json="")
assert r.status_code == 403
# test_api_submission_delete_non_admin
"""Can a user delete /api/v1/submissions/<submission_id> if not admin"""
r = client.delete("/api/v1/submissions/1", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_submissions_get_admin():
"""Can a user get /api/v1/submissions if admin"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as client:
r = client.get("/api/v1/submissions", json="")
assert r.status_code == 200
r = client.get("/api/v1/submissions?user_id=1", json="")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_submissions_post_admin():
"""Can a user post /api/v1/submissions if admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
with login_as_user(app, name="admin") as client:
r = client.post(
"/api/v1/submissions",
json={
"provided": "MARKED AS SOLVED BY ADMIN",
"user_id": 1,
"team_id": None,
"challenge_id": "1",
"type": "correct",
},
)
assert r.status_code == 200
destroy_ctfd(app)
def test_api_submission_get_admin():
"""Can a user get /api/v1/submissions/<submission_id> if admin"""
app = create_ctfd()
with app.app_context():
gen_solve(app.db, user_id=1)
with login_as_user(app, "admin") as client:
r = client.get("/api/v1/submissions/1", json="")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_submission_delete_admin():
"""Can a user patch /api/v1/submissions/<submission_id> if admin"""
app = create_ctfd()
with app.app_context():
gen_solve(app.db, user_id=1)
with login_as_user(app, "admin") as client:
r = client.delete("/api/v1/submissions/1", json="")
assert r.status_code == 200
assert r.get_json().get("data") is None
destroy_ctfd(app)
def test_api_submission_patch_correct():
"""Test that patching a submission to correct creates a solve"""
app = create_ctfd()
with app.app_context():
register_user(app)
gen_challenge(app.db)
gen_fail(app.db, challenge_id=1, user_id=2)
assert Solves.query.count() == 0
with login_as_user(app, "admin") as client:
r = client.patch("/api/v1/submissions/1", json={"type": "correct"})
assert r.status_code == 200
assert Fails.query.count() == 0
assert Solves.query.count() == 1
assert Discards.query.count() == 1
destroy_ctfd(app)
def test_api_submission_patch_correct_with_existing_solve_fails():
"""Test that patching a submission to correct fails if a solve already exists"""
app = create_ctfd()
with app.app_context():
register_user(app)
gen_challenge(app.db)
gen_fail(app.db, challenge_id=1, user_id=2)
gen_solve(app.db, challenge_id=1, user_id=2)
assert Solves.query.count() == 1
with login_as_user(app, "admin") as client:
r = client.patch("/api/v1/submissions/1", json={"type": "correct"})
data = r.get_json()
assert r.status_code == 400
assert data["success"] is False
assert Solves.query.count() == 1
assert Fails.query.count() == 1
assert Discards.query.count() == 0
destroy_ctfd(app)
def test_api_submission_patch_correct_scoreboard():
"If we adjust a submission for someone the scoreboard should be correct accounting for the time of the adjusted submission"
app = create_ctfd()
with app.app_context():
# Create 2 test users
register_user(app, name="user1", email="user1@examplectf.com")
register_user(app, name="user2", email="user2@examplectf.com")
# Create 2 test challenges
gen_challenge(app.db, name="chal1")
gen_challenge(app.db, name="chal2")
# Give the first test user only fails
gen_fail(app.db, challenge_id=1, user_id=2)
gen_fail(app.db, challenge_id=2, user_id=2)
# Give the second test user only solves
gen_solve(app.db, challenge_id=1, user_id=3)
gen_solve(app.db, challenge_id=2, user_id=3)
with login_as_user(app, "admin") as client:
# user2 who has both solves should be considered on top
scoreboard = client.get("/api/v1/scoreboard").get_json()["data"]
assert len(scoreboard) == 1
assert scoreboard[0]["name"] == "user2"
# We mark user1's first solve as correct
# This should give them 100 points
# It should not place them above user2 who has 200 points
client.patch("/api/v1/submissions/1", json={"type": "correct"})
scoreboard = client.get("/api/v1/scoreboard").get_json()["data"]
assert len(scoreboard) == 2
assert scoreboard[0]["name"] == "user2"
assert scoreboard[1]["name"] == "user1"
assert scoreboard[1]["score"] == 100
# We mark user1's second solve as correct
# This should give them 200 points
# It should place them above user2 who has 200 points but was not the first to solve the challenge
# Based on time user1's attempts should be considered correct and first
client.patch("/api/v1/submissions/2", json={"type": "correct"})
scoreboard = client.get("/api/v1/scoreboard").get_json()["data"]
assert len(scoreboard) == 2
assert scoreboard[0]["name"] == "user1"
assert scoreboard[0]["score"] == 200
assert scoreboard[1]["name"] == "user2"
assert scoreboard[1]["score"] == 200
destroy_ctfd(app)
def test_api_submission_patch_correct_scoreboard_teams():
"If we adjust a submission for a team the scoreboard should be correct after the adjustment"
app = create_ctfd(user_mode="teams")
with app.app_context():
# Create 2 test teams each with only 1 user
gen_team(app.db, name="team1", email="team1@examplectf.com", member_count=1)
gen_team(app.db, name="team2", email="team2@examplectf.com", member_count=1)
# Create 2 test challenges
gen_challenge(app.db, name="chal1")
gen_challenge(app.db, name="chal2")
# Assign only fails to the user in team 1
gen_fail(app.db, challenge_id=1, user_id=2, team_id=1)
gen_fail(app.db, challenge_id=2, user_id=2, team_id=1)
# Assign only solves to the user in team 2
gen_solve(app.db, challenge_id=1, user_id=3, team_id=2)
gen_solve(app.db, challenge_id=2, user_id=3, team_id=2)
with login_as_user(app, "admin") as client:
# team2 who has both solves should be considered on top
scoreboard = client.get("/api/v1/scoreboard").get_json()["data"]
assert len(scoreboard) == 1
assert scoreboard[0]["name"] == "team2"
# We then convert the first submission which should give team1 100 points
# team1 should also now appear on the scoreboard in 2nd place
client.patch("/api/v1/submissions/1", json={"type": "correct"})
scoreboard = client.get("/api/v1/scoreboard").get_json()["data"]
assert len(scoreboard) == 2
assert scoreboard[0]["name"] == "team2"
assert scoreboard[1]["name"] == "team1"
assert scoreboard[1]["score"] == 100
# We mark team1's second solve as correct
# This should give them 200 points
# It should place them above team2 who has 200 points but was not the first to solve the challenge
# Based on time team1's attempts should be considered correct and first
client.patch("/api/v1/submissions/2", json={"type": "correct"})
scoreboard = client.get("/api/v1/scoreboard").get_json()["data"]
assert len(scoreboard) == 2
assert scoreboard[0]["name"] == "team1"
assert scoreboard[0]["score"] == 200
assert scoreboard[1]["name"] == "team2"
assert scoreboard[1]["score"] == 200
destroy_ctfd(app)

103
tests/api/v1/test_tags.py Normal file
View File

@@ -0,0 +1,103 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_challenge,
gen_tag,
login_as_user,
)
def test_api_tags_get_non_admin():
"""Can a user get /api/v1/tags if not admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
gen_tag(app.db, 1)
with app.test_client() as client:
r = client.get("/api/v1/tags", json="")
assert r.status_code == 403
# test_api_tags_post_non_admin
"""Can a user post /api/v1/tags if not admin"""
r = client.post("/api/v1/tags")
assert r.status_code == 403
# test_api_tag_get_non_admin
"""Can a user get /api/v1/tags/<tag_id> if not admin"""
r = client.get("/api/v1/tags/1", json="")
assert r.status_code == 403
# test_api_tag_patch_non_admin
"""Can a user patch /api/v1/tags/<tag_id> if not admin"""
r = client.patch("/api/v1/tags/1", json="")
assert r.status_code == 403
# test_api_tag_delete_non_admin
"""Can a user delete /api/v1/tags/<tag_id> if not admin"""
r = client.delete("/api/v1/tags/1", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_tags_get_admin():
"""Can a user get /api/v1/tags if admin"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as client:
r = client.get("/api/v1/tags", json="")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_tags_post_admin():
"""Can a user post /api/v1/tags if admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
with login_as_user(app, name="admin") as client:
r = client.post("/api/v1/tags", json={"value": "tag", "challenge": 1})
assert r.status_code == 200
destroy_ctfd(app)
def test_api_tag_get_admin():
"""Can a user get /api/v1/tags/<tag_id> if admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
gen_tag(app.db, 1)
with login_as_user(app, "admin") as client:
r = client.get("/api/v1/tags/1", json="")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_tag_patch_admin():
"""Can a user patch /api/v1/tags/<tag_id> if admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
gen_tag(app.db, 1)
with login_as_user(app, "admin") as client:
r = client.patch(
"/api/v1/tags/1", json={"value": "tag_edit", "challenge_id": 1}
)
assert r.status_code == 200
assert r.get_json()["data"]["value"] == "tag_edit"
destroy_ctfd(app)
def test_api_tag_delete_admin():
"""Can a user delete /api/v1/tags/<tag_id> if admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
gen_tag(app.db, 1)
with login_as_user(app, "admin") as client:
r = client.delete("/api/v1/tags/1", json="")
assert r.status_code == 200
assert r.get_json().get("data") is None
destroy_ctfd(app)

891
tests/api/v1/test_teams.py Normal file
View File

@@ -0,0 +1,891 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from freezegun import freeze_time
from CTFd.models import Awards, Fails, Solves, Teams, Users
from CTFd.utils import set_config
from CTFd.utils.crypto import verify_password
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_award,
gen_challenge,
gen_fail,
gen_flag,
gen_solve,
gen_team,
gen_user,
login_as_user,
register_user,
simulate_user_activity,
)
def test_api_teams_get_public():
"""Can a user get /api/v1/teams if teams are public"""
app = create_ctfd(user_mode="teams")
with app.app_context():
with app.test_client() as client:
set_config("account_visibility", "public")
r = client.get("/api/v1/teams")
assert r.status_code == 200
set_config("account_visibility", "private")
r = client.get("/api/v1/teams")
assert r.status_code == 302
set_config("account_visibility", "admins")
r = client.get("/api/v1/teams")
assert r.status_code == 404
destroy_ctfd(app)
def test_api_teams_get_private():
"""Can a user get /api/v1/teams if teams are private"""
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
with login_as_user(app) as client:
set_config("account_visibility", "public")
r = client.get("/api/v1/teams")
assert r.status_code == 200
set_config("account_visibility", "private")
r = client.get("/api/v1/teams")
assert r.status_code == 200
set_config("account_visibility", "admins")
r = client.get("/api/v1/teams")
assert r.status_code == 404
destroy_ctfd(app)
def test_api_teams_get_admin():
"""Can a user get /api/v1/teams if teams are viewed by admins only"""
app = create_ctfd(user_mode="teams")
with app.app_context():
with login_as_user(app, "admin") as client:
set_config("account_visibility", "public")
r = client.get("/api/v1/teams")
assert r.status_code == 200
set_config("account_visibility", "private")
r = client.get("/api/v1/teams")
assert r.status_code == 200
set_config("account_visibility", "admins")
r = client.get("/api/v1/teams")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_teams_post_non_admin():
"""Can a user post /api/v1/teams if not admin"""
app = create_ctfd(user_mode="teams")
with app.app_context():
with app.test_client() as client:
r = client.post("/api/v1/teams", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_teams_post_admin():
"""Can a user post /api/v1/teams if admin"""
app = create_ctfd(user_mode="teams")
with app.app_context():
with login_as_user(app, "admin") as client:
# Create team
r = client.post(
"/api/v1/teams",
json={
"website": "http://www.team.com",
"name": "team",
"country": "TW",
"email": "team@team.com",
"affiliation": "team",
"password": "password",
},
)
assert r.status_code == 200
# Make sure password was hashed properly
team = Teams.query.filter_by(email="team@team.com").first()
assert team
assert verify_password("password", team.password)
# Make sure team can actually be joined
register_user(app)
client = login_as_user(app)
with client.session_transaction() as sess:
data = {
"name": "team",
"password": "password",
"nonce": sess.get("nonce"),
}
r = client.post("/teams/join", data=data)
user = Users.query.filter_by(id=2).first()
assert user.team_id == 1
destroy_ctfd(app)
def test_api_teams_post_admin_duplicate():
"""Test that admins can only create teams with unique information"""
app = create_ctfd(user_mode="teams")
with app.app_context():
gen_team(app.db, name="team1")
with login_as_user(app, "admin") as client:
# Duplicate name
r = client.post(
"/api/v1/teams",
json={
"website": "https://examplectf.com",
"name": "team1",
"country": "TW",
"email": "team1@examplectf.com",
"affiliation": "team",
"password": "password",
},
)
resp = r.get_json()
assert r.status_code == 400
assert resp["errors"]["name"]
assert resp["success"] is False
assert Teams.query.count() == 1
# Duplicate email
r = client.post(
"/api/v1/teams",
json={
"website": "https://examplectf.com",
"name": "new_team",
"country": "TW",
"email": "team@examplectf.com",
"affiliation": "team",
"password": "password",
},
)
resp = r.get_json()
assert r.status_code == 400
assert resp["errors"]["email"]
assert resp["success"] is False
assert Teams.query.count() == 1
destroy_ctfd(app)
def test_api_team_get_public():
"""Can a user get /api/v1/team/<team_id> if teams are public"""
app = create_ctfd(user_mode="teams")
with app.app_context():
with app.test_client() as client:
set_config("account_visibility", "public")
gen_team(app.db)
r = client.get("/api/v1/teams/1")
assert r.status_code == 200
set_config("account_visibility", "private")
r = client.get("/api/v1/teams/1")
assert r.status_code == 302
set_config("account_visibility", "admins")
r = client.get("/api/v1/teams/1")
assert r.status_code == 404
destroy_ctfd(app)
def test_api_team_get_private():
"""Can a user get /api/v1/teams/<team_id> if teams are private"""
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
with login_as_user(app) as client:
set_config("account_visibility", "public")
gen_team(app.db)
r = client.get("/api/v1/teams/1")
assert r.status_code == 200
set_config("account_visibility", "private")
r = client.get("/api/v1/teams/1")
assert r.status_code == 200
set_config("account_visibility", "admins")
r = client.get("/api/v1/teams/1")
assert r.status_code == 404
destroy_ctfd(app)
def test_api_team_get_admin():
"""Can a user get /api/v1/teams/<team_id> if teams are viewed by admins only"""
app = create_ctfd(user_mode="teams")
with app.app_context():
with login_as_user(app, "admin") as client:
gen_team(app.db)
set_config("account_visibility", "public")
r = client.get("/api/v1/teams/1")
assert r.status_code == 200
set_config("account_visibility", "private")
r = client.get("/api/v1/teams/1")
assert r.status_code == 200
set_config("account_visibility", "admins")
r = client.get("/api/v1/teams/1")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_team_patch_non_admin():
"""Can a user patch /api/v1/teams/<team_id> if not admin"""
app = create_ctfd(user_mode="teams")
with app.app_context():
gen_team(app.db)
with app.test_client() as client:
r = client.patch("/api/v1/teams/1", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_team_patch_admin():
"""Can a user patch /api/v1/teams/<team_id> if admin"""
app = create_ctfd(user_mode="teams")
with app.app_context():
gen_team(app.db)
with login_as_user(app, "admin") as client:
r = client.patch(
"/api/v1/teams/1",
json={
"name": "team_name",
"email": "team@examplectf.com",
"password": "password",
"affiliation": "changed",
},
)
team = Teams.query.filter_by(id=1).first()
assert r.status_code == 200
assert r.get_json()["data"]["affiliation"] == "changed"
assert verify_password("password", team.password)
destroy_ctfd(app)
def test_api_team_delete_non_admin():
"""Can a user delete /api/v1/teams/<team_id> if not admin"""
app = create_ctfd(user_mode="teams")
with app.app_context():
gen_team(app.db)
with app.test_client() as client:
r = client.delete("/api/v1/teams/1", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_team_delete_admin():
"""Can a user patch /api/v1/teams/<team_id> if admin"""
app = create_ctfd(user_mode="teams")
with app.app_context():
team = gen_team(app.db)
assert len(team.members) == 4
members = team.members
for user in members:
simulate_user_activity(app.db, user=user)
with login_as_user(app, "admin") as client:
r = client.delete("/api/v1/teams/1", json="")
assert r.status_code == 200
assert r.get_json().get("data") is None
for user in Users.query.all():
assert user.team_id is None
destroy_ctfd(app)
def test_api_team_get_me_not_logged_in():
"""Can a user get /api/v1/teams/me if not logged in"""
app = create_ctfd(user_mode="teams")
with app.app_context():
with app.test_client() as client:
r = client.get("/api/v1/teams/me")
assert r.status_code == 302
destroy_ctfd(app)
def test_api_team_get_me_logged_in():
"""Can a user get /api/v1/teams/me if logged in"""
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") as client:
r = client.get("/api/v1/teams/me")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_team_patch_me_not_logged_in():
"""Can a user patch /api/v1/teams/me if not logged in"""
app = create_ctfd(user_mode="teams")
with app.app_context():
with app.test_client() as client:
r = client.patch("/api/v1/teams/me", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_team_patch_me_logged_in_user():
"""Can a user patch /api/v1/teams/me if logged in as a regular user"""
app = create_ctfd(user_mode="teams")
with app.app_context():
user1 = gen_user(app.db, name="user1", email="user1@examplectf.com")
user2 = gen_user(app.db, name="user2", email="user2@examplectf.com")
team = gen_team(app.db)
team.members.append(user1)
team.members.append(user2)
user1.team_id = team.id
user2.team_id = team.id
app.db.session.commit()
with login_as_user(app, name="user2") as client:
r = client.patch(
"/api/v1/teams/me", json={"name": "team_name", "affiliation": "changed"}
)
assert r.status_code == 403
destroy_ctfd(app)
def test_api_team_patch_me_logged_in_captain():
"""Can a user patch /api/v1/teams/me if logged in as the captain"""
app = create_ctfd(user_mode="teams")
with app.app_context():
user = gen_user(app.db)
team = gen_team(app.db)
team.members.append(user)
team.captain_id = 2
user.team_id = team.id
app.db.session.commit()
with login_as_user(app, name="user_name") as client:
r = client.patch(
"/api/v1/teams/me", json={"name": "team_name", "affiliation": "changed"}
)
assert r.status_code == 200
destroy_ctfd(app)
def test_api_team_patch_me_logged_in_admin_captain():
"""Can an admin patch /api/v1/teams/me if logged in as a team captain"""
app = create_ctfd(user_mode="teams")
with app.app_context():
admin = Users.query.filter_by(id=1).first()
user = gen_user(app.db)
team = gen_team(app.db)
team.members.append(user)
team.members.append(admin)
user.team_id = team.id
admin.team_id = team.id
# We want the admin to be the captain
team.captain_id = 1
app.db.session.commit()
with login_as_user(app, name="admin") as client:
# Users can't null out their team name
r = client.patch("/api/v1/teams/me", json={"name": None})
resp = r.get_json()
assert r.status_code == 400
assert resp["errors"]["name"] == ["Field may not be null."]
r = client.patch(
"/api/v1/teams/me", json={"name": "team_name", "affiliation": "changed"}
)
assert r.status_code == 200
team = Teams.query.filter_by(id=1).first()
assert team.name == "team_name"
destroy_ctfd(app)
def test_api_team_get_me_solves_not_logged_in():
"""Can a user get /api/v1/teams/me/solves if not logged in"""
app = create_ctfd(user_mode="teams")
with app.app_context():
with app.test_client() as client:
r = client.get("/api/v1/teams/me/solves", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_team_get_me_solves_logged_in():
"""Can a user get /api/v1/teams/me/solves if logged in"""
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") as client:
r = client.get("/api/v1/teams/me/solves")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_team_get_solves():
"""Can a user get /api/v1/teams/<team_id>/solves if logged in"""
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") as client:
r = client.get("/api/v1/teams/1/solves")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_team_get_solves_after_freze_time():
"""Can a user get /api/v1/teams/<team_id>/solves after freeze time"""
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
team = gen_team(
app.db, name="team1", email="team1@examplectf.com", member_count=1
)
team_member = team.members[0]
tm_name = team_member.name
set_config("freeze", "1507262400")
with freeze_time("2017-10-4"):
chal = gen_challenge(app.db)
chal_id = chal.id
gen_solve(app.db, user_id=3, team_id=1, challenge_id=chal_id)
chal2 = gen_challenge(app.db)
chal2_id = chal2.id
with freeze_time("2017-10-8"):
gen_solve(app.db, user_id=3, team_id=1, challenge_id=chal2_id)
assert Solves.query.count() == 2
with login_as_user(app) as client:
r = client.get("/api/v1/teams/1/solves")
data = r.get_json()["data"]
assert len(data) == 1
with login_as_user(app, name=tm_name) as client:
r = client.get("/api/v1/teams/me/solves")
data = r.get_json()["data"]
assert len(data) == 2
with login_as_user(app, name="admin") as client:
r = client.get("/api/v1/teams/1/solves")
data = r.get_json()["data"]
assert len(data) == 2
destroy_ctfd(app)
def test_api_team_get_me_fails_not_logged_in():
"""Can a user get /api/v1/teams/me/fails if not logged in"""
app = create_ctfd(user_mode="teams")
with app.app_context():
with app.test_client() as client:
r = client.get("/api/v1/teams/me/fails", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_team_get_me_fails_logged_in():
"""Can a user get /api/v1/teams/me/fails if logged in"""
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") as client:
r = client.get("/api/v1/teams/me/fails")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_team_get_fails():
"""Can a user get /api/v1/teams/<team_id>/fails if logged in"""
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") as client:
r = client.get("/api/v1/teams/1/fails")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_team_get_fails_after_freze_time():
"""Can a user get /api/v1/teams/<team_id>/fails after freeze time"""
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
team = gen_team(
app.db, name="team1", email="team1@examplectf.com", member_count=1
)
team_member = team.members[0]
tm_name = team_member.name
set_config("freeze", "1507262400")
with freeze_time("2017-10-4"):
chal = gen_challenge(app.db)
chal_id = chal.id
chal2 = gen_challenge(app.db)
chal2_id = chal2.id
gen_fail(app.db, user_id=3, team_id=1, challenge_id=chal_id)
with freeze_time("2017-10-8"):
gen_fail(app.db, user_id=3, team_id=1, challenge_id=chal2_id)
assert Fails.query.count() == 2
with login_as_user(app) as client:
r = client.get("/api/v1/teams/1/fails")
assert r.get_json()["meta"]["count"] == 1
with login_as_user(app, name=tm_name) as client:
r = client.get("/api/v1/teams/me/fails")
assert r.get_json()["meta"]["count"] == 2
with login_as_user(app, name="admin") as client:
r = client.get("/api/v1/teams/1/fails")
assert r.get_json()["meta"]["count"] == 2
destroy_ctfd(app)
def test_api_team_get_me_awards_not_logged_in():
"""Can a user get /api/v1/teams/me/awards if not logged in"""
app = create_ctfd(user_mode="teams")
with app.app_context():
with app.test_client() as client:
r = client.get("/api/v1/teams/me/awards", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_team_get_me_awards_logged_in():
"""Can a user get /api/v1/teams/me/awards if logged in"""
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") as client:
r = client.get("/api/v1/teams/me/awards")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_team_get_awards():
"""Can a user get /api/v1/teams/<team_id>/awards if logged in"""
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") as client:
r = client.get("/api/v1/teams/1/awards")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_team_get_awards_after_freze_time():
"""Can a user get /api/v1/teams/<team_id>/awards after freeze time"""
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
team = gen_team(
app.db, name="team1", email="team1@examplectf.com", member_count=1
)
team_member = team.members[0]
tm_name = team_member.name
set_config("freeze", "1507262400")
with freeze_time("2017-10-4"):
gen_award(app.db, user_id=3)
with freeze_time("2017-10-8"):
gen_award(app.db, user_id=3)
assert Awards.query.count() == 2
with login_as_user(app) as client:
r = client.get("/api/v1/teams/1/awards")
data = r.get_json()["data"]
assert len(data) == 1
with login_as_user(app, name=tm_name) as client:
r = client.get("/api/v1/teams/me/awards")
data = r.get_json()["data"]
assert len(data) == 2
with login_as_user(app, name="admin") as client:
r = client.get("/api/v1/teams/1/awards")
data = r.get_json()["data"]
assert len(data) == 2
destroy_ctfd(app)
def test_api_team_patch_password():
"""Can a user change their team password /api/v1/teams/me if logged in as the captain"""
app = create_ctfd(user_mode="teams")
with app.app_context():
user1 = gen_user(
app.db, name="user1", email="user1@examplectf.com", password="captain"
) # ID 2
user2 = gen_user(app.db, name="user2", email="user2@examplectf.com") # ID 3
team = gen_team(app.db)
team.members.append(user1)
team.members.append(user2)
team.captain_id = 2
user1.team_id = team.id
user2.team_id = team.id
app.db.session.commit()
with login_as_user(app, name="user2") as client:
r = client.patch(
"/api/v1/teams/me",
json={"confirm": "password", "password": "new_password"},
)
assert r.status_code == 403
assert r.get_json() == {
"errors": {"": ["Only team captains can edit team information"]},
"success": False,
}
team = Teams.query.filter_by(id=1).first()
assert (
verify_password(plaintext="new_password", ciphertext=team.password)
is False
)
with login_as_user(app, name="user1", password="captain") as client:
# Test that invalid passwords aren't accepted
r = client.patch(
"/api/v1/teams/me",
json={"confirm": "incorrect_password", "password": "new_password"},
)
assert r.status_code == 400
assert (
verify_password(plaintext="new_password", ciphertext=team.password)
is False
)
# Test that the team's password is accepted
r = client.patch(
"/api/v1/teams/me",
json={"confirm": "password", "password": "new_password"},
)
assert r.status_code == 200
team = Teams.query.filter_by(id=1).first()
assert verify_password(plaintext="new_password", ciphertext=team.password)
# Test that the captain's password is also accepted
r = client.patch(
"/api/v1/teams/me",
json={"confirm": "captain", "password": "captain_password"},
)
assert r.status_code == 200
team = Teams.query.filter_by(id=1).first()
assert verify_password(
plaintext="captain_password", ciphertext=team.password
)
def test_api_team_captain_disbanding():
"""Test that only team captains can disband teams"""
app = create_ctfd(user_mode="teams")
with app.app_context():
user = gen_user(app.db, name="user")
team = gen_team(app.db)
team.members.append(user)
user.team_id = team.id
team.captain_id = 2
user2 = gen_user(app.db, name="user2", email="user2@examplectf.com")
team.members.append(user2)
app.db.session.commit()
with login_as_user(app, name="user2") as client:
r = client.delete("/api/v1/teams/me", json="")
assert r.status_code == 403
assert r.get_json() == {
"success": False,
"errors": {"": ["Only team captains can disband their team"]},
}
with login_as_user(app) as client:
r = client.delete("/api/v1/teams/me", json="")
assert r.status_code == 200
assert r.get_json() == {
"success": True,
}
destroy_ctfd(app)
def test_api_team_disbanding_disabled():
"""Test that team disbanding can be disabled"""
app = create_ctfd(user_mode="teams")
with app.app_context():
set_config("team_disbanding", "disabled")
team = gen_team(app.db)
captain = Users.query.filter_by(id=team.captain_id).first()
app.db.session.commit()
with login_as_user(app, name=captain.name) as client:
r = client.delete("/api/v1/teams/me", json="")
assert r.status_code == 403
assert r.get_json() == {
"success": False,
"errors": {"": ["Team disbanding is currently disabled"]},
}
set_config("team_disbanding", "inactive_only")
with login_as_user(app, name=captain.name) as client:
r = client.delete("/api/v1/teams/me", json="")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_team_captain_disbanding_only_inactive_teams():
"""Test that only teams that haven't conducted any actions can be disbanded"""
app = create_ctfd(user_mode="teams")
with app.app_context():
user = gen_user(app.db, name="user")
team = gen_team(app.db)
team.members.append(user)
user.team_id = team.id
team.captain_id = 2
user2 = gen_user(app.db, name="user2", email="user2@examplectf.com")
team.members.append(user2)
app.db.session.commit()
gen_challenge(app.db)
gen_flag(app.db, 1)
gen_solve(app.db, user_id=3, team_id=1, challenge_id=1)
with login_as_user(app) as client:
r = client.delete("/api/v1/teams/me", json="")
assert r.status_code == 403
assert r.get_json() == {
"success": False,
"errors": {
"": [
"You cannot disband your team as it has participated in the event. "
"Please contact an admin to disband your team or remove a member."
]
},
}
user = gen_user(app.db, name="user3", email="user3@examplectf.com")
team = gen_team(app.db, name="team2", email="team2@examplectf.com")
print(user.id)
team.members.append(user)
user.team_id = team.id
team.captain_id = user.id
app.db.session.commit()
with login_as_user(app, name="user3") as client:
r = client.delete("/api/v1/teams/me", json="")
print(r.get_json())
assert r.status_code == 200
assert r.get_json() == {"success": True}
destroy_ctfd(app)
def test_api_accessing_hidden_banned_users():
"""Hidden/Banned users should not be visible to normal users, only to admins"""
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
register_user(app, name="user2", email="user2@examplectf.com")
register_user(app, name="visible_user", email="visible_user@examplectf.com")
user = Users.query.filter_by(id=2).first()
team = gen_team(
app.db, name="hidden_team", email="hidden_team@examplectf.com", hidden=True
)
team.members.append(user)
user.team_id = team.id
app.db.session.commit()
user = Users.query.filter_by(id=3).first()
team = gen_team(
app.db, name="banned_team", email="banned_team@examplectf.com", banned=True
)
team.members.append(user)
user.team_id = team.id
app.db.session.commit()
with login_as_user(app, name="visible_user") as client:
list_teams = client.get("/api/v1/teams").get_json()["data"]
assert len(list_teams) == 0
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
assert client.get("/api/v1/teams/2").status_code == 404
assert client.get("/api/v1/teams/2/solves").status_code == 404
assert client.get("/api/v1/teams/2/fails").status_code == 404
assert client.get("/api/v1/teams/2/awards").status_code == 404
with login_as_user(app, name="admin") as client:
# Admins see hidden teams in lists
list_users = client.get("/api/v1/teams?view=admin").get_json()["data"]
assert len(list_users) == 2
assert client.get("/api/v1/teams/1").status_code == 200
assert client.get("/api/v1/teams/1/solves").status_code == 200
assert client.get("/api/v1/teams/1/fails").status_code == 200
assert client.get("/api/v1/teams/1/awards").status_code == 200
assert client.get("/api/v1/teams/2").status_code == 200
assert client.get("/api/v1/teams/2/solves").status_code == 200
assert client.get("/api/v1/teams/2/fails").status_code == 200
assert client.get("/api/v1/teams/2/awards").status_code == 200
destroy_ctfd(app)
def test_api_user_without_team_challenge_interaction():
"""Can a user interact with challenges without having joined a team?"""
app = create_ctfd(user_mode="teams")
with app.app_context():
register_user(app)
gen_challenge(app.db)
gen_flag(app.db, 1)
with login_as_user(app) as client:
assert client.get("/api/v1/challenges").status_code == 403
assert client.get("/api/v1/challenges/1").status_code == 403
assert (
client.post(
"/api/v1/challenges/attempt",
json={"challenge_id": 1, "submission": "wrong_flag"},
).status_code
== 403
)
# Create a user with a team
user = gen_user(app.db, email="user_name@examplectf.com")
team = gen_team(app.db)
team.members.append(user)
user.team_id = team.id
app.db.session.commit()
# Test if user with team can interact with challenges
with login_as_user(app, name="user_name") as client:
assert client.get("/api/v1/challenges").status_code == 200
assert client.get("/api/v1/challenges/1").status_code == 200
assert (
client.post(
"/api/v1/challenges/attempt",
json={"challenge_id": 1, "submission": "flag"},
).status_code
== 200
)
destroy_ctfd(app)

123
tests/api/v1/test_tokens.py Normal file
View File

@@ -0,0 +1,123 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import datetime
from CTFd.models import Tokens, Users
from CTFd.schemas.tokens import TokenSchema
from CTFd.utils.security.auth import generate_user_token
from tests.helpers import create_ctfd, destroy_ctfd, gen_user, login_as_user
def test_api_tag_list_post():
"""Can a user create a token"""
app = create_ctfd()
with app.app_context():
user = gen_user(app.db, name="user")
user_id = user.id
with login_as_user(app) as client:
r = client.post("/api/v1/tokens", json={})
assert r.status_code == 200
resp = r.get_json()
value = resp["data"]["value"]
token = Tokens.query.filter_by(value=value).first()
assert token.user_id == user_id
assert token.expiration > datetime.datetime.utcnow()
data = {"expiration": "9999-12-30"}
r = client.post("/api/v1/tokens", json=data)
assert r.status_code == 200
resp = r.get_json()
value = resp["data"]["value"]
token = Tokens.query.filter_by(value=value).first()
assert token.user_id == user_id
assert token.expiration.year == 9999
destroy_ctfd(app)
def test_api_tag_list_get():
"""Can a user get /api/v1/tokens"""
app = create_ctfd()
with app.app_context():
user = gen_user(app.db, name="user")
generate_user_token(user)
user2 = gen_user(app.db, name="user2", email="user2@examplectf.com")
generate_user_token(user2)
generate_user_token(user2)
with login_as_user(app) as client:
r = client.get("/api/v1/tokens", json="")
assert r.status_code == 200
resp = r.get_json()
assert len(resp["data"]) == 1
with login_as_user(app, name="user2") as client:
r = client.get("/api/v1/tokens", json="")
assert r.status_code == 200
resp = r.get_json()
assert len(resp["data"]) == 2
destroy_ctfd(app)
def test_api_tag_detail_get():
"""Can a user get /api/v1/tokens/<token_id>"""
app = create_ctfd()
with app.app_context():
user = gen_user(app.db, name="user")
generate_user_token(user)
with login_as_user(app) as client:
r = client.get("/api/v1/tokens/1", json="")
assert r.status_code == 200
resp = r.get_json()
assert sorted(resp["data"].keys()) == sorted(TokenSchema().views["user"])
with login_as_user(app, "admin") as client:
r = client.get("/api/v1/tokens/1", json="")
assert r.status_code == 200
resp = r.get_json()
assert sorted(resp["data"].keys()) == sorted(TokenSchema().views["admin"])
gen_user(app.db, name="user2", email="user2@examplectf.com")
with login_as_user(app, "user2") as client:
r = client.get("/api/v1/tokens/1", json="")
assert r.status_code == 404
destroy_ctfd(app)
def test_api_token_delete():
"""Can tokens be deleted by owners, and admins"""
app = create_ctfd()
with app.app_context():
# Can be deleted by the user
user = gen_user(app.db)
user_id = user.id
username = user.name
token = generate_user_token(user)
token_id = token.id
with login_as_user(app, username) as client:
r = client.delete("/api/v1/tokens/" + str(token_id), json="")
assert r.status_code == 200
assert Tokens.query.count() == 0
# Can be deleted by admins
user = Users.query.filter_by(id=user_id).first()
token = generate_user_token(user)
token_id = token.id
with login_as_user(app, "admin") as client:
r = client.delete("/api/v1/tokens/" + str(token_id), json="")
assert r.status_code == 200
assert Tokens.query.count() == 0
# First user
first_user = Users.query.filter_by(id=user_id).first()
token = generate_user_token(first_user)
token_id = token.id
# Second user
second_user = gen_user(app.db, name="user2", email="user2@examplectf.com")
username2 = second_user.name
with login_as_user(app, username2) as client:
r = client.delete("/api/v1/tokens/" + str(token_id), json="")
assert r.status_code == 404
assert Tokens.query.count() == 1
destroy_ctfd(app)

132
tests/api/v1/test_topics.py Normal file
View File

@@ -0,0 +1,132 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_challenge,
gen_topic,
login_as_user,
register_user,
)
def test_api_topics_non_admin():
"""Can a user interact with /api/v1/topics if not admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
gen_topic(app.db, challenge_id=1)
with app.test_client() as client:
r = client.get("/api/v1/topics", json="")
assert r.status_code == 403
"""Can a user post /api/v1/topics if not admin"""
r = client.post("/api/v1/topics")
assert r.status_code == 403
"""Can a user delete /api/v1/topics if not admin"""
r = client.delete("/api/v1/topics")
assert r.status_code == 403
"""Can a user get /api/v1/topics/<topic_id> if not admin"""
r = client.get("/api/v1/topics/1", json="")
assert r.status_code == 403
"""Can a user delete /api/v1/topics/<topic_id> if not admin"""
r = client.delete("/api/v1/topics/1", json="")
assert r.status_code == 403
register_user(app)
with login_as_user(app) as client:
r = client.get("/api/v1/topics", json="")
assert r.status_code == 403
"""Can a user post /api/v1/topics if not admin"""
r = client.post("/api/v1/topics")
assert r.status_code == 403
"""Can a user delete /api/v1/topics if not admin"""
r = client.delete("/api/v1/topics")
assert r.status_code == 403
"""Can a user get /api/v1/topics/<topic_id> if not admin"""
r = client.get("/api/v1/topics/1", json="")
assert r.status_code == 403
"""Can a user delete /api/v1/topics/<topic_id> if not admin"""
r = client.delete("/api/v1/topics/1", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_topics_get_admin():
"""Can a user get /api/v1/topics if admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
gen_topic(app.db, challenge_id=1)
gen_topic(app.db, challenge_id=1, value="topic2")
with login_as_user(app, name="admin") as client:
r = client.get("/api/v1/topics")
assert r.status_code == 200
assert r.get_json() == {
"success": True,
"data": [{"id": 1, "value": "topic"}, {"id": 2, "value": "topic2"}],
}
destroy_ctfd(app)
def test_api_topics_post_admin():
"""Can a user post /api/v1/topics if admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
with login_as_user(app, name="admin") as client:
r = client.post(
"/api/v1/topics",
json={"value": "topic", "type": "challenge", "challenge_id": 1},
)
assert r.status_code == 200
print(r.get_json())
assert r.get_json() == {
"success": True,
"data": {
"challenge_id": 1,
"challenge": 1,
"topic": 1,
"id": 1,
"topic_id": 1,
},
}
destroy_ctfd(app)
def test_api_topics_delete_admin():
"""Can a user delete /api/v1/topics/<topic_id> if admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
gen_topic(app.db, challenge_id=1)
with login_as_user(app, "admin") as client:
r = client.delete("/api/v1/topics/1", json="")
assert r.status_code == 200
resp = r.get_json()
assert resp.get("data") is None
assert resp.get("success") is True
destroy_ctfd(app)
def test_api_topics_delete_target_admin():
"""Can a user delete /api/v1/topics if admin"""
app = create_ctfd()
with app.app_context():
gen_challenge(app.db)
gen_topic(app.db, challenge_id=1)
with login_as_user(app, "admin") as client:
r = client.delete("/api/v1/topics?type=challenge&target_id=1", json="")
assert r.status_code == 200
resp = r.get_json()
assert resp.get("data") is None
assert resp.get("success") is True
destroy_ctfd(app)

970
tests/api/v1/test_users.py Normal file
View File

@@ -0,0 +1,970 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from freezegun import freeze_time
from CTFd.models import Awards, Fails, Solves, Users
from CTFd.schemas.users import UserSchema
from CTFd.utils import set_config
from CTFd.utils.crypto import verify_password
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_award,
gen_challenge,
gen_fail,
gen_solve,
gen_team,
gen_user,
login_as_user,
register_user,
simulate_user_activity,
)
def test_api_users_get_public():
"""Can a user get /api/v1/users if users are public"""
app = create_ctfd()
with app.app_context():
with app.test_client() as client:
set_config("account_visibility", "public")
r = client.get("/api/v1/users")
assert r.status_code == 200
set_config("account_visibility", "private")
r = client.get("/api/v1/users")
assert r.status_code == 302
set_config("account_visibility", "admins")
r = client.get("/api/v1/users")
assert r.status_code == 404
destroy_ctfd(app)
def test_api_users_get_private():
"""Can a user get /api/v1/users if users are public"""
app = create_ctfd()
with app.app_context():
with app.test_client() as client:
set_config("account_visibility", "public")
r = client.get("/api/v1/users")
assert r.status_code == 200
set_config("account_visibility", "private")
r = client.get("/api/v1/users")
assert r.status_code == 302
set_config("account_visibility", "admins")
r = client.get("/api/v1/users")
assert r.status_code == 404
destroy_ctfd(app)
def test_api_users_get_admins():
"""Can a user get /api/v1/users if users are public"""
app = create_ctfd()
with app.app_context():
with app.test_client() as client:
set_config("account_visibility", "public")
r = client.get("/api/v1/users")
assert r.status_code == 200
set_config("account_visibility", "private")
r = client.get("/api/v1/users")
assert r.status_code == 302
set_config("account_visibility", "admins")
r = client.get("/api/v1/users")
assert r.status_code == 404
destroy_ctfd(app)
def test_api_users_post_non_admin():
"""Can a user post /api/v1/users if not admin"""
app = create_ctfd()
with app.app_context():
with app.test_client() as client:
r = client.post("/api/v1/users", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_users_post_admin():
"""Can a user post /api/v1/users if admin"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as client:
# Create user
r = client.post(
"/api/v1/users",
json={"name": "user", "email": "user@user.com", "password": "password"},
)
assert r.status_code == 200
# Make sure password was hashed properly
user = Users.query.filter_by(email="user@user.com").first()
assert user
assert verify_password("password", user.password)
# Make sure user can login with the creds
client = login_as_user(app)
r = client.get("/profile")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_users_post_admin_with_attributes():
"""Can a user post /api/v1/users with user settings"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as client:
# Create user
r = client.post(
"/api/v1/users",
json={
"name": "user",
"email": "user@user.com",
"password": "password",
"banned": True,
"hidden": True,
"verified": True,
},
)
assert r.status_code == 200
# Make sure password was hashed properly
user = Users.query.filter_by(email="user@user.com").first()
assert user
assert verify_password("password", user.password)
assert user.banned
assert user.hidden
assert user.verified
destroy_ctfd(app)
def test_api_users_post_admin_duplicate_information():
"""Can an admin create a user with duplicate information"""
app = create_ctfd()
with app.app_context():
register_user(app)
with login_as_user(app, "admin") as client:
# Duplicate email
r = client.post(
"/api/v1/users",
json={
"name": "user2",
"email": "user@examplectf.com",
"password": "password",
},
)
resp = r.get_json()
assert r.status_code == 400
assert resp["errors"]["email"]
assert resp["success"] is False
assert Users.query.count() == 2
# Duplicate user
r = client.post(
"/api/v1/users",
json={
"name": "user",
"email": "user2@examplectf.com",
"password": "password",
},
)
resp = r.get_json()
assert r.status_code == 400
assert resp["errors"]["name"]
assert resp["success"] is False
assert Users.query.count() == 2
destroy_ctfd(app)
def test_api_users_patch_admin_duplicate_information():
"""Can an admin modify a user with duplicate information"""
app = create_ctfd()
with app.app_context():
register_user(
app, name="user1", email="user1@examplectf.com", password="password"
)
register_user(
app, name="user2", email="user2@examplectf.com", password="password"
)
with login_as_user(app, "admin") as client:
# Duplicate name
r = client.patch(
"/api/v1/users/1",
json={
"name": "user2",
"email": "user@examplectf.com",
"password": "password",
},
)
resp = r.get_json()
assert r.status_code == 400
assert resp["errors"]["name"]
assert resp["success"] is False
# Duplicate email
r = client.patch(
"/api/v1/users/1",
json={
"name": "user",
"email": "user2@examplectf.com",
"password": "password",
},
)
resp = r.get_json()
assert r.status_code == 400
assert resp["errors"]["email"]
assert resp["success"] is False
assert Users.query.count() == 3
destroy_ctfd(app)
def test_api_users_patch_duplicate_information():
"""Can a user modify their information to another user's"""
app = create_ctfd()
with app.app_context():
register_user(
app, name="user1", email="user1@examplectf.com", password="password"
)
register_user(
app, name="user2", email="user2@examplectf.com", password="password"
)
with login_as_user(app, "user1") as client:
# Duplicate email
r = client.patch(
"/api/v1/users/me",
json={
"name": "user1",
"email": "user2@examplectf.com",
"confirm": "password",
},
)
resp = r.get_json()
assert r.status_code == 400
assert resp["errors"]["email"]
assert resp["success"] is False
# Duplicate user
r = client.patch(
"/api/v1/users/me",
json={
"name": "user2",
"email": "user1@examplectf.com",
"confirm": "password",
},
)
resp = r.get_json()
assert r.status_code == 400
assert resp["errors"]["name"]
assert resp["success"] is False
assert Users.query.count() == 3
destroy_ctfd(app)
def test_api_team_get_public():
"""Can a user get /api/v1/team/<user_id> if users are public"""
app = create_ctfd()
with app.app_context():
with app.test_client() as client:
set_config("account_visibility", "public")
gen_user(app.db)
r = client.get("/api/v1/users/2")
assert r.status_code == 200
set_config("account_visibility", "private")
r = client.get("/api/v1/users/2")
assert r.status_code == 302
set_config("account_visibility", "admins")
r = client.get("/api/v1/users/2")
assert r.status_code == 404
destroy_ctfd(app)
def test_api_team_get_private():
"""Can a user get /api/v1/users/<user_id> if users are private"""
app = create_ctfd()
with app.app_context():
register_user(app)
with login_as_user(app) as client:
set_config("account_visibility", "public")
r = client.get("/api/v1/users/2")
print(r.__dict__)
assert r.status_code == 200
set_config("account_visibility", "private")
r = client.get("/api/v1/users/2")
assert r.status_code == 200
set_config("account_visibility", "admins")
r = client.get("/api/v1/users/2")
assert r.status_code == 404
destroy_ctfd(app)
def test_api_team_get_admin():
"""Can a user get /api/v1/users/<user_id> if users are viewed by admins only"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, "admin") as client:
gen_user(app.db)
set_config("account_visibility", "public")
r = client.get("/api/v1/users/2")
assert r.status_code == 200
set_config("account_visibility", "private")
r = client.get("/api/v1/users/2")
assert r.status_code == 200
set_config("account_visibility", "admins")
r = client.get("/api/v1/users/2")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_user_patch_non_admin():
"""Can a user patch /api/v1/users/<user_id> if not admin"""
app = create_ctfd()
with app.app_context():
register_user(app)
with app.test_client() as client:
r = client.patch("/api/v1/users/2", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_user_patch_admin():
"""Can a user patch /api/v1/users/<user_id> if admin"""
app = create_ctfd()
with app.app_context():
register_user(app)
with login_as_user(app, "admin") as client:
r = client.patch(
"/api/v1/users/2",
json={
"name": "user",
"email": "user@examplectf.com",
"password": "password",
"country": "US",
"verified": True,
},
)
assert r.status_code == 200
user_data = r.get_json()["data"]
assert user_data["country"] == "US"
assert user_data["verified"] is True
destroy_ctfd(app)
def test_api_user_delete_non_admin():
"""Can a user delete /api/v1/users/<user_id> if not admin"""
app = create_ctfd()
with app.app_context():
register_user(app)
with app.test_client() as client:
r = client.delete("/api/v1/teams/2", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_user_delete_admin():
"""Can a user patch /api/v1/users/<user_id> if admin"""
app = create_ctfd()
with app.app_context():
register_user(app)
user = Users.query.filter_by(id=2).first()
simulate_user_activity(app.db, user=user)
with login_as_user(app, "admin") as client:
r = client.delete("/api/v1/users/2", json="")
assert r.status_code == 200
assert r.get_json().get("data") is None
assert Users.query.filter_by(id=2).first() is None
destroy_ctfd(app)
def test_api_user_get_me_not_logged_in():
"""Can a user get /api/v1/users/me if not logged in"""
app = create_ctfd()
with app.app_context():
with app.test_client() as client:
r = client.get("/api/v1/users/me")
assert r.status_code == 302
destroy_ctfd(app)
def test_api_user_get_me_logged_in():
"""Can a user get /api/v1/users/me if logged in"""
app = create_ctfd()
with app.app_context():
register_user(app)
with login_as_user(app) as client:
r = client.get("/api/v1/users/me")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_user_patch_me_not_logged_in():
"""Can a user patch /api/v1/users/me if not logged in"""
app = create_ctfd()
with app.app_context():
with app.test_client() as client:
r = client.patch("/api/v1/users/me", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_user_patch_me_logged_in():
"""Can a user patch /api/v1/users/me if logged in"""
app = create_ctfd()
with app.app_context():
register_user(app)
with login_as_user(app) as client:
r = client.patch(
"/api/v1/users/me",
json={
"name": "user",
"email": "user@examplectf.com",
"password": "password",
"confirm": "password",
"country": "US",
},
)
assert r.status_code == 200
assert r.get_json()["data"]["country"] == "US"
destroy_ctfd(app)
def test_api_admin_user_patch_me_logged_in():
"""Can an admin patch /api/v1/users/me"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, name="admin") as client:
r = client.patch(
"/api/v1/users/me",
json={
"name": "user",
"email": "user@examplectf.com",
"password": "password",
"confirm": "password",
"country": "US",
},
)
assert r.status_code == 200
assert r.get_json()["data"]["country"] == "US"
user = Users.query.filter_by(id=1).first()
assert user.name == "user"
assert user.email == "user@examplectf.com"
destroy_ctfd(app)
def test_api_user_change_name():
"""Can a user change their name via the API"""
app = create_ctfd()
with app.app_context():
register_user(app)
with login_as_user(app) as client:
r = client.patch("/api/v1/users/me", json={"name": "user2"})
assert r.status_code == 200
resp = r.get_json()
assert resp["data"]["name"] == "user2"
assert resp["success"] is True
r = client.patch("/api/v1/users/me", json={"name": None})
resp = r.get_json()
print(resp)
assert r.status_code == 400
assert resp["errors"]["name"] == ["Field may not be null."]
assert resp["success"] is False
set_config("name_changes", False)
r = client.patch("/api/v1/users/me", json={"name": "new_name"})
assert r.status_code == 400
resp = r.get_json()
assert "name" in resp["errors"]
assert resp["success"] is False
set_config("name_changes", True)
r = client.patch("/api/v1/users/me", json={"name": "new_name"})
assert r.status_code == 200
resp = r.get_json()
assert resp["data"]["name"] == "new_name"
assert resp["success"] is True
destroy_ctfd(app)
def test_api_user_change_email():
"""Test that users can change their email via the API"""
app = create_ctfd()
with app.app_context():
register_user(app)
user = Users.query.filter_by(id=2).first()
app.db.session.commit()
with login_as_user(app) as client:
# Test users can't submit null
r = client.patch(
"/api/v1/users/me", json={"email": None, "confirm": "password"}
)
resp = r.get_json()
print(resp)
assert r.status_code == 400
assert resp["errors"]["email"] == ["Field may not be null."]
# Test users can exercise the API
r = client.patch(
"/api/v1/users/me",
json={"email": "new_email@email.com", "confirm": "password"},
)
assert r.status_code == 200
resp = r.get_json()
assert resp["data"]["email"] == "new_email@email.com"
assert resp["success"] is True
user = Users.query.filter_by(id=2).first()
assert user.email == "new_email@email.com"
destroy_ctfd(app)
def test_api_user_change_verify_email():
"""Test that users are marked unconfirmed if they change their email and verify_emails is turned on"""
app = create_ctfd()
with app.app_context():
set_config("verify_emails", True)
register_user(app)
user = Users.query.filter_by(id=2).first()
user.verified = True
app.db.session.commit()
with login_as_user(app) as client:
r = client.patch(
"/api/v1/users/me",
json={"email": "new_email@email.com", "confirm": "password"},
)
assert r.status_code == 200
resp = r.get_json()
assert resp["data"]["email"] == "new_email@email.com"
assert resp["success"] is True
user = Users.query.filter_by(id=2).first()
assert user.verified is False
destroy_ctfd(app)
def test_api_user_change_email_under_whitelist():
"""Test that users can only change emails to ones in the whitelist"""
app = create_ctfd()
with app.app_context():
register_user(app)
set_config(
"domain_whitelist", "whitelisted.com, whitelisted.org, whitelisted.net"
)
with login_as_user(app) as client:
r = client.patch(
"/api/v1/users/me",
json={"email": "new_email@email.com", "confirm": "password"},
)
assert r.status_code == 400
resp = r.get_json()
assert resp["errors"]["email"]
assert resp["success"] is False
r = client.patch(
"/api/v1/users/me",
json={"email": "new_email@whitelisted.com", "confirm": "password"},
)
assert r.status_code == 200
resp = r.get_json()
assert resp["data"]["email"] == "new_email@whitelisted.com"
assert resp["success"] is True
destroy_ctfd(app)
def test_api_user_change_email_under_blacklist():
"""Test that users can not change emails to ones in the blacklist"""
app = create_ctfd()
with app.app_context():
register_user(app)
set_config(
"domain_blacklist", "blacklisted.com, blacklisted.org, blacklisted.net"
)
with login_as_user(app) as client:
r = client.patch(
"/api/v1/users/me",
json={"email": "new_email@blacklisted.com", "confirm": "password"},
)
assert r.status_code == 400
resp = r.get_json()
assert resp["errors"]["email"]
assert resp["success"] is False
r = client.patch(
"/api/v1/users/me",
json={"email": "new_email@test.com", "confirm": "password"},
)
assert r.status_code == 200
resp = r.get_json()
assert resp["data"]["email"] == "new_email@test.com"
assert resp["success"] is True
destroy_ctfd(app)
def test_api_user_get_me_solves_not_logged_in():
"""Can a user get /api/v1/users/me/solves if not logged in"""
app = create_ctfd()
with app.app_context():
with app.test_client() as client:
r = client.get("/api/v1/users/me/solves", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_user_get_me_solves_logged_in():
"""Can a user get /api/v1/users/me/solves if logged in"""
app = create_ctfd()
with app.app_context():
register_user(app)
with login_as_user(app) as client:
r = client.get("/api/v1/users/me/solves")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_user_get_solves():
"""Can a user get /api/v1/users/<user_id>/solves if logged in"""
app = create_ctfd(user_mode="users")
with app.app_context():
register_user(app)
with login_as_user(app) as client:
r = client.get("/api/v1/users/2/solves")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_user_get_solves_after_freze_time():
"""Can a user get /api/v1/users/<user_id>/solves after freeze time"""
app = create_ctfd(user_mode="users")
with app.app_context():
register_user(app, name="user1", email="user1@examplectf.com")
register_user(app, name="user2", email="user2@examplectf.com")
# Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
set_config("freeze", "1507262400")
with freeze_time("2017-10-4"):
chal = gen_challenge(app.db)
chal_id = chal.id
gen_solve(app.db, user_id=2, challenge_id=chal_id)
chal2 = gen_challenge(app.db)
chal2_id = chal2.id
with freeze_time("2017-10-8"):
chal2 = gen_solve(app.db, user_id=2, challenge_id=chal2_id)
# There should now be two solves assigned to the same user.
assert Solves.query.count() == 2
# User 2 should have 2 solves when seen by themselves
client = login_as_user(app, name="user1")
r = client.get("/api/v1/users/me/solves")
data = r.get_json()["data"]
assert len(data) == 2
# User 2 should have 1 solve when seen by another user
client = login_as_user(app, name="user2")
r = client.get("/api/v1/users/2/solves")
data = r.get_json()["data"]
assert len(data) == 1
# Admins should see all solves for the user
admin = login_as_user(app, name="admin")
r = admin.get("/api/v1/users/2/solves")
data = r.get_json()["data"]
assert len(data) == 2
destroy_ctfd(app)
def test_api_user_get_me_fails_not_logged_in():
"""Can a user get /api/v1/users/me/fails if not logged in"""
app = create_ctfd()
with app.app_context():
with app.test_client() as client:
r = client.get("/api/v1/users/me/fails", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_user_get_me_fails_logged_in():
"""Can a user get /api/v1/users/me/fails if logged in"""
app = create_ctfd()
with app.app_context():
register_user(app)
with login_as_user(app) as client:
r = client.get("/api/v1/users/me/fails")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_user_get_fails():
"""Can a user get /api/v1/users/<user_id>/fails if logged in"""
app = create_ctfd()
with app.app_context():
register_user(app)
with login_as_user(app) as client:
r = client.get("/api/v1/users/2/fails")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_user_get_fails_after_freze_time():
"""Can a user get /api/v1/users/<user_id>/fails after freeze time"""
app = create_ctfd(user_mode="users")
with app.app_context():
register_user(app, name="user1", email="user1@examplectf.com")
register_user(app, name="user2", email="user2@examplectf.com")
# Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
set_config("freeze", "1507262400")
with freeze_time("2017-10-4"):
chal = gen_challenge(app.db)
chal_id = chal.id
chal2 = gen_challenge(app.db)
chal2_id = chal2.id
gen_fail(app.db, user_id=2, challenge_id=chal_id)
with freeze_time("2017-10-8"):
chal2 = gen_fail(app.db, user_id=2, challenge_id=chal2_id)
# There should now be two fails assigned to the same user.
assert Fails.query.count() == 2
# User 2 should have 2 fail when seen by themselves
client = login_as_user(app, name="user1")
r = client.get("/api/v1/users/me/fails")
assert r.get_json()["meta"]["count"] == 2
# User 2 should have 1 fail when seen by another user
client = login_as_user(app, name="user2")
r = client.get("/api/v1/users/2/fails")
assert r.get_json()["meta"]["count"] == 1
# Admins should see all fails for the user
admin = login_as_user(app, name="admin")
r = admin.get("/api/v1/users/2/fails")
assert r.get_json()["meta"]["count"] == 2
destroy_ctfd(app)
def test_api_user_get_me_awards_not_logged_in():
"""Can a user get /api/v1/users/me/awards if not logged in"""
app = create_ctfd()
with app.app_context():
with app.test_client() as client:
r = client.get("/api/v1/users/me/awards", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_user_get_me_awards_logged_in():
"""Can a user get /api/v1/users/me/awards if logged in"""
app = create_ctfd(user_mode="users")
with app.app_context():
register_user(app)
with login_as_user(app) as client:
r = client.get("/api/v1/users/me/awards")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_user_get_awards():
"""Can a user get /api/v1/users/<user_id>/awards if logged in"""
app = create_ctfd()
with app.app_context():
register_user(app)
with login_as_user(app) as client:
r = client.get("/api/v1/users/2/awards")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_user_get_awards_after_freze_time():
"""Can a user get /api/v1/users/<user_id>/awards after freeze time"""
app = create_ctfd(user_mode="users")
with app.app_context():
register_user(app, name="user1", email="user1@examplectf.com")
register_user(app, name="user2", email="user2@examplectf.com")
# Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
set_config("freeze", "1507262400")
with freeze_time("2017-10-4"):
gen_award(app.db, user_id=2)
with freeze_time("2017-10-8"):
gen_award(app.db, user_id=2)
# There should now be two awards assigned to the same user.
assert Awards.query.count() == 2
# User 2 should have 2 awards when seen by themselves
client = login_as_user(app, name="user1")
r = client.get("/api/v1/users/me/awards")
data = r.get_json()["data"]
assert len(data) == 2
# User 2 should have 1 award when seen by another user
client = login_as_user(app, name="user2")
r = client.get("/api/v1/users/2/awards")
data = r.get_json()["data"]
assert len(data) == 1
# Admins should see all awards for the user
admin = login_as_user(app, name="admin")
r = admin.get("/api/v1/users/2/awards")
data = r.get_json()["data"]
assert len(data) == 2
destroy_ctfd(app)
def test_api_accessing_hidden_users():
"""Hidden users should not be visible to normal users, only to admins"""
app = create_ctfd()
with app.app_context():
register_user(app, name="visible_user", email="visible_user@examplectf.com")
register_user(
app, name="hidden_user", email="hidden_user@examplectf.com"
) # ID 3
user = Users.query.filter_by(name="hidden_user").first()
user.hidden = True
app.db.session.commit()
with login_as_user(app, name="visible_user") as client:
list_users = client.get("/api/v1/users").get_json()["data"]
assert len(list_users) == 1
assert client.get("/api/v1/users/3").status_code == 404
assert client.get("/api/v1/users/3/solves").status_code == 404
assert client.get("/api/v1/users/3/fails").status_code == 404
assert client.get("/api/v1/users/3/awards").status_code == 404
with login_as_user(app, name="admin") as client:
# Admins see the user in lists
list_users = client.get("/api/v1/users?view=admin").get_json()["data"]
assert len(list_users) == 3
assert client.get("/api/v1/users/3").status_code == 200
assert client.get("/api/v1/users/3/solves").status_code == 200
assert client.get("/api/v1/users/3/fails").status_code == 200
assert client.get("/api/v1/users/3/awards").status_code == 200
destroy_ctfd(app)
def test_api_accessing_banned_users():
"""Banned users should not be visible to normal users, only to admins"""
app = create_ctfd()
with app.app_context():
register_user(app, name="visible_user", email="visible_user@examplectf.com")
register_user(
app, name="banned_user", email="banned_user@examplectf.com"
) # ID 3
user = Users.query.filter_by(name="banned_user").first()
user.banned = True
app.db.session.commit()
with login_as_user(app, name="visible_user") as client:
list_users = client.get("/api/v1/users").get_json()["data"]
assert len(list_users) == 1
assert client.get("/api/v1/users/3").status_code == 404
assert client.get("/api/v1/users/3/solves").status_code == 404
assert client.get("/api/v1/users/3/fails").status_code == 404
assert client.get("/api/v1/users/3/awards").status_code == 404
with login_as_user(app, name="admin") as client:
# Admins see the user in lists
list_users = client.get("/api/v1/users?view=admin").get_json()["data"]
assert len(list_users) == 3
assert client.get("/api/v1/users/3").status_code == 200
assert client.get("/api/v1/users/3/solves").status_code == 200
assert client.get("/api/v1/users/3/fails").status_code == 200
assert client.get("/api/v1/users/3/awards").status_code == 200
destroy_ctfd(app)
def test_api_user_send_email():
"""Can an admin post /api/v1/users/<user_id>/email"""
app = create_ctfd()
with app.app_context():
register_user(app)
with login_as_user(app) as client:
r = client.post(
"/api/v1/users/2/email", json={"text": "email should get rejected"}
)
assert r.status_code == 403
with login_as_user(app, "admin") as admin:
r = admin.post(
"/api/v1/users/2/email", json={"text": "email should be accepted"}
)
assert r.get_json() == {
"success": False,
"errors": {"": ["Email settings not configured"]},
}
assert r.status_code == 400
set_config("verify_emails", True)
set_config("mail_server", "localhost")
set_config("mail_port", 25)
set_config("mail_useauth", True)
set_config("mail_username", "username")
set_config("mail_password", "password")
with login_as_user(app, "admin") as admin:
r = admin.post("/api/v1/users/2/email", json={"text": ""})
assert r.get_json() == {
"success": False,
"errors": {"text": ["Email text cannot be empty"]},
}
assert r.status_code == 400
with login_as_user(app, "admin") as admin:
r = admin.post(
"/api/v1/users/2/email", json={"text": "email should be accepted"}
)
# Email should go through but since we aren't mocking
# the server we get a Connection refused error
assert r.status_code == 400
destroy_ctfd(app)
def test_api_user_get_schema():
"""Can a user get /api/v1/users/<user_id> doesn't return unnecessary data"""
app = create_ctfd()
with app.app_context():
register_user(app, name="user1", email="user1@examplectf.com") # ID 2
register_user(app, name="user2", email="user2@examplectf.com") # ID 3
with app.test_client() as client:
r = client.get("/api/v1/users/3")
data = r.get_json()["data"]
assert sorted(data.keys()) == sorted(
UserSchema.views["user"] + ["score", "place"]
)
with login_as_user(app, name="user1") as client:
r = client.get("/api/v1/users/3")
data = r.get_json()["data"]
assert sorted(data.keys()) == sorted(
UserSchema.views["user"] + ["score", "place"]
)
destroy_ctfd(app)
def test_api_user_patch_team_id():
"""Users can't patch their team_id directly"""
app = create_ctfd()
with app.app_context():
register_user(app)
gen_team(app.db)
with login_as_user(app) as client:
data = {
"team_id": 1,
}
r = client.patch("/api/v1/users/me", json=data)
data = r.get_json()
assert data["data"]["team_id"] is None
destroy_ctfd(app)

View File

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from tests.helpers import create_ctfd, destroy_ctfd, login_as_user, register_user
def test_api_hint_404():
"""Are admin protected resources accessible by admins/non-admins"""
app = create_ctfd()
endpoints = [
"/api/v1/configs/{}",
"/api/v1/challenges/types",
"/api/v1/statistics/teams",
"/api/v1/flags/{}",
"/api/v1/statistics/users/{}",
"/api/v1/configs",
"/api/v1/statistics/challenges/solves/percentages",
"/api/v1/statistics/scores/distribution",
"/api/v1/tags/{}",
"/api/v1/pages",
"/api/v1/files/{}",
"/api/v1/challenges/{}/tags",
"/api/v1/hints",
"/api/v1/challenges/{}/files",
"/api/v1/flags",
"/api/v1/submissions/{}",
"/api/v1/challenges/{}/flags",
"/api/v1/awards/{}",
"/api/v1/unlocks",
"/api/v1/challenges/{}/hints",
"/api/v1/statistics/submissions/{}",
"/api/v1/flags/types/{}",
"/api/v1/tags",
"/api/v1/statistics/challenges/{}",
"/api/v1/files",
"/api/v1/flags/types",
"/api/v1/submissions",
"/api/v1/pages/{}",
]
with app.app_context():
register_user(app)
client = login_as_user(app)
for endpoint in endpoints:
r = client.get(endpoint.format(1))
assert r.status_code == 302
assert r.location.startswith("/login")
destroy_ctfd(app)

View File

@@ -0,0 +1,393 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from freezegun import freeze_time
from CTFd.utils import set_config
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_challenge,
gen_user,
login_as_user,
register_user,
)
def test_api_challenge_list_visibility():
"""Can the api load /api/v1/challenges if challenge_visibility is private/public"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-5"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
set_config("challenge_visibility", "public")
with app.test_client() as client:
r = client.get("/api/v1/challenges")
assert r.status_code == 200
set_config("challenge_visibility", "private")
r = client.get("/api/v1/challenges")
assert r.status_code == 302
destroy_ctfd(app)
def test_api_challenge_list_ctftime():
"""Can the api load /api/v1/challenges if ctftime is over"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-7"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
set_config("challenge_visibility", "public")
with app.test_client() as client:
r = client.get("/api/v1/challenges")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_challenge_list_user_visibility():
"""Can the user load /api/v1/challenges if challenge_visibility is private/public"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-5"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
register_user(app)
client = login_as_user(app)
r = client.get("/api/v1/challenges")
assert r.status_code == 200
set_config("challenge_visibility", "public")
r = client.get("/api/v1/challenges")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_challenge_list_user_ctftime():
"""Can the user load /api/v1/challenges if ctftime is over"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-7"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
register_user(app)
client = login_as_user(app)
r = client.get("/api/v1/challenges")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_challenge_list_verified_emails():
"""Can a verified email load /api/v1/challenges"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-5"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
set_config("verify_emails", True)
register_user(app)
client = login_as_user(app)
r = client.get("/api/v1/challenges")
assert r.status_code == 302
gen_user(
app.db,
name="user_name",
email="verified_user@examplectf.com",
password="password",
verified=True,
)
registered_client = login_as_user(app, "user_name", "password")
r = registered_client.get("/api/v1/challenges")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_challenge_visibility():
"""Can the api load /api/v1/challenges/<challenge_id> if challenge_visibility is private/public"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-5"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
set_config("challenge_visibility", "public")
with app.test_client() as client:
gen_challenge(app.db)
r = client.get("/api/v1/challenges/1")
assert r.status_code == 200
set_config("challenge_visibility", "private")
r = client.get("/api/v1/challenges/1")
assert r.status_code == 302
destroy_ctfd(app)
def test_api_challenge_ctftime():
"""Can the api load /api/v1/challenges/<challenge_id> if ctftime is over"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-7"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
set_config("challenge_visibility", "public")
gen_challenge(app.db)
with app.test_client() as client:
r = client.get("/api/v1/challenges/1")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_challenge_user_visibility():
"""Can the user load /api/v1/challenges/<challenge_id> if challenge_visibility is private/public"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-5"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
gen_challenge(app.db)
register_user(app)
client = login_as_user(app)
r = client.get("/api/v1/challenges/1")
assert r.status_code == 200
set_config("challenge_visibility", "public")
r = client.get("/api/v1/challenges/1")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_challenge_user_ctftime():
"""Can the user load /api/v1/challenges/<challenge_id> if ctftime is over"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-7"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
gen_challenge(app.db)
register_user(app)
client = login_as_user(app)
r = client.get("/api/v1/challenges/1")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_challenge_verified_emails():
"""Can a verified email load /api/v1/challenges/<challenge_id>"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-5"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
set_config("verify_emails", True)
gen_challenge(app.db)
gen_user(
app.db,
name="user_name",
email="verified_user@examplectf.com",
password="password",
verified=True,
)
register_user(app)
client = login_as_user(app)
registered_client = login_as_user(app, "user_name", "password")
r = client.get("/api/v1/challenges/1")
assert r.status_code == 302
r = registered_client.get("/api/v1/challenges/1")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_challenge_404():
"""Will a bad <challenge_id> at /api/v1/challenges/<challenge_id> 404"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-5"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
register_user(app)
client = login_as_user(app)
r = client.get("/api/v1/challenges/1")
assert r.status_code == 404
destroy_ctfd(app)
def test_api_challenge_solves_visibility():
"""Can the api load /api/v1/challenges/<challenge_id>/solves if challenge_visibility is private/public"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-5"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
set_config("challenge_visibility", "public")
gen_challenge(app.db)
with app.test_client() as client:
r = client.get("/api/v1/challenges/1/solves")
assert r.status_code == 200
set_config("challenge_visibility", "private")
r = client.get("/api/v1/challenges/1/solves")
assert r.status_code == 302
destroy_ctfd(app)
def test_api_challenge_solves_ctftime():
"""Can the api load /api/v1/challenges/<challenge_id>/solves if ctftime is over"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-7"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
set_config("challenge_visibility", "public")
gen_challenge(app.db)
with app.test_client() as client:
r = client.get("/api/v1/challenges/1/solves")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_challenge_solves_user_visibility():
"""Can the user load /api/v1/challenges/<challenge_id>/solves if challenge_visibility is private/public"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-5"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
gen_challenge(app.db)
register_user(app)
client = login_as_user(app)
r = client.get("/api/v1/challenges/1/solves")
assert r.status_code == 200
set_config("challenge_visibility", "public")
r = client.get("/api/v1/challenges/1/solves")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_challenge_solves_user_ctftime():
"""Can the user load /api/v1/challenges/<challenge_id>/solves if ctftime is over"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-7"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
gen_challenge(app.db)
register_user(app)
client = login_as_user(app)
r = client.get("/api/v1/challenges/1/solves")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_challenge_solves_verified_emails():
"""Can a verified email load /api/v1/challenges/<challenge_id>/solves"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-5"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
set_config("verify_emails", True)
gen_challenge(app.db)
gen_user(
app.db,
name="user_name",
email="verified_user@examplectf.com",
password="password",
verified=True,
)
register_user(app)
client = login_as_user(app)
registered_client = login_as_user(app, "user_name", "password")
r = client.get("/api/v1/challenges/1/solves")
assert r.status_code == 302
r = registered_client.get("/api/v1/challenges/1/solves")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_challenges_solves_score_visibility():
"""Can a user load /api/v1/challenges/<challenge_id>/solves if score_visibility is public/private/admin"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-5"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
set_config("challenge_visibility", "public")
set_config("score_visibility", "public")
gen_challenge(app.db)
with app.test_client() as client:
r = client.get("/api/v1/challenges/1/solves")
assert r.status_code == 200
set_config("challenge_visibility", "private")
set_config("score_visibility", "private")
register_user(app)
private_client = login_as_user(app)
r = private_client.get("/api/v1/challenges/1/solves")
assert r.status_code == 200
set_config("score_visibility", "admins")
admin = login_as_user(app, "admin", "password")
r = admin.get("/api/v1/challenges/1/solves")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_challenge_solves_404():
"""Will a bad <challenge_id> at /api/v1/challenges/<challenge_id>/solves 404"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-5"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
register_user(app)
client = login_as_user(app)
r = client.get("/api/v1/challenges/1/solves")
assert r.status_code == 404
destroy_ctfd(app)

View File

@@ -0,0 +1,285 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from freezegun import freeze_time
from CTFd.models import Hints
from CTFd.utils import set_config
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_award,
gen_challenge,
gen_hint,
login_as_user,
register_user,
)
def test_api_hint_404():
"""Can the users 404 /api/v1/hints/<hint_id> if logged in/out"""
app = create_ctfd()
with app.app_context():
register_user(app)
client = login_as_user(app)
r = client.get("/api/v1/hints/1")
assert r.status_code == 404
destroy_ctfd(app)
def test_api_hint_visibility():
"""Can the users load /api/v1/hints/<hint_id> if logged in/out"""
app = create_ctfd()
with app.app_context():
chal = gen_challenge(app.db)
gen_hint(app.db, chal.id)
with app.test_client() as non_logged_in_user:
r = non_logged_in_user.get("/api/v1/hints/1")
assert r.status_code == 302
register_user(app)
client = login_as_user(app)
r = client.get("/api/v1/hints/1")
assert r.status_code == 200
destroy_ctfd(app)
def test_api_hint_visibility_ctftime():
"""Can the users load /api/v1/hints/<hint_id> if not ctftime"""
app = create_ctfd()
with app.app_context(), freeze_time("2017-10-7"):
set_config(
"start", "1507089600"
) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
set_config(
"end", "1507262400"
) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
chal = gen_challenge(app.db)
gen_hint(app.db, chal.id)
register_user(app)
client = login_as_user(app)
r = client.get("/api/v1/hints/1")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_hint_locked():
"""Can the users unlock /api/v1/hints/<hint_id> if they don't have enough points"""
app = create_ctfd()
with app.app_context():
chal = gen_challenge(app.db)
gen_hint(app.db, chal.id, content="This is a hint", cost=1, type="standard")
register_user(app)
client = login_as_user(app)
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 == 400
destroy_ctfd(app)
def test_api_hint_unlocked():
"""Can the users unlock /api/v1/hints/<hint_id> if they have enough points"""
app = create_ctfd()
with app.app_context():
chal = gen_challenge(app.db)
gen_hint(app.db, chal.id, content="This is a hint", cost=1, type="standard")
register_user(app)
# Give user points with an award
gen_award(app.db, 2)
client = login_as_user(app)
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)
def test_api_hint_double_unlock():
"""Can a target hint be unlocked twice"""
app = create_ctfd()
with app.app_context():
chal = gen_challenge(app.db)
gen_hint(app.db, chal.id, content="This is a hint", cost=1, type="standard")
register_user(app)
# Give user points with an award
gen_award(app.db, 2)
client = login_as_user(app)
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
r = client.post("/api/v1/unlocks", json={"target": 1, "type": "hints"})
assert r.status_code == 400
destroy_ctfd(app)
def test_users_dont_prevent_other_users_from_unlocking_hints():
"""Unlocks from one user don't affect other users"""
app = create_ctfd()
with app.app_context():
chal = gen_challenge(app.db)
gen_hint(app.db, chal.id, content="This is a hint", cost=1, type="standard")
register_user(app)
register_user(app, name="user2", email="user2@examplectf.com")
# Give users points with an award
gen_award(app.db, user_id=2)
gen_award(app.db, user_id=3)
# First user unlocks hints
with login_as_user(app) 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 user unlocks hints
with login_as_user(app, name="user2") 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)
def test_api_hints_admin_access():
"""Can the users access /api/v1/hints if not admin"""
app = create_ctfd()
with app.app_context():
register_user(app)
client = login_as_user(app)
r = client.get("/api/v1/hints")
assert r.status_code == 302
r = client.post("/api/v1/hints", json="")
assert r.status_code == 403
destroy_ctfd(app)
def test_api_hint_admin_access():
"""Can the users patch/delete /api/v1/hint/<hint_id> if not admin"""
app = create_ctfd()
with app.app_context():
chal = gen_challenge(app.db)
gen_hint(app.db, chal.id, content="This is a hint", cost=1, type="standard")
admin = login_as_user(app, "admin")
register_user(app)
client = login_as_user(app)
r = client.patch("/api/v1/hints/1", json="")
assert r.status_code == 403
r = client.delete("/api/v1/hints/1", json="")
assert r.status_code == 403
r_admin = admin.patch("/api/v1/hints/1", json={"cost": 2})
assert r_admin.status_code == 200
r_admin = admin.delete("/api/v1/hints/1", json="")
assert r_admin.status_code == 200
destroy_ctfd(app)
def test_api_hints_accessible_public():
"""Test that hints with no cost and no prerequsites can be viewed publicy"""
app = create_ctfd()
with app.app_context():
# Set challenges to be visible publicly
set_config("challenge_visibility", "public")
register_user(app)
chal = gen_challenge(app.db)
gen_hint(
app.db, chal.id, content="This is a free hint", cost=0, type="standard"
)
gen_hint(
app.db, chal.id, content="This is a private hint", cost=1, type="standard"
)
gen_hint(
app.db, chal.id, content="This is a private hint", cost=1, type="standard"
)
hint = Hints.query.filter_by(id=3).first()
hint.requirements = {"prerequisites": [2]}
app.db.session.commit()
with app.test_client() as non_logged_in_user:
# Hints cannot be seen unless free public access is on
r = non_logged_in_user.get("/api/v1/hints/1")
assert r.status_code == 403
errors = r.get_json()["errors"]
assert errors == {"cost": ["You must login to unlock this hint"]}
# Enable free public access
set_config("hints_free_public_access", True)
r = non_logged_in_user.get("/api/v1/hints/1")
hint = r.get_json()["data"]
assert hint["content"] == "This is a free hint"
r = non_logged_in_user.get("/api/v1/hints/2")
assert r.status_code == 403
errors = r.get_json()["errors"]
assert errors == {"cost": ["You must login to unlock this hint"]}
r = non_logged_in_user.get("/api/v1/hints/3")
assert r.status_code == 403
errors = r.get_json()["errors"]
assert errors == {"cost": ["You must login to unlock this hint"]}
r = non_logged_in_user.post(
"/api/v1/unlocks", json={"target": 2, "type": "hints"}
)
assert r.status_code == 403
# Set challenges to be visible to only authed
set_config("challenge_visibility", "private")
# Free hints no longer visible to unauthed
with app.test_client() as non_logged_in_user:
r = non_logged_in_user.get("/api/v1/hints/1", json="")
assert r.status_code == 403
# Verify existing hint behavior for authed users
with login_as_user(app) as client:
# Free hints require an unlock
r = client.get("/api/v1/hints/1")
hint = r.get_json()["data"]
assert hint.get("content") is None
# Issue the unlock and then request the content
r = client.post("/api/v1/unlocks", json={"target": 1, "type": "hints"})
r = client.get("/api/v1/hints/1")
hint = r.get_json()["data"]
assert hint["content"] == "This is a free hint"
r = client.get("/api/v1/hints/2")
assert r.status_code == 200
assert "content" not in r.get_json()["data"]
r = client.get("/api/v1/hints/3")
assert r.status_code == 403
gen_award(app.db, 2)
# Haven't unlocked the prereq hint
r = client.get("/api/v1/hints/3")
assert r.status_code == 403
# Unlock the prereq
r = client.post("/api/v1/unlocks", json={"target": 2, "type": "hints"})
assert r.status_code == 200
r = client.get("/api/v1/hints/2")
assert r.status_code == 200
# Attempt to unlock again but dont have the points
r = client.get("/api/v1/hints/3")
assert r.status_code == 200
assert "content" not in r.get_json()["data"]
r = client.post("/api/v1/unlocks", json={"target": 3, "type": "hints"})
assert r.status_code == 200
r = client.get("/api/v1/hints/3")
assert r.status_code == 200
assert "content" in r.get_json()["data"]

View File

View File

@@ -0,0 +1,89 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Users
from CTFd.utils import set_config
from tests.helpers import (
create_ctfd,
destroy_ctfd,
login_as_user,
register_user,
simulate_user_activity,
)
def test_api_user_place_score_hidden_if_scores_hidden():
"""/api/v1/users/me should not reveal user place if scores aren't visible"""
app = create_ctfd()
with app.app_context():
register_user(app)
user = Users.query.filter_by(id=2).first()
simulate_user_activity(app.db, user=user)
with login_as_user(app, name="user") as client:
r = client.get("/api/v1/users/me", json="")
resp = r.get_json()
assert resp["data"]["place"] == "1st"
assert resp["data"]["score"] == 200
set_config("score_visibility", "hidden")
with login_as_user(app, name="user") as client:
r = client.get("/api/v1/users/me", json="")
resp = r.get_json()
# Users can see their own score but they cannot see their place
# This is because a user can always sum up their own score but
# they cannot determine their place without social information
assert resp["data"]["place"] is None
assert resp["data"]["score"] == 200
set_config("score_visibility", "admins")
with login_as_user(app, name="user") as client:
r = client.get("/api/v1/users/me", json="")
resp = r.get_json()
# The same behavior as above applies even under admins only score mode
# The rationale is the same. Users can always sum their own score
assert resp["data"]["place"] is None
assert resp["data"]["score"] == 200
with login_as_user(app, name="admin") as client:
r = client.get("/api/v1/users/2", json="")
resp = r.get_json()
assert resp["data"]["place"] == "1st"
assert resp["data"]["score"] is not None
destroy_ctfd(app)
def test_api_public_user_place_score_hidden_if_scores_hidden():
"""/api/v1/users/<user_id> should not reveal user place if scores aren't visible"""
app = create_ctfd()
with app.app_context():
register_user(app)
user = Users.query.filter_by(id=2).first()
simulate_user_activity(app.db, user=user)
with login_as_user(app, name="user") as client:
r = client.get("/api/v1/users/2", json="")
resp = r.get_json()
assert resp["data"]["place"] == "1st"
assert resp["data"]["score"] is not None
set_config("score_visibility", "hidden")
with login_as_user(app, name="user") as client:
r = client.get("/api/v1/users/2", json="")
resp = r.get_json()
assert resp["data"]["place"] is None
assert resp["data"]["score"] is None
set_config("score_visibility", "admins")
with login_as_user(app, name="user") as client:
r = client.get("/api/v1/users/2", json="")
resp = r.get_json()
assert resp["data"]["place"] is None
assert resp["data"]["score"] is None
with login_as_user(app, name="admin") as client:
r = client.get("/api/v1/users/2", json="")
resp = r.get_json()
assert resp["data"]["place"] == "1st"
assert resp["data"]["score"] is not None
destroy_ctfd(app)

View File

@@ -0,0 +1,113 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Users, db
from CTFd.utils.crypto import verify_password
from tests.helpers import create_ctfd, destroy_ctfd, login_as_user, register_user
def test_api_self_ban():
"""PATCH /api/v1/users/<user_id> should not allow a user to ban themselves"""
app = create_ctfd()
with app.app_context():
with login_as_user(app, name="admin") as client:
r = client.patch("/api/v1/users/1", json={"banned": True})
resp = r.get_json()
assert r.status_code == 400
assert resp["success"] == False
assert resp["errors"] == {"id": "You cannot ban yourself"}
destroy_ctfd(app)
def test_api_modify_user_type():
"""Can a user patch /api/v1/users/<user_id> to promote a user to admin and demote them to user"""
app = create_ctfd()
with app.app_context():
register_user(app)
with login_as_user(app, "admin") as client:
r = client.patch("/api/v1/users/2", json={"type": "admin"})
assert r.status_code == 200
user_data = r.get_json()["data"]
assert user_data["name"] == "user"
assert user_data["type"] == "admin"
r = client.patch("/api/v1/users/2", json={"type": "user"})
assert r.status_code == 200
user_data = r.get_json()["data"]
assert user_data["name"] == "user"
assert user_data["type"] == "user"
destroy_ctfd(app)
def test_api_can_query_by_user_emails():
"""Can an admin user query /api/v1/users using a user's email address"""
app = create_ctfd()
with app.app_context():
register_user(app, name="testuser", email="user@findme.com")
with login_as_user(app, "testuser") as client:
r = client.get("/api/v1/users?field=email&q=findme", json=True)
assert r.status_code == 400
assert r.get_json()["errors"].get("field")
with login_as_user(app, "admin") as client:
r = client.get("/api/v1/users?field=email&q=findme", json=True)
assert r.status_code == 200
assert r.get_json()["data"][0]["id"] == 2
destroy_ctfd(app)
def test_api_user_can_update_password_if_none_not_if_set():
"""Can a user set their password if they do not currently have a password"""
app = create_ctfd()
with app.app_context():
# Create a user with a null password. Use raw SQL to bypass SQLAlchemy validates
register_user(app, name="testuser", email="user@examplectf.com")
db.session.execute("UPDATE users SET password=NULL WHERE name='testuser'")
user = Users.query.filter_by(name="testuser").first()
db.session.commit()
assert user.password is None
with app.test_client() as client:
# Login as user
with client.session_transaction() as sess:
sess["id"] = user.id
r = client.get("/api/v1/users/me", json=True)
assert r.status_code == 200
# Test that user can change password
user = Users.query.filter_by(name="testuser").first()
assert user.password is None
data = {"password": "12345", "confirm": "password"}
r = client.patch("/api/v1/users/me", json=data)
assert r.status_code == 200
# Verify password is now set
user = Users.query.filter_by(name="testuser").first()
assert verify_password(plaintext="12345", ciphertext=user.password)
# Verify that password cannot be changed
data = {"password": "noset", "confirm": "password"}
r = client.patch("/api/v1/users/me", json=data)
resp = r.get_json()
assert resp["errors"]["confirm"] == ["Your previous password is incorrect"]
assert r.status_code == 400
# Verify a regular user cannot patch another user
register_user(
app,
name="testuser2",
email="user2@examplectf.com",
password="testinguser",
)
testuser = Users.query.filter_by(name="testuser2").first()
assert verify_password(
plaintext="testinguser", ciphertext=testuser.password
)
data = {"password": "password", "confirm": "password"}
r = client.patch("/api/v1/users/3", json=data)
assert r.status_code == 403
testuser = Users.query.filter_by(name="testuser2").first()
assert verify_password(
plaintext="testinguser", ciphertext=testuser.password
)
destroy_ctfd(app)