mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-18 14:34:21 +01:00
Reorganize challenge plugins (#1492)
* Convert Challenge plugin static methods into class methods * Copy the create, read, update, attempt, solve, fail methods into the BaseChallenge class so that they can be re-used between challenge plugins
This commit is contained in:
@@ -21,6 +21,153 @@ class BaseChallenge(object):
|
||||
name = None
|
||||
templates = {}
|
||||
scripts = {}
|
||||
challenge_model = Challenges
|
||||
|
||||
@classmethod
|
||||
def create(cls, request):
|
||||
"""
|
||||
This method is used to process the challenge creation request.
|
||||
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
data = request.form or request.get_json()
|
||||
|
||||
challenge = cls.challenge_model(**data)
|
||||
|
||||
db.session.add(challenge)
|
||||
db.session.commit()
|
||||
|
||||
return challenge
|
||||
|
||||
@classmethod
|
||||
def read(cls, challenge):
|
||||
"""
|
||||
This method is in used to access the data of a challenge in a format processable by the front end.
|
||||
|
||||
:param challenge:
|
||||
:return: Challenge object, data dictionary to be returned to the user
|
||||
"""
|
||||
data = {
|
||||
"id": challenge.id,
|
||||
"name": challenge.name,
|
||||
"value": challenge.value,
|
||||
"description": challenge.description,
|
||||
"category": challenge.category,
|
||||
"state": challenge.state,
|
||||
"max_attempts": challenge.max_attempts,
|
||||
"type": challenge.type,
|
||||
"type_data": {
|
||||
"id": cls.id,
|
||||
"name": cls.name,
|
||||
"templates": cls.templates,
|
||||
"scripts": cls.scripts,
|
||||
},
|
||||
}
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def update(cls, challenge, request):
|
||||
"""
|
||||
This method is used to update the information associated with a challenge. This should be kept strictly to the
|
||||
Challenges table and any child tables.
|
||||
|
||||
:param challenge:
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
data = request.form or request.get_json()
|
||||
for attr, value in data.items():
|
||||
setattr(challenge, attr, value)
|
||||
|
||||
db.session.commit()
|
||||
return challenge
|
||||
|
||||
@classmethod
|
||||
def delete(cls, challenge):
|
||||
"""
|
||||
This method is used to delete the resources used by a challenge.
|
||||
|
||||
:param challenge:
|
||||
:return:
|
||||
"""
|
||||
Fails.query.filter_by(challenge_id=challenge.id).delete()
|
||||
Solves.query.filter_by(challenge_id=challenge.id).delete()
|
||||
Flags.query.filter_by(challenge_id=challenge.id).delete()
|
||||
files = ChallengeFiles.query.filter_by(challenge_id=challenge.id).all()
|
||||
for f in files:
|
||||
delete_file(f.id)
|
||||
ChallengeFiles.query.filter_by(challenge_id=challenge.id).delete()
|
||||
Tags.query.filter_by(challenge_id=challenge.id).delete()
|
||||
Hints.query.filter_by(challenge_id=challenge.id).delete()
|
||||
cls.challenge_model.query.filter_by(id=challenge.id).delete()
|
||||
db.session.commit()
|
||||
|
||||
@classmethod
|
||||
def attempt(cls, challenge, request):
|
||||
"""
|
||||
This method is used to check whether a given input is right or wrong. It does not make any changes and should
|
||||
return a boolean for correctness and a string to be shown to the user. It is also in charge of parsing the
|
||||
user's input from the request itself.
|
||||
|
||||
:param challenge: The Challenge object from the database
|
||||
:param request: The request the user submitted
|
||||
:return: (boolean, string)
|
||||
"""
|
||||
data = request.form or request.get_json()
|
||||
submission = data["submission"].strip()
|
||||
flags = Flags.query.filter_by(challenge_id=challenge.id).all()
|
||||
for flag in flags:
|
||||
try:
|
||||
if get_flag_class(flag.type).compare(flag, submission):
|
||||
return True, "Correct"
|
||||
except FlagException as e:
|
||||
return False, e.message
|
||||
return False, "Incorrect"
|
||||
|
||||
@classmethod
|
||||
def solve(cls, user, team, challenge, request):
|
||||
"""
|
||||
This method is used to insert Solves into the database in order to mark a challenge as solved.
|
||||
|
||||
:param team: The Team object from the database
|
||||
:param chal: The Challenge object from the database
|
||||
:param request: The request the user submitted
|
||||
:return:
|
||||
"""
|
||||
data = request.form or request.get_json()
|
||||
submission = data["submission"].strip()
|
||||
solve = Solves(
|
||||
user_id=user.id,
|
||||
team_id=team.id if team else None,
|
||||
challenge_id=challenge.id,
|
||||
ip=get_ip(req=request),
|
||||
provided=submission,
|
||||
)
|
||||
db.session.add(solve)
|
||||
db.session.commit()
|
||||
|
||||
@classmethod
|
||||
def fail(cls, user, team, challenge, request):
|
||||
"""
|
||||
This method is used to insert Fails into the database in order to mark an answer incorrect.
|
||||
|
||||
:param team: The Team object from the database
|
||||
:param chal: The Challenge object from the database
|
||||
:param request: The request the user submitted
|
||||
:return:
|
||||
"""
|
||||
data = request.form or request.get_json()
|
||||
submission = data["submission"].strip()
|
||||
wrong = Fails(
|
||||
user_id=user.id,
|
||||
team_id=team.id if team else None,
|
||||
challenge_id=challenge.id,
|
||||
ip=get_ip(request),
|
||||
provided=submission,
|
||||
)
|
||||
db.session.add(wrong)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
class CTFdStandardChallenge(BaseChallenge):
|
||||
@@ -42,154 +189,7 @@ class CTFdStandardChallenge(BaseChallenge):
|
||||
blueprint = Blueprint(
|
||||
"standard", __name__, template_folder="templates", static_folder="assets"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create(request):
|
||||
"""
|
||||
This method is used to process the challenge creation request.
|
||||
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
data = request.form or request.get_json()
|
||||
|
||||
challenge = Challenges(**data)
|
||||
|
||||
db.session.add(challenge)
|
||||
db.session.commit()
|
||||
|
||||
return challenge
|
||||
|
||||
@staticmethod
|
||||
def read(challenge):
|
||||
"""
|
||||
This method is in used to access the data of a challenge in a format processable by the front end.
|
||||
|
||||
:param challenge:
|
||||
:return: Challenge object, data dictionary to be returned to the user
|
||||
"""
|
||||
data = {
|
||||
"id": challenge.id,
|
||||
"name": challenge.name,
|
||||
"value": challenge.value,
|
||||
"description": challenge.description,
|
||||
"category": challenge.category,
|
||||
"state": challenge.state,
|
||||
"max_attempts": challenge.max_attempts,
|
||||
"type": challenge.type,
|
||||
"type_data": {
|
||||
"id": CTFdStandardChallenge.id,
|
||||
"name": CTFdStandardChallenge.name,
|
||||
"templates": CTFdStandardChallenge.templates,
|
||||
"scripts": CTFdStandardChallenge.scripts,
|
||||
},
|
||||
}
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def update(challenge, request):
|
||||
"""
|
||||
This method is used to update the information associated with a challenge. This should be kept strictly to the
|
||||
Challenges table and any child tables.
|
||||
|
||||
:param challenge:
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
data = request.form or request.get_json()
|
||||
for attr, value in data.items():
|
||||
setattr(challenge, attr, value)
|
||||
|
||||
db.session.commit()
|
||||
return challenge
|
||||
|
||||
@staticmethod
|
||||
def delete(challenge):
|
||||
"""
|
||||
This method is used to delete the resources used by a challenge.
|
||||
|
||||
:param challenge:
|
||||
:return:
|
||||
"""
|
||||
Fails.query.filter_by(challenge_id=challenge.id).delete()
|
||||
Solves.query.filter_by(challenge_id=challenge.id).delete()
|
||||
Flags.query.filter_by(challenge_id=challenge.id).delete()
|
||||
files = ChallengeFiles.query.filter_by(challenge_id=challenge.id).all()
|
||||
for f in files:
|
||||
delete_file(f.id)
|
||||
ChallengeFiles.query.filter_by(challenge_id=challenge.id).delete()
|
||||
Tags.query.filter_by(challenge_id=challenge.id).delete()
|
||||
Hints.query.filter_by(challenge_id=challenge.id).delete()
|
||||
Challenges.query.filter_by(id=challenge.id).delete()
|
||||
db.session.commit()
|
||||
|
||||
@staticmethod
|
||||
def attempt(challenge, request):
|
||||
"""
|
||||
This method is used to check whether a given input is right or wrong. It does not make any changes and should
|
||||
return a boolean for correctness and a string to be shown to the user. It is also in charge of parsing the
|
||||
user's input from the request itself.
|
||||
|
||||
:param challenge: The Challenge object from the database
|
||||
:param request: The request the user submitted
|
||||
:return: (boolean, string)
|
||||
"""
|
||||
data = request.form or request.get_json()
|
||||
submission = data["submission"].strip()
|
||||
flags = Flags.query.filter_by(challenge_id=challenge.id).all()
|
||||
for flag in flags:
|
||||
try:
|
||||
if get_flag_class(flag.type).compare(flag, submission):
|
||||
return True, "Correct"
|
||||
except FlagException as e:
|
||||
return False, e.message
|
||||
return False, "Incorrect"
|
||||
|
||||
@staticmethod
|
||||
def solve(user, team, challenge, request):
|
||||
"""
|
||||
This method is used to insert Solves into the database in order to mark a challenge as solved.
|
||||
|
||||
:param team: The Team object from the database
|
||||
:param chal: The Challenge object from the database
|
||||
:param request: The request the user submitted
|
||||
:return:
|
||||
"""
|
||||
data = request.form or request.get_json()
|
||||
submission = data["submission"].strip()
|
||||
solve = Solves(
|
||||
user_id=user.id,
|
||||
team_id=team.id if team else None,
|
||||
challenge_id=challenge.id,
|
||||
ip=get_ip(req=request),
|
||||
provided=submission,
|
||||
)
|
||||
db.session.add(solve)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
@staticmethod
|
||||
def fail(user, team, challenge, request):
|
||||
"""
|
||||
This method is used to insert Fails into the database in order to mark an answer incorrect.
|
||||
|
||||
:param team: The Team object from the database
|
||||
:param chal: The Challenge object from the database
|
||||
:param request: The request the user submitted
|
||||
:return:
|
||||
"""
|
||||
data = request.form or request.get_json()
|
||||
submission = data["submission"].strip()
|
||||
wrong = Fails(
|
||||
user_id=user.id,
|
||||
team_id=team.id if team else None,
|
||||
challenge_id=challenge.id,
|
||||
ip=get_ip(request),
|
||||
provided=submission,
|
||||
)
|
||||
db.session.add(wrong)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
challenge_model = Challenges
|
||||
|
||||
|
||||
def get_chal_class(class_id):
|
||||
|
||||
@@ -4,23 +4,25 @@ import math
|
||||
|
||||
from flask import Blueprint
|
||||
|
||||
from CTFd.models import (
|
||||
ChallengeFiles,
|
||||
Challenges,
|
||||
Fails,
|
||||
Flags,
|
||||
Hints,
|
||||
Solves,
|
||||
Tags,
|
||||
db,
|
||||
)
|
||||
from CTFd.models import Challenges, Solves, db
|
||||
from CTFd.plugins import register_plugin_assets_directory
|
||||
from CTFd.plugins.challenges import CHALLENGE_CLASSES, BaseChallenge
|
||||
from CTFd.plugins.flags import FlagException, get_flag_class
|
||||
from CTFd.plugins.migrations import upgrade
|
||||
from CTFd.utils.modes import get_model
|
||||
from CTFd.utils.uploads import delete_file
|
||||
from CTFd.utils.user import get_ip
|
||||
|
||||
|
||||
class DynamicChallenge(Challenges):
|
||||
__mapper_args__ = {"polymorphic_identity": "dynamic"}
|
||||
id = db.Column(
|
||||
db.Integer, db.ForeignKey("challenges.id", ondelete="CASCADE"), primary_key=True
|
||||
)
|
||||
initial = db.Column(db.Integer, default=0)
|
||||
minimum = db.Column(db.Integer, default=0)
|
||||
decay = db.Column(db.Integer, default=0)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DynamicChallenge, self).__init__(**kwargs)
|
||||
self.initial = kwargs["value"]
|
||||
|
||||
|
||||
class DynamicValueChallenge(BaseChallenge):
|
||||
@@ -45,6 +47,7 @@ class DynamicValueChallenge(BaseChallenge):
|
||||
template_folder="templates",
|
||||
static_folder="assets",
|
||||
)
|
||||
challenge_model = DynamicChallenge
|
||||
|
||||
@classmethod
|
||||
def calculate_value(cls, challenge):
|
||||
@@ -82,24 +85,8 @@ class DynamicValueChallenge(BaseChallenge):
|
||||
db.session.commit()
|
||||
return challenge
|
||||
|
||||
@staticmethod
|
||||
def create(request):
|
||||
"""
|
||||
This method is used to process the challenge creation request.
|
||||
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
data = request.form or request.get_json()
|
||||
challenge = DynamicChallenge(**data)
|
||||
|
||||
db.session.add(challenge)
|
||||
db.session.commit()
|
||||
|
||||
return challenge
|
||||
|
||||
@staticmethod
|
||||
def read(challenge):
|
||||
@classmethod
|
||||
def read(cls, challenge):
|
||||
"""
|
||||
This method is in used to access the data of a challenge in a format processable by the front end.
|
||||
|
||||
@@ -120,16 +107,16 @@ class DynamicValueChallenge(BaseChallenge):
|
||||
"max_attempts": challenge.max_attempts,
|
||||
"type": challenge.type,
|
||||
"type_data": {
|
||||
"id": DynamicValueChallenge.id,
|
||||
"name": DynamicValueChallenge.name,
|
||||
"templates": DynamicValueChallenge.templates,
|
||||
"scripts": DynamicValueChallenge.scripts,
|
||||
"id": cls.id,
|
||||
"name": cls.name,
|
||||
"templates": cls.templates,
|
||||
"scripts": cls.scripts,
|
||||
},
|
||||
}
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def update(challenge, request):
|
||||
@classmethod
|
||||
def update(cls, challenge, request):
|
||||
"""
|
||||
This method is used to update the information associated with a challenge. This should be kept strictly to the
|
||||
Challenges table and any child tables.
|
||||
@@ -148,112 +135,12 @@ class DynamicValueChallenge(BaseChallenge):
|
||||
|
||||
return DynamicValueChallenge.calculate_value(challenge)
|
||||
|
||||
@staticmethod
|
||||
def delete(challenge):
|
||||
"""
|
||||
This method is used to delete the resources used by a challenge.
|
||||
|
||||
:param challenge:
|
||||
:return:
|
||||
"""
|
||||
Fails.query.filter_by(challenge_id=challenge.id).delete()
|
||||
Solves.query.filter_by(challenge_id=challenge.id).delete()
|
||||
Flags.query.filter_by(challenge_id=challenge.id).delete()
|
||||
files = ChallengeFiles.query.filter_by(challenge_id=challenge.id).all()
|
||||
for f in files:
|
||||
delete_file(f.id)
|
||||
ChallengeFiles.query.filter_by(challenge_id=challenge.id).delete()
|
||||
Tags.query.filter_by(challenge_id=challenge.id).delete()
|
||||
Hints.query.filter_by(challenge_id=challenge.id).delete()
|
||||
DynamicChallenge.query.filter_by(id=challenge.id).delete()
|
||||
Challenges.query.filter_by(id=challenge.id).delete()
|
||||
db.session.commit()
|
||||
|
||||
@staticmethod
|
||||
def attempt(challenge, request):
|
||||
"""
|
||||
This method is used to check whether a given input is right or wrong. It does not make any changes and should
|
||||
return a boolean for correctness and a string to be shown to the user. It is also in charge of parsing the
|
||||
user's input from the request itself.
|
||||
|
||||
:param challenge: The Challenge object from the database
|
||||
:param request: The request the user submitted
|
||||
:return: (boolean, string)
|
||||
"""
|
||||
data = request.form or request.get_json()
|
||||
submission = data["submission"].strip()
|
||||
flags = Flags.query.filter_by(challenge_id=challenge.id).all()
|
||||
for flag in flags:
|
||||
try:
|
||||
if get_flag_class(flag.type).compare(flag, submission):
|
||||
return True, "Correct"
|
||||
except FlagException as e:
|
||||
return False, e.message
|
||||
return False, "Incorrect"
|
||||
|
||||
@staticmethod
|
||||
def solve(user, team, challenge, request):
|
||||
"""
|
||||
This method is used to insert Solves into the database in order to mark a challenge as solved.
|
||||
|
||||
:param team: The Team object from the database
|
||||
:param chal: The Challenge object from the database
|
||||
:param request: The request the user submitted
|
||||
:return:
|
||||
"""
|
||||
challenge = DynamicChallenge.query.filter_by(id=challenge.id).first()
|
||||
data = request.form or request.get_json()
|
||||
submission = data["submission"].strip()
|
||||
|
||||
solve = Solves(
|
||||
user_id=user.id,
|
||||
team_id=team.id if team else None,
|
||||
challenge_id=challenge.id,
|
||||
ip=get_ip(req=request),
|
||||
provided=submission,
|
||||
)
|
||||
db.session.add(solve)
|
||||
db.session.commit()
|
||||
@classmethod
|
||||
def solve(cls, user, team, challenge, request):
|
||||
super().solve(user, team, challenge, request)
|
||||
|
||||
DynamicValueChallenge.calculate_value(challenge)
|
||||
|
||||
@staticmethod
|
||||
def fail(user, team, challenge, request):
|
||||
"""
|
||||
This method is used to insert Fails into the database in order to mark an answer incorrect.
|
||||
|
||||
:param team: The Team object from the database
|
||||
:param challenge: The Challenge object from the database
|
||||
:param request: The request the user submitted
|
||||
:return:
|
||||
"""
|
||||
data = request.form or request.get_json()
|
||||
submission = data["submission"].strip()
|
||||
wrong = Fails(
|
||||
user_id=user.id,
|
||||
team_id=team.id if team else None,
|
||||
challenge_id=challenge.id,
|
||||
ip=get_ip(request),
|
||||
provided=submission,
|
||||
)
|
||||
db.session.add(wrong)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
|
||||
class DynamicChallenge(Challenges):
|
||||
__mapper_args__ = {"polymorphic_identity": "dynamic"}
|
||||
id = db.Column(
|
||||
db.Integer, db.ForeignKey("challenges.id", ondelete="CASCADE"), primary_key=True
|
||||
)
|
||||
initial = db.Column(db.Integer, default=0)
|
||||
minimum = db.Column(db.Integer, default=0)
|
||||
decay = db.Column(db.Integer, default=0)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DynamicChallenge, self).__init__(**kwargs)
|
||||
self.initial = kwargs["value"]
|
||||
|
||||
|
||||
def load(app):
|
||||
upgrade()
|
||||
|
||||
Reference in New Issue
Block a user