Files
CTFd/tests/challenges/test_challenge_logic.py
gkr 2e06f92c64
Some checks are pending
Linting / Linting (3.11) (push) Waiting to run
Mirror core-theme / mirror (push) Waiting to run
init CTFd source
2025-12-25 09:39:21 +08:00

276 lines
11 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from CTFd.models import Challenges
from tests.helpers import (
create_ctfd,
destroy_ctfd,
gen_flag,
gen_team,
login_as_user,
register_user,
)
def test_all_flags_challenge_logic():
"""Test a challenge that requires all flags to be submitted for solving"""
app = create_ctfd()
with app.app_context():
register_user(app, name="user1")
admin = login_as_user(app, name="admin", password="password")
# Create a challenge
challenge_data = {
"name": "All Flags Challenge",
"category": "Logic",
"description": "Submit all flags to solve.",
"value": 100,
"state": "visible",
"type": "standard",
"logic": "all",
}
r = admin.post("/api/v1/challenges", json=challenge_data)
challenge_id = r.get_json()["data"]["id"]
client = login_as_user(app, name="user1", password="password")
# Add multiple flags to the challenge
flags = ["flag{one}", "flag{two}", "flag{three}"]
for content in flags:
gen_flag(app.db, challenge_id=challenge_id, content=content)
# Simulate "all flags" logic: user must submit all flags to solve
# Submit only one flag
submission = {"challenge_id": challenge_id, "submission": "flag{one}"}
r = client.post("/api/v1/challenges/attempt", json=submission)
assert r.get_json()["data"]["status"] == "partial"
# Submit two flags (simulate by submitting them one after another)
submission = {"challenge_id": challenge_id, "submission": "flag{two}"}
r = client.post("/api/v1/challenges/attempt", json=submission)
assert r.get_json()["data"]["status"] == "partial"
# Submit all three flags
submission = {"challenge_id": challenge_id, "submission": "flag{three}"}
r = client.post("/api/v1/challenges/attempt", json=submission)
assert r.get_json()["data"]["status"] == "correct"
# After all flags are submitted, the challenge should be marked as solved
r = client.get(f"/api/v1/challenges/{challenge_id}/solves")
solves = r.get_json()["data"]
assert len(solves) == 1
assert solves[0]["account_id"] == 2 # user1's ID (admin is 1)
destroy_ctfd(app)
def test_all_flags_challenge_logic_teams_mode():
"""Test a challenge that requires all flags to be submitted for solving in teams mode"""
app = create_ctfd(user_mode="teams")
with app.app_context():
# Create admin for challenge creation
admin = login_as_user(app, name="admin", password="password")
# Create a challenge with "all" logic
challenge_data = {
"name": "All Flags Team Challenge",
"category": "Logic",
"description": "Submit all flags to solve as a team.",
"value": 100,
"state": "visible",
"type": "standard",
"logic": "all",
}
r = admin.post("/api/v1/challenges", json=challenge_data)
challenge_id = r.get_json()["data"]["id"]
c = Challenges.query.filter_by(id=challenge_id).first()
assert c.logic == "all"
# Create a team with members using gen_team helper
team = gen_team(
app.db, name="test_team", email="team@examplectf.com", member_count=3
)
team_id = team.id
# Add multiple flags to the challenge
flags = ["flag{team_one}", "flag{team_two}", "flag{team_three}"]
for content in flags:
gen_flag(app.db, challenge_id=challenge_id, content=content)
# Get team members for testing
members = team.members
user1_name = members[0].name
user2_name = members[1].name
user3_name = members[2].name
# Test that different team members can submit flags and it counts towards team progress
# User1 submits first flag
client1 = login_as_user(app, name=user1_name, password="password")
submission = {"challenge_id": challenge_id, "submission": "flag{team_one}"}
r = client1.post("/api/v1/challenges/attempt", json=submission)
assert r.get_json()["data"]["status"] == "partial"
# User2 submits second flag
client2 = login_as_user(app, name=user2_name, password="password")
submission = {"challenge_id": challenge_id, "submission": "flag{team_two}"}
r = client2.post("/api/v1/challenges/attempt", json=submission)
assert r.get_json()["data"]["status"] == "partial"
# User3 submits third flag - should complete the challenge for the team
client3 = login_as_user(app, name=user3_name, password="password")
submission = {"challenge_id": challenge_id, "submission": "flag{team_three}"}
r = client3.post("/api/v1/challenges/attempt", json=submission)
assert r.get_json()["data"]["status"] == "correct"
# Check that the team is marked as having solved the challenge
r = admin.get(f"/api/v1/challenges/{challenge_id}/solves")
solves = r.get_json()["data"]
assert len(solves) == 1
assert solves[0]["account_id"] == team_id
# Verify team score reflects the solve
team_response = admin.get(f"/api/v1/teams/{team_id}")
team_data = team_response.get_json()["data"]
assert team_data["score"] == 100
# Test that another team member trying to submit the same flag gets "already_solved"
submission = {"challenge_id": challenge_id, "submission": "flag{team_one}"}
r = client1.post("/api/v1/challenges/attempt", json=submission)
assert r.get_json()["data"]["status"] == "already_solved"
destroy_ctfd(app)
def test_group_flags_challenge_logic_teams_mode():
"""Test a challenge that requires each team member to submit any flag for solving in teams mode"""
app = create_ctfd(user_mode="teams")
with app.app_context():
# Create admin for challenge creation
admin = login_as_user(app, name="admin", password="password")
# Create a challenge with "group" logic
challenge_data = {
"name": "Group Flags Team Challenge",
"category": "Logic",
"description": "Each team member must submit any flag to solve as a team.",
"value": 100,
"state": "visible",
"type": "standard",
"logic": "team",
}
r = admin.post("/api/v1/challenges", json=challenge_data)
challenge_id = r.get_json()["data"]["id"]
c = Challenges.query.filter_by(id=challenge_id).first()
assert c.logic == "team"
# Create a team with members using gen_team helper
team = gen_team(
app.db, name="test_team", email="team@examplectf.com", member_count=3
)
team_id = team.id
# Add multiple flags to the challenge
flags = ["flag{group_one}", "flag{group_two}", "flag{group_three}"]
for content in flags:
gen_flag(app.db, challenge_id=challenge_id, content=content)
# Get team members for testing
members = team.members
user1_name = members[0].name
user2_name = members[1].name
user3_name = members[2].name
# Test that each team member must submit any flag for the team to solve
# User1 submits first flag - should be partial since not all members have submitted
client1 = login_as_user(app, name=user1_name, password="password")
submission = {"challenge_id": challenge_id, "submission": "flag{group_one}"}
r = client1.post("/api/v1/challenges/attempt", json=submission)
assert r.get_json()["data"]["status"] == "partial"
# User2 submits any flag (can be different) - still partial since user3 hasn't submitted
client2 = login_as_user(app, name=user2_name, password="password")
submission = {"challenge_id": challenge_id, "submission": "flag{group_two}"}
r = client2.post("/api/v1/challenges/attempt", json=submission)
assert r.get_json()["data"]["status"] == "partial"
# User3 submits any flag - should complete the challenge since all members have now submitted
client3 = login_as_user(app, name=user3_name, password="password")
submission = {
"challenge_id": challenge_id,
"submission": "flag{group_one}",
} # Can reuse same flag
r = client3.post("/api/v1/challenges/attempt", json=submission)
assert r.get_json()["data"]["status"] == "correct"
# Check that the team is marked as having solved the challenge
r = admin.get(f"/api/v1/challenges/{challenge_id}/solves")
solves = r.get_json()["data"]
assert len(solves) == 1
assert solves[0]["account_id"] == team_id
# Verify team score reflects the solve
team_response = admin.get(f"/api/v1/teams/{team_id}")
team_data = team_response.get_json()["data"]
assert team_data["score"] == 100
# Test that team members trying to submit again get "already_solved"
submission = {"challenge_id": challenge_id, "submission": "flag{group_three}"}
r = client1.post("/api/v1/challenges/attempt", json=submission)
assert r.get_json()["data"]["status"] == "already_solved"
destroy_ctfd(app)
def test_challenge_default_logic_is_any():
"""Test that the default logic for a challenge is 'any'"""
app = create_ctfd()
with app.app_context():
register_user(app, name="user1")
admin = login_as_user(app, name="admin", password="password")
# Create a challenge without specifying logic (should default to "any")
challenge_data = {
"name": "Default Logic Challenge",
"category": "Test",
"description": "Test challenge with default logic.",
"value": 100,
"state": "visible",
"type": "standard",
# Note: no "logic" field specified - should default to "any"
}
r = admin.post("/api/v1/challenges", json=challenge_data)
challenge_id = r.get_json()["data"]["id"]
# Verify the challenge logic is set to "any" by default
challenge = Challenges.query.filter_by(id=challenge_id).first()
assert challenge.logic == "any"
# Test that "any" logic works correctly
client = login_as_user(app, name="user1", password="password")
# Add multiple flags to the challenge
flags = ["flag{first}", "flag{second}", "flag{third}"]
for content in flags:
gen_flag(app.db, challenge_id=challenge_id, content=content)
# With "any" logic, submitting any single correct flag should solve the challenge
submission = {"challenge_id": challenge_id, "submission": "flag{second}"}
r = client.post("/api/v1/challenges/attempt", json=submission)
assert r.get_json()["data"]["status"] == "correct"
# Verify the challenge is marked as solved
r = admin.get(f"/api/v1/challenges/{challenge_id}/solves")
solves = r.get_json()["data"]
assert len(solves) == 1
assert solves[0]["account_id"] == 2 # user1's ID (admin is 1)
# Test that submitting another flag returns "already_solved"
submission = {"challenge_id": challenge_id, "submission": "flag{first}"}
r = client.post("/api/v1/challenges/attempt", json=submission)
assert r.get_json()["data"]["status"] == "already_solved"
destroy_ctfd(app)