Skip to content

Commit

Permalink
WIP: fix(api)!: standard REST API
Browse files Browse the repository at this point in the history
  • Loading branch information
NicoFgrx committed Jan 13, 2025
1 parent 64d7ca0 commit 668abba
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 57 deletions.
54 changes: 21 additions & 33 deletions api.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,8 @@ def get():
'message': f"Error while communicating with CM : {e}",
}}

return {'success': True, 'data': {
'message': json.loads(r.text),
}}

return {'success': True, 'data': json.loads(r.text)}

@staticmethod
@admins_only
def post():
Expand Down Expand Up @@ -112,16 +110,15 @@ def post():
finally:
lock.admin_unlock()

return {'success': True, 'data': {
'message': json.loads(r.text),
}}
return {'success': True, 'data': json.loads(r.text)}

@staticmethod
@admins_only
def patch():
# mandatory
challengeId = request.args.get("challengeId")
sourceId = request.args.get("sourceId")
data = request.get_json()
challengeId = data.get("challengeId")
sourceId = data.get("sourceId")

adminId = str(current_user.get_current_user().id)
logger.info(f"Admin {adminId} request instance update for challengeId: {challengeId}, sourceId: {sourceId}")
Expand All @@ -136,16 +133,15 @@ def patch():
'message': f"Error while communicating with CM : {e}",
}}

return {'success': True, 'data': {
'message': json.loads(r.text),
}}
return {'success': True, 'data': json.loads(r.text)}

@staticmethod
@admins_only
def delete():
# mandatory
challengeId = request.args.get("challengeId")
sourceId = request.args.get("sourceId")
data = request.get_json()
challengeId = data.get("challengeId")
sourceId = data.get("sourceId")

cm_mana_total = get_config("chall-manager:chall-manager_mana_total")

Expand Down Expand Up @@ -178,9 +174,7 @@ def delete():
finally:
lock.admin_unlock()

return {'success': True, 'data': {
'message': json.loads(r.text),
}}
return {'success': True, 'data': json.loads(r.text)}


# region UserInstance
Expand Down Expand Up @@ -231,9 +225,7 @@ def get():
if 'until' in result.keys():
data['until'] = result['until']

return {'success': True, 'data': {
'message': data,
}}
return {'success': True, 'data': data}

@staticmethod
@authed_only
Expand Down Expand Up @@ -319,16 +311,15 @@ def post():
if 'until' in result.keys():
data['until'] = result['until']

return {'success': True, 'data': {
'message': data,
}}
return {'success': True, 'data': data}

@staticmethod
@authed_only
@challenge_visible
def patch():
# mandatory
challengeId = request.args.get("challengeId")
data = request.get_json()
challengeId = data.get("challengeId")

# check userMode of CTFd
sourceId = str(current_user.get_current_user().id)
Expand Down Expand Up @@ -359,9 +350,7 @@ def patch():
'message': f"Error while communicating with CM : {e}",
}}

return {'success': True, 'data': {
'message': f"{r.status_code}",
}}
return {'success': True, 'data': {}}

@staticmethod
@authed_only
Expand All @@ -370,7 +359,8 @@ def delete():
# retrieve all instances deployed by chall-manager
cm_mana_total = get_config("chall-manager:chall-manager_mana_total")

challengeId = request.args.get("challengeId")
data = request.get_json()
challengeId = data.get("challengeId")

# check userMode of CTFd
sourceId = str(current_user.get_current_user().id)
Expand Down Expand Up @@ -412,9 +402,7 @@ def delete():
delete_coupon(challengeId, sourceId)
logger.info(f"Coupon deleted for challengeId: {challengeId}, sourceId: {sourceId}")

return {'success': True, 'data': {
'message': json.loads(r.text),
}}
return {'success': True, 'data': {}}


# region UserMana
Expand Down Expand Up @@ -442,7 +430,7 @@ def get():
lock.player_unlock()

return {'success': True, 'data': {
'mana_used': f"{mana}",
'mana_total': f"{get_config('chall-manager:chall-manager_mana_total')}",
'used': f"{mana}",
'total': f"{get_config('chall-manager:chall-manager_mana_total')}",
}}

