Skip to content

Commit

Permalink
working challenge creation and editing
Browse files Browse the repository at this point in the history
  • Loading branch information
Kathode-Negative committed Dec 7, 2024
1 parent c0db4c0 commit 2c87804
Show file tree
Hide file tree
Showing 18 changed files with 439 additions and 1,981 deletions.
136 changes: 109 additions & 27 deletions CTFd/plugins/userchallenge/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from flask import render_template,request,Blueprint, url_for, abort
from flask import render_template,request,Blueprint, url_for, abort,redirect
from sqlalchemy.sql import and_
from pathlib import Path
from CTFd.utils.plugins import override_template
from CTFd.utils.helpers.models import build_model_filters
from CTFd.plugins.challenges import CHALLENGE_CLASSES, get_chal_class
from CTFd.plugins.flags import FLAG_CLASSES,get_flag_class
from CTFd.utils.user import get_current_user,is_admin, authed,get_current_team_attrs,get_current_user_attrs,get_current_team
from CTFd.models import Challenges, Solves, Flags, db, Configs,Hints,HintUnlocks,Flags,Submissions
from CTFd.utils.decorators import authed_only, admins_only
Expand All @@ -25,7 +26,7 @@
challenges_visible,
scores_visible
)

import functools
import CTFd.plugins.userchallenge.apiModding as apiModding


Expand All @@ -41,39 +42,54 @@ def __init__(self,user,challenge):
self.challenge = challenge

def add_User_Link(challenge_id):
userChallenge = UserChallenges(get_current_user().id,challenge_id)
db.session.add(userChallenge)
userchallenge = UserChallenges(get_current_user().id,challenge_id)
db.session.add(userchallenge)
db.session.commit()

def registerTemplate(old_path, new_path):
dir_path = Path(__file__).parent.resolve()
template_path = dir_path/'templates'/new_path
override_template(old_path,open(template_path).read())
def update_allow_challenges():
config = Configs.query.filter(Configs.key == "allowUserChallenges").all()
config = Configs.query.filter(Configs.key == "allowUserChallenges").first()
if config:
if config.value == "true":
config.value == "false"
return False
else:
config.value == "true"
config.value == "true"
return True
else:
config = Configs("allowUserChallenges","false")
config = Configs("allowUserChallenges","true")
db.session.add(config)
def get_allow_challenges():
config = Configs.query.filter(Configs.key == "allowUserChallenges").all()
if config:
if config.value == "true":
return True
db.session.commit()
return True
def userChallenge_allowed(f):
"""
Decorator that requires the user to be authed and userchallenges to be active
:param f:
:return:
"""

@functools.wraps(f)
def userChallenge_wrapper(*args, **kwargs):
value = get_config("allowUserChallenges")
if value and value == "true" and get_current_user.authed():
return f(*args, **kwargs)
else:
return False
else:
return False
if request.content_type == "application/json":
abort(403)
else:
return redirect(url_for("auth.login", next=request.full_path))
return userChallenge_wrapper

def load(app):

app.db.create_all()

userChallenge = Blueprint('userchallenge',__name__,template_folder='templates',static_folder ='staticAssets')
app.register_blueprint(userChallenge,url_prefix='/userchallenge')
app.register_blueprint(apiModding.api)
#app.register_blueprint(apiModding.api)

#register_plugin_assets_directory(app, base_path ='/plugins/UserChallengeManagement/assets/')

Expand All @@ -83,8 +99,6 @@ def load(app):
@admins_only
def view_config():

action = update_allow_challenges()

key = Configs.query.filter(Configs.key == "allowUserChallenges").all()

if key:
Expand All @@ -93,14 +107,23 @@ def view_config():
else :
enabled = "disabled"
else:
enabled = "disabled"
enabled = "non-existant"

return render_template('userConfig.html',action = action,enabled = enabled)

@app.route('/userchallenge/challenges',methods=['GET'])
return render_template('userConfig.html',enabled = enabled)

