Skip to content

Commit 2dbc5e2

Browse files
authored
Add logic key to challenge specification (#190)
* Add `logic` support and add `logic` key to challenge specification * Fix minor issue in attribution field * Show server response when challenges fail to sync or install
1 parent 7393941 commit 2dbc5e2

File tree

2 files changed

+19
-2
lines changed

2 files changed

+19
-2
lines changed

ctfcli/core/challenge.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class Challenge(dict):
4545
# fmt: off
4646
"name", "author", "category", "description", "attribution", "value",
4747
"type", "extra", "image", "protocol", "host",
48-
"connection_info", "healthcheck", "attempts", "flags",
48+
"connection_info", "healthcheck", "attempts", "logic", "flags",
4949
"files", "topics", "tags", "files", "hints",
5050
"requirements", "next", "state", "version",
5151
# fmt: on
@@ -291,6 +291,10 @@ def _get_initial_challenge_payload(self, ignore: Tuple[str] = ()) -> Dict:
291291
if "connection_info" not in ignore:
292292
challenge_payload["connection_info"] = challenge.get("connection_info", None)
293293

294+
if "logic" not in ignore:
295+
if challenge.get("logic"):
296+
challenge_payload["logic"] = challenge.get("logic") or "any"
297+
294298
if "extra" not in ignore:
295299
challenge_payload = {**challenge_payload, **challenge.get("extra", {})}
296300

@@ -552,13 +556,16 @@ def _normalize_challenge(self, challenge_data: Dict[str, Any]):
552556
"type",
553557
"state",
554558
"connection_info",
559+
"logic",
555560
]
556561
for key in copy_keys:
557562
if key in challenge_data:
558563
challenge[key] = challenge_data[key]
559564

560565
challenge["description"] = challenge_data["description"].strip().replace("\r\n", "\n").replace("\t", "")
561-
challenge["attribution"] = challenge_data.get("attribution", "").strip().replace("\r\n", "\n").replace("\t", "")
566+
challenge["attribution"] = challenge_data.get("attribution", "")
567+
if challenge["attribution"]:
568+
challenge["attribution"] = challenge["attribution"].strip().replace("\r\n", "\n").replace("\t", "")
562569
challenge["attempts"] = challenge_data["max_attempts"]
563570

564571
for key in ["initial", "decay", "minimum"]:
@@ -685,6 +692,8 @@ def sync(self, ignore: Tuple[str] = ()) -> None:
685692

686693
# Update simple properties
687694
r = self.api.patch(f"/api/v1/challenges/{self.challenge_id}", json=challenge_payload)
695+
if r.ok is False:
696+
click.secho(f"Failed to sync challenge: ({r.status_code}) {r.text}", fg="red")
688697
r.raise_for_status()
689698

690699
# Update flags
@@ -809,6 +818,8 @@ def create(self, ignore: Tuple[str] = ()) -> None:
809818
challenge_payload[p] = ""
810819

811820
r = self.api.post("/api/v1/challenges", json=challenge_payload)
821+
if r.ok is False:
822+
click.secho(f"Failed to create challenge: ({r.status_code}) {r.text}", fg="red")
812823
r.raise_for_status()
813824

814825
self.challenge_id = r.json()["data"]["id"]

ctfcli/spec/challenge-example.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ healthcheck: writeup/exploit.sh
5151
# Can be removed if unused
5252
attempts: 5
5353

54+
# Specifies flag processing behavior of challenge
55+
# Accept any flag (default): any
56+
# Require all flags to be provided: all
57+
# Require all team members to submit any flag: team
58+
logic: any
59+
5460
# Flags specify answers that your challenge use. You should generally provide at least one.
5561
# Can be removed if unused
5662
# Accepts strings or dictionaries of CTFd API data

0 commit comments

Comments
 (0)