Skip to content

Commit f35c4bc

Browse files
authored
Add topics field to challenge.yml spec and update create and sync challenge functions (#67)
* Closes #47 * Add topics field to `challenge.yml` spec and update create and sync challenge functions * Rename use of `challenge` to `challenge_id`
1 parent 226036f commit f35c4bc

File tree

2 files changed

+61
-13
lines changed

2 files changed

+61
-13
lines changed

ctfcli/spec/challenge-example.yml

+9-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,15 @@ flags:
5959
data: "case_insensitive",
6060
}
6161

62-
# Tags are used to classify your challenge with topics. You should provide at
63-
# least one.
62+
# Topics are used to help tell what techniques/information a challenge involves
63+
# They are generally only visible to admins
64+
# Accepts strings
65+
topics:
66+
- information disclosure
67+
- buffer overflow
68+
- memory forensics
69+
70+
# Tags are used to provide additional public tagging to a challenge
6471
# Can be removed if unused
6572
# Accepts strings
6673
tags:

ctfcli/utils/challenge.py

+52-11
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,38 @@ def sync_challenge(challenge, ignore=[]):
7676
r.raise_for_status()
7777
for flag in challenge["flags"]:
7878
if type(flag) == str:
79-
data = {"content": flag, "type": "static", "challenge": challenge_id}
79+
data = {"content": flag, "type": "static", "challenge_id": challenge_id}
8080
r = s.post(f"/api/v1/flags", json=data)
8181
r.raise_for_status()
8282
elif type(flag) == dict:
83-
flag["challenge"] = challenge_id
83+
flag["challenge_id"] = challenge_id
8484
r = s.post(f"/api/v1/flags", json=flag)
8585
r.raise_for_status()
8686

87+
# Update topics
88+
if challenge.get("topics") and "topics" not in ignore:
89+
# Delete existing challenge topics
90+
current_topics = s.get(
91+
f"/api/v1/challenges/{challenge_id}/topics", json=""
92+
).json()["data"]
93+
for topic in current_topics:
94+
topic_id = topic["id"]
95+
r = s.delete(
96+
f"/api/v1/topics?type=challenge&target_id={topic_id}", json=True
97+
)
98+
r.raise_for_status()
99+
# Add new challenge topics
100+
for topic in challenge["topics"]:
101+
r = s.post(
102+
f"/api/v1/topics",
103+
json={
104+
"value": topic,
105+
"type": "challenge",
106+
"challenge_id": challenge_id,
107+
},
108+
)
109+
r.raise_for_status()
110+
87111
# Update tags
88112
if challenge.get("tags") and "tags" not in ignore:
89113
# Delete existing tags
@@ -94,7 +118,9 @@ def sync_challenge(challenge, ignore=[]):
94118
r = s.delete(f"/api/v1/tags/{tag_id}", json=True)
95119
r.raise_for_status()
96120
for tag in challenge["tags"]:
97-
r = s.post(f"/api/v1/tags", json={"challenge": challenge_id, "value": tag})
121+
r = s.post(
122+
f"/api/v1/tags", json={"challenge_id": challenge_id, "value": tag}
123+
)
98124
r.raise_for_status()
99125

100126
# Upload files
@@ -119,7 +145,7 @@ def sync_challenge(challenge, ignore=[]):
119145
click.secho(f"File {file_path} was not found", fg="red")
120146
raise Exception(f"File {file_path} was not found")
121147

122-
data = {"challenge": challenge_id, "type": "challenge"}
148+
data = {"challenge_id": challenge_id, "type": "challenge"}
123149
# Specifically use data= here instead of json= to send multipart/form-data
124150
r = s.post(f"/api/v1/files", files=files, data=data)
125151
r.raise_for_status()
@@ -136,12 +162,12 @@ def sync_challenge(challenge, ignore=[]):
136162

137163
for hint in challenge["hints"]:
138164
if type(hint) == str:
139-
data = {"content": hint, "cost": 0, "challenge": challenge_id}
165+
data = {"content": hint, "cost": 0, "challenge_id": challenge_id}
140166
else:
141167
data = {
142168
"content": hint["content"],
143169
"cost": hint["cost"],
144-
"challenge": challenge_id,
170+
"challenge_id": challenge_id,
145171
}
146172

147173
r = s.post(f"/api/v1/hints", json=data)
@@ -205,18 +231,33 @@ def create_challenge(challenge, ignore=[]):
205231
if challenge.get("flags") and "flags" not in ignore:
206232
for flag in challenge["flags"]:
207233
if type(flag) == str:
208-
data = {"content": flag, "type": "static", "challenge": challenge_id}
234+
data = {"content": flag, "type": "static", "challenge_id": challenge_id}
209235
r = s.post(f"/api/v1/flags", json=data)
210236
r.raise_for_status()
211237
elif type(flag) == dict:
212238
flag["challenge"] = challenge_id
213239
r = s.post(f"/api/v1/flags", json=flag)
214240
r.raise_for_status()
215241

242+
# Create topics
243+
if challenge.get("topics") and "topics" not in ignore:
244+
for topic in challenge["topics"]:
245+
r = s.post(
246+
f"/api/v1/topics",
247+
json={
248+
"value": topic,
249+
"type": "challenge",
250+
"challenge_id": challenge_id,
251+
},
252+
)
253+
r.raise_for_status()
254+
216255
# Create tags
217256
if challenge.get("tags") and "tags" not in ignore:
218257
for tag in challenge["tags"]:
219-
r = s.post(f"/api/v1/tags", json={"challenge": challenge_id, "value": tag})
258+
r = s.post(
259+
f"/api/v1/tags", json={"challenge_id": challenge_id, "value": tag}
260+
)
220261
r.raise_for_status()
221262

222263
# Upload files
@@ -231,7 +272,7 @@ def create_challenge(challenge, ignore=[]):
231272
click.secho(f"File {file_path} was not found", fg="red")
232273
raise Exception(f"File {file_path} was not found")
233274

234-
data = {"challenge": challenge_id, "type": "challenge"}
275+
data = {"challenge_id": challenge_id, "type": "challenge"}
235276
# Specifically use data= here instead of json= to send multipart/form-data
236277
r = s.post(f"/api/v1/files", files=files, data=data)
237278
r.raise_for_status()
@@ -240,12 +281,12 @@ def create_challenge(challenge, ignore=[]):
240281
if challenge.get("hints") and "hints" not in ignore:
241282
for hint in challenge["hints"]:
242283
if type(hint) == str:
243-
data = {"content": hint, "cost": 0, "challenge": challenge_id}
284+
data = {"content": hint, "cost": 0, "challenge_id": challenge_id}
244285
else:
245286
data = {
246287
"content": hint["content"],
247288
"cost": hint["cost"],
248-
"challenge": challenge_id,
289+
"challenge_id": challenge_id,
249290
}
250291

251292
r = s.post(f"/api/v1/hints", json=data)

0 commit comments

Comments
 (0)