@app.route('/userchallenge/api/config',methods=['GET','POST'])
@admins_only
def getConfig():
newstate = update_allow_challenges()
data = "disabled"
if newstate:
data = "enabled"
return {"success":True,"data":data}

@app.route('/userchallenge/challenges')
#@userChallenge_allowed
@authed_only
def view_challenges():

#TODO: add custom html extension of admin/challenges/challenges
# change methods to check for rights and only display challenges by user
# add custom html to change challenge editing to be available to users
Expand All @@ -109,10 +132,15 @@ def view_challenges():

q = request.args.get("q")
field = request.args.get("field")
filters =[]
if q:
# The field exists as an exposed column
if Challenges.__mapper__.has_property(field):
filters.append(getattr(Challenges, field).like("%{}%".format(q)))

query = UserChallenges.query.filter(UserChallenges.user == get_current_user().id).order_by(UserChallenges.id.asc())
query = db.session.query(UserChallenges.challenge).filter_by(user=get_current_user().id)
challenge_ids = query.all()

challenges = Challenges.query.filter(Challenges.id.in_(challenge_ids)).all()

total = query.count()
Expand All @@ -121,13 +149,12 @@ def view_challenges():
return render_template('userChallenges.html',challenges=challenges,total = total)

@app.route('/userchallenge/challenges/new',methods=['GET'])
@authed_only
def view_newChallenge():
types = CHALLENGE_CLASSES.keys()
return render_template('createUserChallenge.html',types=types)