18 changes: 14 additions & 4 deletions assets/instances.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
async function delete_instance(challengeId, sourceId) {
let response = await CTFd.fetch("/api/v1/plugins/ctfd-chall-manager/admin/instance?challengeId=" + challengeId + "&sourceId=" + sourceId, {
let params = {
"challengeId": challengeId.toString(),
"sourceId": sourceId.toString()
};
let response = await CTFd.fetch("/api/v1/plugins/ctfd-chall-manager/admin/instance" , {
method: "DELETE",
credentials: "same-origin",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
}
},
body: JSON.stringify(params)
});
console.log(response)
response = await response.json();
return response;
}

async function renew_instance(challengeId, sourceId) {
let response = await CTFd.fetch("/api/v1/plugins/ctfd-chall-manager/admin/instance?challengeId=" + challengeId + "&sourceId=" + sourceId, {
let params = {
"challengeId": challengeId.toString(),
"sourceId": sourceId.toString()
};
let response = await CTFd.fetch("/api/v1/plugins/ctfd-chall-manager/admin/instance", {
method: "PATCH",
credentials: "same-origin",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
}
},
body: JSON.stringify(params)
});
console.log(response)
response = await response.json();
Expand Down
6 changes: 4 additions & 2 deletions assets/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,14 @@ CTFd._internal.challenge.destroy = function() {

CTFd._internal.challenge.renew = function () {
var challenge_id = CTFd._internal.challenge.data.id;
var url = "/api/v1/plugins/ctfd-chall-manager/instance?challengeId=" + challenge_id;
var url = "/api/v1/plugins/ctfd-chall-manager/instance";

$('#whale-button-renew').text("Waiting...");
$('#whale-button-renew').prop('disabled', true);

var params = {};
var params = {
"challengeId": challenge_id,
};

CTFd.fetch(url, {
method: 'PATCH',
Expand Down
4 changes: 2 additions & 2 deletions hack/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ services:
LOG_LEVEL: DEBUG
PLUGIN_SETTINGS_CM_API_URL: http://chall-manager:8080/api/v1
PLUGIN_SETTINGS_CM_MANA_TOTAL: 10
REDIS_URL: redis://redis-svc:6379
DATABASE_URL : mysql+pymysql://root:password@mariadb-svc:3306/ctfd
# REDIS_URL: redis://redis-svc:6379
# DATABASE_URL : mysql+pymysql://root:password@mariadb-svc:3306/ctfd
depends_on:
- chall-manager
# - redis-svc
Expand Down
10 changes: 5 additions & 5 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def __str__(self):



class DynamicIaCValueChallenge(BaseChallenge):
class DynamicIaCValueChallenge(DynamicValueChallenge):
id = "dynamic_iac" # Unique identifier used to register challenges
name = "dynamic_iac" # Name of a challenge type
templates = { # Handlebars templates used for each aspect of challenge editing & viewing
Expand Down Expand Up @@ -214,7 +214,7 @@ def update(cls, challenge, request):
# Workaround
if "state" in data.keys() and len(data.keys()) == 1:
setattr(challenge, "state", data["state"])
return DynamicValueChallenge.calculate_value(challenge)
return super().calculate_value(challenge)

# Patch Challenge on CTFd
optional = {}
Expand Down Expand Up @@ -267,7 +267,7 @@ def update(cls, challenge, request):
except Exception as e:
logger.error(f"Error while patching the challenge: {e}")

return DynamicValueChallenge.calculate_value(challenge)
return super().calculate_value(challenge)

@classmethod
def delete(cls, challenge):
Expand Down Expand Up @@ -392,8 +392,8 @@ def attempt(cls, challenge, request):
logger.info(f"invalid submission for CTFd flag: challenge {challenge.id} source {sourceId}")
return False, "Incorrect"

# remove this ?
@classmethod
def solve(cls, user, team, challenge, request):
super().solve(user, team, challenge, request)

DynamicValueChallenge.calculate_value(challenge)
super().calculate_value(challenge)
28 changes: 19 additions & 9 deletions test/test_api_admin_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ def test_valid_challenge_valid_source(self):
a = json.loads(r.text)
self.assertEqual(a["success"], True)

r = requests.get(f"{config.plugin_url}/admin/instance?challengeId={challengeId}&sourceId={sourceId}", headers=config.headers_admin)
r = requests.get(f"{config.plugin_url}/admin/instance", headers=config.headers_admin, data=json.dumps(payload))
a = json.loads(r.text)
self.assertEqual(a["success"], True)
self.assertEqual("connectionInfo" in a["data"]["message"].keys(), True)

r = requests.patch(f"{config.plugin_url}/admin/instance?challengeId={challengeId}&sourceId={sourceId}", headers=config.headers_admin)
r = requests.patch(f"{config.plugin_url}/admin/instance", headers=config.headers_admin, data=json.dumps(payload))
a = json.loads(r.text)
self.assertEqual(a["success"], True)

r = requests.delete(f"{config.plugin_url}/admin/instance?challengeId={challengeId}&sourceId={sourceId}", headers=config.headers_admin)
r = requests.delete(f"{config.plugin_url}/admin/instance", headers=config.headers_admin, data=json.dumps(payload))
a = json.loads(r.text)
self.assertEqual(a["success"], True)

Expand All @@ -64,11 +64,11 @@ def test_invalid_challenge_valid_source(self):
a = json.loads(r.text)
self.assertEqual(a["success"], False)

r = requests.patch(f"{config.plugin_url}/admin/instance?challengeId={challengeId}&sourceId={sourceId}", headers=config.headers_admin)
r = requests.patch(f"{config.plugin_url}/admin/instance", headers=config.headers_admin, data=json.dumps(payload))
a = json.loads(r.text)
self.assertEqual(a["success"], False)

r = requests.delete(f"{config.plugin_url}/admin/instance?challengeId={challengeId}&sourceId={sourceId}", headers=config.headers_admin)
r = requests.delete(f"{config.plugin_url}/admin/instance", headers=config.headers_admin, data=json.dumps(payload))
a = json.loads(r.text)
self.assertEqual(a["success"], False)

Expand All @@ -88,11 +88,11 @@ def test_valid_challenge_unknown_source(self):
a = json.loads(r.text)
self.assertEqual(a["success"], True)

r = requests.patch(f"{config.plugin_url}/admin/instance?challengeId={challengeId}&sourceId={sourceId}", headers=config.headers_admin)
r = requests.patch(f"{config.plugin_url}/admin/instance", headers=config.headers_admin, data=json.dumps(payload))
a = json.loads(r.text)
self.assertEqual(a["success"], True)

r = requests.delete(f"{config.plugin_url}/admin/instance?challengeId={challengeId}&sourceId={sourceId}", headers=config.headers_admin)
r = requests.delete(f"{config.plugin_url}/admin/instance", headers=config.headers_admin, data=json.dumps(payload))
a = json.loads(r.text)
self.assertEqual(a["success"], True)

Expand All @@ -101,15 +101,25 @@ def test_valid_challenge_unknown_source(self):
def test_delete_valid_challenge_but_no_instance(self):
challengeId = create_challenge()
sourceId = 999999
r = requests.delete(f"{config.plugin_url}/admin/instance?challengeId={challengeId}&sourceId={sourceId}", headers=config.headers_admin)
payload = {
"challengeId": f"{challengeId}",
"sourceId": f"{sourceId}"
}

r = requests.delete(f"{config.plugin_url}/admin/instance", headers=config.headers_admin, data=json.dumps(payload))
a = json.loads(r.text)
self.assertEqual(a["success"], False)
delete_challenge(challengeId)

def test_patch_valid_challenge_but_no_instance(self):
challengeId = create_challenge(timeout=9999)
sourceId = 999999
r = requests.patch(f"{config.plugin_url}/admin/instance?challengeId={challengeId}&sourceId={sourceId}", headers=config.headers_admin)
payload = {
"challengeId": f"{challengeId}",
"sourceId": f"{sourceId}"
}

r = requests.patch(f"{config.plugin_url}/admin/instance", headers=config.headers_admin, data=json.dumps(payload))
a = json.loads(r.text)
self.assertEqual(a["success"], False)

Expand Down
10 changes: 8 additions & 2 deletions test/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,17 @@ def get_admin_instance(challengeId: int, sourceId: int):
return r

def delete_instance(challengeId: int):
r = requests.delete(f"{config.plugin_url}/instance?challengeId={challengeId}", headers=config.headers_user)
payload = {
"challengeId": f"{challengeId}"
}
r = requests.delete(f"{config.plugin_url}/instance", headers=config.headers_user, data=json.dumps(payload))
return r

def patch_instance(challengeId: int):
r = requests.patch(f"{config.plugin_url}/instance?challengeId={challengeId}", headers=config.headers_user)
payload = {
"challengeId": f"{challengeId}"
}
r = requests.patch(f"{config.plugin_url}/instance", headers=config.headers_user, data=json.dumps(payload))
return r

# Run post on thread
Expand Down

0 comments on commit 668abba

Please sign in to comment.