Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set challenge attribution #164

Merged
merged 3 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
- name: Build package
run: poetry build

- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
path: |
./dist/*.tar.gz
Expand Down
12 changes: 7 additions & 5 deletions ctfcli/core/challenge.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def str_presenter(dumper, data):
class Challenge(dict):
key_order = [
# fmt: off
"name", "author", "category", "description", "value",
"name", "author", "category", "description", "attribution", "value",
"type", "extra", "image", "protocol", "host",
"connection_info", "healthcheck", "attempts", "flags",
"files", "topics", "tags", "files", "hints",
Expand Down Expand Up @@ -262,6 +262,7 @@ def _get_initial_challenge_payload(self, ignore: Tuple[str] = ()) -> Dict:
"name": self["name"],
"category": self.get("category", ""),
"description": self.get("description", ""),
"attribution": self.get("attribution", ""),
"type": self.get("type", "standard"),
# Hide the challenge for the duration of the sync / creation
"state": "hidden",
Expand Down Expand Up @@ -459,12 +460,13 @@ def normalize_requirements(requirements):
def _normalize_challenge(self, challenge_data: Dict[str, Any]):
challenge = {}

copy_keys = ["name", "category", "value", "type", "state", "connection_info"]
copy_keys = ["name", "category", "attribution", "value", "type", "state", "connection_info"]
for key in copy_keys:
if key in challenge_data:
challenge[key] = challenge_data[key]

challenge["description"] = challenge_data["description"].strip().replace("\r\n", "\n").replace("\t", "")
challenge["attribution"] = challenge_data.get("attribution", "").strip().replace("\r\n", "\n").replace("\t", "")
challenge["attempts"] = challenge_data["max_attempts"]

for key in ["initial", "decay", "minimum"]:
Expand Down Expand Up @@ -556,7 +558,7 @@ def sync(self, ignore: Tuple[str] = ()) -> None:
remote_challenge = self.load_installed_challenge(self.challenge_id)

# if value, category, type or description are ignored, revert them to the remote state in the initial payload
reset_properties_if_ignored = ["value", "category", "type", "description"]
reset_properties_if_ignored = ["value", "category", "type", "description", "attribution"]
for p in reset_properties_if_ignored:
if p in ignore:
challenge_payload[p] = remote_challenge[p]
Expand Down Expand Up @@ -670,7 +672,7 @@ def create(self, ignore: Tuple[str] = ()) -> None:
# value is required (unless the challenge is a dynamic value challenge),
# and the type will default to standard
# if category or description are ignored, set them to an empty string
reset_properties_if_ignored = ["category", "description"]
reset_properties_if_ignored = ["category", "description", "attribution"]
for p in reset_properties_if_ignored:
if p in ignore:
challenge_payload[p] = ""
Expand Down Expand Up @@ -716,7 +718,7 @@ def lint(self, skip_hadolint=False, flag_format="flag{") -> bool:
issues = {"fields": [], "dockerfile": [], "hadolint": [], "files": []}

# Check if required fields are present
for field in ["name", "author", "category", "description", "value"]:
for field in ["name", "author", "category", "description", "attribution", "value"]:
# value is allowed to be none if the challenge type is dynamic
if field == "value" and challenge.get("type") == "dynamic":
continue
Expand Down
30 changes: 26 additions & 4 deletions tests/core/test_challenge.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ def test_updates_simple_properties(self, mock_api_constructor: MagicMock, *args,
"name": "Test Challenge",
"category": "New Test",
"description": "New Test Description",
"attribution": "New Test Attribution",
"type": "standard",
"value": 150,
"state": "hidden",
Expand Down Expand Up @@ -239,6 +240,7 @@ def test_updates_attempts(self, mock_api_constructor: MagicMock, *args, **kwargs
"name": "Test Challenge",
"category": "New Test",
"description": "New Test Description",
"attribution": "New Test Attribution",
"type": "standard",
"value": 150,
"state": "hidden",
Expand Down Expand Up @@ -284,6 +286,7 @@ def test_updates_extra_properties(self, mock_api_constructor: MagicMock, *args,
"name": "Test Challenge",
"category": "New Test",
"description": "New Test Description",
"attribution": "New Test Attribution",
"value": 150,
"state": "hidden",
"type": "application_target",
Expand Down Expand Up @@ -342,6 +345,7 @@ def test_updates_flags(self, mock_api_constructor: MagicMock, *args, **kwargs):
"name": "Test Challenge",
"category": "New Test",
"description": "New Test Description",
"attribution": "New Test Attribution",
"type": "standard",
"value": 150,
"state": "hidden",
Expand Down Expand Up @@ -430,6 +434,7 @@ def test_updates_topics(self, mock_api_constructor: MagicMock, *args, **kwargs):
"name": "Test Challenge",
"category": "New Test",
"description": "New Test Description",
"attribution": "New Test Attribution",
"type": "standard",
"value": 150,
"state": "hidden",
Expand Down Expand Up @@ -490,6 +495,7 @@ def test_updates_tags(self, mock_api_constructor: MagicMock, *args, **kwargs):
"name": "Test Challenge",
"category": "New Test",
"description": "New Test Description",
"attribution": "New Test Attribution",
"type": "standard",
"value": 150,
"state": "hidden",
Expand Down Expand Up @@ -553,6 +559,7 @@ def test_updates_files(self, mock_api_constructor: MagicMock, *args, **kwargs):
"name": "Test Challenge",
"category": "New Test",
"description": "New Test Description",
"attribution": "New Test Attribution",
"type": "standard",
"value": 150,
"state": "hidden",
Expand Down Expand Up @@ -654,6 +661,7 @@ def test_updates_hints(self, mock_api_constructor: MagicMock, *args, **kwargs):
"name": "Test Challenge",
"category": "New Test",
"description": "New Test Description",
"attribution": "New Test Attribution",
"type": "standard",
"value": 150,
"state": "hidden",
Expand Down Expand Up @@ -714,6 +722,7 @@ def test_updates_requirements(self, mock_api_constructor: MagicMock, *args, **kw
"name": "Test Challenge",
"category": "New Test",
"description": "New Test Description",
"attribution": "New Test Attribution",
"type": "standard",
"value": 150,
"state": "hidden",
Expand Down Expand Up @@ -756,6 +765,7 @@ def test_challenge_cannot_require_itself(
"name": "Test Challenge",
"category": "New Test",
"description": "New Test Description",
"attribution": "New Test Attribution",
"type": "standard",
"value": 150,
"state": "hidden",
Expand Down Expand Up @@ -816,6 +826,7 @@ def test_defaults_to_standard_challenge_type(self, mock_api_constructor: MagicMo
"name": "Test Challenge",
"category": "New Test",
"description": "New Test Description",
"attribution": "New Test Attribution",
"type": "standard",
"value": 150,
"state": "hidden",
Expand Down Expand Up @@ -854,6 +865,7 @@ def test_defaults_to_visible_state(self, mock_api_constructor: MagicMock, *args,
"name": "Test Challenge",
"category": "New Test",
"description": "New Test Description",
"attribution": "New Test Attribution",
"type": "standard",
"value": 150,
"max_attempts": 0,
Expand Down Expand Up @@ -904,6 +916,7 @@ def test_does_not_update_dynamic_value(self, mock_api_constructor: MagicMock, *a
"name": "Test Challenge",
"category": "New Test",
"description": "New Test Description",
"attribution": "New Test Attribution",
"type": "dynamic",
"state": "hidden",
"max_attempts": 0,
Expand Down Expand Up @@ -961,6 +974,7 @@ def test_updates_multiple_attributes_at_once(self, mock_api_constructor: MagicMo
"name": "Test Challenge",
"category": "New Test",
"description": "New Test Description",
"attribution": "New Test Attribution",
"type": "standard",
"value": 150,
"state": "hidden",
Expand Down Expand Up @@ -1018,7 +1032,7 @@ def test_does_not_update_ignored_attributes(self):
properties = [
# fmt: off
# simple types
"category", "description", "type", "value", "attempts", "connection_info", "state",
"category", "description", "attribution", "type", "value", "attempts", "connection_info", "state",
# complex types
"extra", "flags", "topics", "tags", "files", "hints", "requirements",
# fmt: on
Expand All @@ -1028,6 +1042,7 @@ def test_does_not_update_ignored_attributes(self):
"name": "Test Challenge",
"category": "Old Category",
"description": "Old Description",
"attribution": "Old Attribution",
"type": "some-custom-type",
"value": 100,
"state": "visible",
Expand Down Expand Up @@ -1057,6 +1072,7 @@ def test_does_not_update_ignored_attributes(self):
"name": "Test Challenge",
"category": "New Test",
"description": "New Test Description",
"attribution": "New Test Attribution",
"type": "standard",
"value": 150,
"state": "hidden",
Expand All @@ -1072,7 +1088,7 @@ def test_does_not_update_ignored_attributes(self):
expected_challenge_payload["value"] = remote_installed_challenge["value"]
challenge["value"] = 200

if p in ["category", "description", "type"]:
if p in ["category", "description", "attribution", "type"]:
expected_challenge_payload[p] = remote_installed_challenge[p]
challenge[p] = "new-value"

Expand Down Expand Up @@ -1154,6 +1170,7 @@ def test_creates_standard_challenge(self, mock_api_constructor: MagicMock, *args
"name": "Test Challenge",
"category": "Test",
"description": "Test Description",
"attribution": "Test Attribution",
"value": 150,
"max_attempts": 5,
"type": "standard",
Expand Down Expand Up @@ -1244,7 +1261,7 @@ def test_exits_if_files_do_not_exist(self, mock_api_constructor: MagicMock, *arg
def test_does_not_set_ignored_attributes(self):
# fmt:off
properties = [
"value", "category", "description", "attempts", "connection_info", "state", # simple types
"value", "category", "description", "attribution", "attempts", "connection_info", "state", # simple types
"extra", "flags", "topics", "tags", "files", "hints", "requirements" # complex types
]
# fmt:on
Expand All @@ -1262,6 +1279,7 @@ def test_does_not_set_ignored_attributes(self):
"name": "Test Challenge",
"category": "New Test",
"description": "New Test Description",
"attribution": "New Test Attribution",
"type": "standard",
"value": 150,
"state": "hidden",
Expand All @@ -1282,7 +1300,7 @@ def test_does_not_set_ignored_attributes(self):
expected_challenge_payload[p] = "custom-type"

# expect these to be in the payload, with the defaults or empty:
if p in ["category", "description"]:
if p in ["category", "description", "attribution"]:
challenge[p] = "new-value"
expected_challenge_payload[p] = ""

Expand Down Expand Up @@ -1520,6 +1538,7 @@ def mock_get(self, *args, **kwargs):
"name": "Test Challenge",
"value": 150,
"description": "Test Description",
"attribution": "Test Attribution",
"connection_info": "https://example.com",
"next_id": None,
"category": "Test",
Expand Down Expand Up @@ -1681,6 +1700,7 @@ def test_normalize_fetches_and_normalizes_challenge(self, mock_api_constructor:
"name": "Test Challenge",
"category": "Test",
"description": "Test Description",
"attribution": "Test Attribution",
"value": 150,
"max_attempts": 5,
"type": "standard",
Expand All @@ -1703,6 +1723,7 @@ def test_normalize_fetches_and_normalizes_challenge(self, mock_api_constructor:
"state": "hidden",
"connection_info": "https://example.com",
"description": "Test Description",
"attribution": "Test Attribution",
"attempts": 5,
"flags": [
"flag{test-flag}",
Expand Down Expand Up @@ -1755,6 +1776,7 @@ def test_mirror_challenge(self, mock_api_constructor: MagicMock):
{
"value": 200,
"description": "other description",
"attribution": "other attribution",
"connection_info": "https://other.example.com",
"flags": ["flag{other-flag}", "other-flag"],
"topics": ["other-topic-1", "other-topic-2"],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
name: Test Challenge
category: New Test
description: New Test Description
attribution: New Test Attribution
value: 150
author: Test
type: standard
state: hidden
image: .
protocol: http
protocol: http
3 changes: 2 additions & 1 deletion tests/fixtures/challenges/test-challenge-files/challenge.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
name: Test Challenge
category: New Test
description: New Test Description
attribution: New Test Attribution
value: 150
author: Test
type: standard
state: hidden

files:
- files/test.png
- files/test.pdf
- files/test.pdf
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Test Challenge
category: Test
description: Test Description
attribution: Test Attribution
value: 150
author: Test
type: standard
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: Test Challenge
category: New Test
description: New Test Description
attribution: New Test Attribution
value: 150
author: Test
type: standard
state: hidden
image: .
image: .
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name: Test Challenge
category: New Test
description: New Test Description
attribution: New Test Attribution
value: 150
author: Test
type: standard
state: hidden
state: hidden