@app.route('/userchallenge/challenges/<int:challenge_id>',methods=['GET'])
@authed_only
#@userChallenge_allowed
def updateChallenge(challenge_id):
#TODO: update logic to work with plugin
challenges = dict(
Expand Down Expand Up @@ -169,6 +196,7 @@ def updateChallenge(challenge_id):
# api rerouting
## challenges
@app.route('/userchallenge/api/challenges/',methods=['POST'])
#@userChallenge_allowed
def challengepost():
data = request.form or request.get_json()

Expand All @@ -192,6 +220,7 @@ def challengepost():

## singular challenge
@app.route('/userchallenge/api/challenges/<challenge_id>',methods=['PATCH'])
#@userChallenge_allowed
def idchallpatch(challenge_id):
data = request.get_json()

Expand All @@ -211,6 +240,7 @@ def idchallpatch(challenge_id):

return {"success": True, "data": response}
@app.route('/userchallenge/api/challenges/<challenge_id>',methods=['GET'])
#@userChallenge_allowed
def idchallget(challenge_id):
if is_admin():
chal = Challenges.query.filter(Challenges.id == challenge_id).first_or_404()
Expand Down Expand Up @@ -369,6 +399,7 @@ def idchallget(challenge_id):

## types
@app.route('/userchallenge/api/challenges/types')
#@userChallenge_allowed
def typeget():
response = {}

Expand All @@ -387,6 +418,7 @@ def typeget():
return {"success": True, "data": response}
## flag saving
@app.route('/userchallenge/api/challenges/<challenge_id>/flags',methods=['GET'])
#@userChallenge_allowed
def flagget(challenge_id):
flags = Flags.query.filter_by(challenge_id=challenge_id).all()
schema = FlagSchema(many=True)
Expand All @@ -399,6 +431,7 @@ def flagget(challenge_id):

## flag posting
@app.route('/userchallenge/api/flags',methods=['POST'])
#@userChallenge_allowed
def flagpost():
req = request.get_json()
schema = FlagSchema()
Expand All @@ -414,6 +447,55 @@ def flagpost():
db.session.close()

return {"success": True, "data": response.data}
@app.route('/userchallenge/api/flags/types',methods=['GET'])
#@userChallenge_allowed
def flagTypeGet():
response = {}
for class_id in FLAG_CLASSES:
flag_class = FLAG_CLASSES.get(class_id)
response[class_id] = {
"name": flag_class.name,
"templates": flag_class.templates,
}
return {"success": True, "data": response}
@app.route('/userchallenge/api/flags/<flag_id>',methods=['GET'])
#@userChallenge_allowed
def flagIDget(flag_id):
flag = Flags.query.filter_by(id=flag_id).first_or_404()
schema = FlagSchema()
response = schema.dump(flag)

if response.errors:
return {"success": False, "errors": response.errors}, 400

response.data["templates"] = get_flag_class(flag.type).templates

return {"success": True, "data": response.data}
@app.route('/userchallenge/api/flags/<flag_id>',methods=['PATCH'])
#@userChallenge_allowed
def flagIDpatch(flag_id):
flag = Flags.query.filter_by(id=flag_id).first_or_404()
schema = FlagSchema()
req = request.get_json()

response = schema.load(req, session=db.session, instance=flag, partial=True)

if response.errors:
return {"success": False, "errors": response.errors}, 400

db.session.commit()

response = schema.dump(response.data)
db.session.close()

return {"success": True, "data": response.data}
@app.route('/userchallenge/api/flags/<flag_id>',methods=['DELETE'])
#@userChallenge_allowed
def flagIDdelete(flag_id):
flag = Flags.query.filter_by(id=flag_id).first_or_404()

db.session.delete(flag)
db.session.commit()
db.session.close()

return {"success": True}
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export default {
});
},
loadTypes: function () {
CTFd.fetch("/api/v1/flags/types", {
CTFd.fetch("/userchallenge/api/flags/types", {
method: "GET",
})
.then((response) => {
Expand All @@ -117,7 +117,7 @@ export default {
let params = form.serializeJSON(true);
params["challenge"] = this.$props.challenge_id;
CTFd.fetch("/api/v1/flags", {
CTFd.fetch("/userchallenge/api/flags", {
method: "POST",
credentials: "same-origin",
headers: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default {
},
methods: {
loadFlag: function () {
CTFd.fetch(`/api/v1/flags/${this.$props.flag_id}`, {
CTFd.fetch(`/userchallenge/api/flags/${this.$props.flag_id}`, {
method: "GET",
})
.then((response) => {
Expand Down Expand Up @@ -91,7 +91,7 @@ export default {
let form = $(event.target);
let params = form.serializeJSON(true);
CTFd.fetch(`/api/v1/flags/${this.$props.flag_id}`, {
CTFd.fetch(`/userchallenge/api/flags/${this.$props.flag_id}`, {
method: "PATCH",
credentials: "same-origin",
headers: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export default {
},
methods: {
loadFlags: function () {
CTFd.fetch(`/api/v1/challenges/${this.$props.challenge_id}/flags`, {
CTFd.fetch(`/userchallenge/api/challenges/${this.$props.challenge_id}/flags`, {
method: "GET",
credentials: "same-origin",
headers: {
Expand Down Expand Up @@ -127,7 +127,7 @@ export default {
},
deleteFlag: function (flag_id) {
if (confirm("Are you sure you'd like to delete this flag?")) {
CTFd.fetch(`/api/v1/flags/${flag_id}`, {
CTFd.fetch(`/userchallenge/api/flags/${flag_id}`, {
method: "DELETE",
})
.then((response) => {
Expand Down
7 changes: 7 additions & 0 deletions CTFd/plugins/userchallenge/assets/js/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import $ from "jquery";
$("#toggle-button").click(()=>{
$.get("/userchallenge/api/config",function (res){
console.log(res.data)
$("#label-enable").html("challenge creation is " + res.data)
})
})
13 changes: 13 additions & 0 deletions CTFd/plugins/userchallenge/staticAssets/assets/jquery-BT3bhPE2.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import{$ as l}from"../jquery-BT3bhPE2.js";l("#toggle-button").click(()=>{l.get("/userchallenge/api/config",function(e){console.log(e.data),l("#label-enable").html("challenge creation is "+e.data)})});
274 changes: 0 additions & 274 deletions CTFd/plugins/userchallenge/staticAssets/assets/js/main-DLxiQC1V.js

This file was deleted.

Loading

0 comments on commit 2c87804

Please sign in to comment.