diff --git a/CTFd-Unique_flags/__init__.py b/CTFd-Unique_flags/__init__.py index 15c43a0..753b95e 100644 --- a/CTFd-Unique_flags/__init__.py +++ b/CTFd-Unique_flags/__init__.py @@ -19,6 +19,17 @@ class UniqueFlag(CTFdStaticFlag): @staticmethod def compare(chal_key_obj, provided_flag): + """ + Compare the provided flag to the saved flag + + Args: + chal_key_obj: A CTFd.models.Flags object + provided_flag: A string provided by the user + + + Returns: + bool: Whether or not the provided flag is correct + """ # Get the actual flag to check for the challenge submitted (the function compare() is called for each flag of the challenge) saved_flag = chal_key_obj.content @@ -26,29 +37,35 @@ def compare(chal_key_obj, provided_flag): if len(saved_flag) != len(provided_flag): return False - + result = 0 - + for x, y in zip(saved_flag, provided_flag): result |= ord(x) ^ ord(y) - - if result == 0: - team_id = chal_key_obj.data - if int(team_id) == int(curr_team_id): - return True - else: - curr_user_id = get_current_user().id - cheater = CheaterTeams(challengeid=chal_key_obj.challenge_id, cheaterid=curr_user_id, cheatteamid=curr_team_id, sharerteamid=team_id, flagid=chal_key_obj.id) - db.session.add(cheater) - return False - else: + if result != 0: return False + + # Check if the team id of the player is the same as the team id of the flag data + # True = the flag is correct and the player is in the right team + # False = the flag is correct but the player is not in the right team + team_id = chal_key_obj.data + if int(team_id) == int(curr_team_id): + return True + + # The player is not in the right team, so we add the record in the cheater_teams table + curr_user_id = get_current_user().id + cheater = CheaterTeams(challengeid=chal_key_obj.challenge_id, cheaterid=curr_user_id, cheatteamid=curr_team_id, sharerteamid=team_id, flagid=chal_key_obj.id) + db.session.add(cheater) + return False def load(app): - + """ + Load the plugin into CTFd + Creates the database table and registers the blueprint + """ app.db.create_all() app.register_blueprint(plugin_blueprint) diff --git a/CTFd-Unique_flags/models.py b/CTFd-Unique_flags/models.py index 0c9e1d9..b0277a3 100644 --- a/CTFd-Unique_flags/models.py +++ b/CTFd-Unique_flags/models.py @@ -4,6 +4,19 @@ from CTFd.models import db, Challenges class CheaterTeams(db.Model): + """ + Model for the cheater_teams table + + Columns: + id (int): The ID of the record + challengeid (int): The ID of the challenge + cheaterid (int): The ID of the cheater + cheatteamid (int): The ID of the team that maybe cheated + sharerteamid (int): The ID of the team that shared the flag + flagid (int): The ID of the flag + date (datetime): The date of the record creation + """ + __tablename__ = 'cheater_teams' id = db.Column(db.Integer, primary_key=True) @@ -15,6 +28,14 @@ class CheaterTeams(db.Model): date = db.Column(db.DateTime, default=db.func.current_timestamp()) def __init__(self, challengeid, cheaterid, cheatteamid, sharerteamid, flagid): + """ Initializes the CheaterTeams object + Args: + challengeid (int): The ID of the challenge + cheaterid (int): The ID of the cheater + cheatteamid (int): The ID of the team that maybe cheated + sharerteamid (int): The ID of the team that shared the flag + flagid (int): The ID of the flag + """ self.challengeid = challengeid self.cheaterid = cheaterid self.cheatteamid = cheatteamid @@ -22,16 +43,31 @@ def __init__(self, challengeid, cheaterid, cheatteamid, sharerteamid, flagid): self.flagid = flagid def __repr__(self): + """ + Returns the string representation of the CheaterTeams object + """ return "".format(self.cheatteamid, self.challengeid, self.flagid, self.sharerteamid, self.date) def cheated_team_name(self): + """ + Get the name of the team that maybe cheated + """ return Teams.query.filter_by(id=self.cheatteamid).first().name def shared_team_name(self): + """ + Get the name of the team that shared the flag + """ return Teams.query.filter_by(id=self.sharerteamid).first().name def challenge_name(self): + """ + Get the name of the challenge + """ return Challenges.query.filter_by(id=self.challengeid).first().name def cheater_name(self): + """ + Get the name of the cheater + """ return Users.query.filter_by(id=self.cheaterid).first().email \ No newline at end of file diff --git a/CTFd-Unique_flags/routes.py b/CTFd-Unique_flags/routes.py index 842a204..1b8637e 100644 --- a/CTFd-Unique_flags/routes.py +++ b/CTFd-Unique_flags/routes.py @@ -15,6 +15,11 @@ @plugin_blueprint.route("/admin/unique-flag", methods=["GET", "POST"]) @admins_only def home(): + """ Import unique flags or show the unique flags administration page + + Returns: + render_template: Render the admin-import.html template or redirects to the unique_flags.home page + """ infos = get_infos() errors = get_errors() @@ -38,6 +43,11 @@ def home(): @plugin_blueprint.route("/admin/unique-flag/delete-flags", methods=["POST"]) @admins_only def delete_flags(): + """ Deletes all unique flags or flags of a specific challenge + + Returns: + redirection: Redirects to the unique_flags.home page + """ if request.form.get('challenge_id') is None: count = Flags.query.filter_by(type="uniqueflag").delete() else: @@ -53,4 +63,9 @@ def delete_flags(): @plugin_blueprint.route("/admin/unique-flag/cheating-monitor", methods=["GET"]) @admins_only def view_cheater(): + """ View the cheater teams + + Returns: + render_template: Render the cheat-monitor.html template + """ return render_template('cheat-monitor.html', cheaters=CheaterTeams.query.all()) diff --git a/CTFd-Unique_flags/utils.py b/CTFd-Unique_flags/utils.py index aa3dd80..e594f32 100644 --- a/CTFd-Unique_flags/utils.py +++ b/CTFd-Unique_flags/utils.py @@ -6,13 +6,31 @@ from CTFd.models import Teams, Flags, db def getTeamID(classe, groupe): + """ + Get the ID of a team from its class and group + + Args: + classe (string) + groupe (string) + + Returns: + int: The ID of the team + """ team_name = f"Team-{classe.upper()}-{int(groupe):02}" result = Teams.query.filter_by(name=team_name).first() return "Error" if result is None else result.id def importFlag(csv_content): + """ + Import flags from a CSV file into the database + + Args: + csv_content (string): The content of the CSV file (Check assets/example.csv) to see the format + Returns: + A list of infos and a list of errors + """ infos = get_infos() errors = get_errors() @@ -23,7 +41,7 @@ def importFlag(csv_content): errors.append("You need at least the columns 'CLASS', 'GROUP', 'FLAG', 'CHALLENGE_ID'.") return infos, errors - previous_team = "" # Cette variable permet de ne pas demander plusieurs fois à la base de données l'ID de l'équipe si la classe et le groupe n'ont pas changé. + previous_team = "" # This variable is used to avoid multiple queries to the database flags = [] for row in csvreader: diff --git a/CTFd-Universal_flag_submitter/__init__.py b/CTFd-Universal_flag_submitter/__init__.py index f3ca826..8f5d45a 100644 --- a/CTFd-Universal_flag_submitter/__init__.py +++ b/CTFd-Universal_flag_submitter/__init__.py @@ -10,6 +10,9 @@ def load(app): + """ + Load the plugin into CTFd and registers the blueprint + """ app.register_blueprint(plugin_blueprint) diff --git a/CTFd-Universal_flag_submitter/decorators.py b/CTFd-Universal_flag_submitter/decorators.py index b353e63..7288a8f 100644 --- a/CTFd-Universal_flag_submitter/decorators.py +++ b/CTFd-Universal_flag_submitter/decorators.py @@ -1,10 +1,7 @@ import functools from CTFd.utils import get_config from flask import abort - -import contextlib -import os -from flask import abort, redirect, render_template, request, url_for +from flask import abort, request from CTFd.utils.user import ( authed, get_current_team, @@ -12,41 +9,19 @@ ) from CTFd.utils import config, get_config from CTFd.utils.logging import log -from CTFd.models import db -from flask import Blueprint -from CTFd.utils.decorators import admins_only - -from CTFd.models import Teams, Flags, db -from CTFd.models import ( - ChallengeFiles, - Challenges, - Fails, - Flags, - Hints, - Solves, - Tags, - db, -) -from CTFd.utils.helpers import info_for, error_for + from CTFd.utils import user as current_user from CTFd.utils.user import get_current_team, get_current_user -from CTFd.utils import config, get_config, set_config -from CTFd.utils.dates import ctf_ended, ctf_paused, ctftime -from CTFd.plugins.challenges import CHALLENGE_CLASSES, get_chal_class -from flask import render_template, Blueprint, request -from CTFd.utils.decorators import admins_only - -from CTFd.utils.helpers import get_errors, get_infos -from .utils import add_fail, add_solves -from CTFd.cache import ( - cache, - clear_challenges, - clear_config, - clear_pages, - clear_standings, -) +from CTFd.utils import config, get_config +from CTFd.utils.dates import ctf_paused, ctftime +from flask import request + + def is_allowed_to_attempt(f): + """ + Decorator to check if the user is allowed to submit a flag. + """ @functools.wraps(f) def is_allowed_to_attempt_wrapper(*args, **kwargs): @@ -55,18 +30,16 @@ def is_allowed_to_attempt_wrapper(*args, **kwargs): if authed() is False: return ( {"success": False, - "message": "Veuillez vous authentifier.", + "message": "You must be logged in to access to this feature.", "design": "neutral" }, 403, ) - - if config.is_teams_mode() and get_current_team() is None: return ( {"success": False, - "message": "veuillez d'abord rejoindre une équipe.", + "message": "Please create or join a team to submit flags.", "design": "neutral" }, 403, diff --git a/CTFd-Universal_flag_submitter/routes.py b/CTFd-Universal_flag_submitter/routes.py index bc36dd8..3d09c9b 100644 --- a/CTFd-Universal_flag_submitter/routes.py +++ b/CTFd-Universal_flag_submitter/routes.py @@ -25,8 +25,14 @@ @require_complete_profile @during_ctf_time_only @require_verified_emails +#@require_team def attempt_hidden_challenge(): + """ + Attempt to solve a hidden challenge + Returns: + dict: A dictionary containing the success status, message, and design + """ chall = db.session.query(Challenges).filter(Challenges.state == "visible").all() if len(chall) == 0: @@ -59,7 +65,10 @@ def attempt_hidden_challenge(): @plugin_blueprint.route("/admin/hide-challenge", methods=["GET", "POST"]) @admins_only def home(): - + """ + Hide challenges from the challenge list and show hidden challenges administration page + """ + if request.method == "GET": infos = get_infos() errors = get_errors() diff --git a/CTFd-Universal_flag_submitter/utils.py b/CTFd-Universal_flag_submitter/utils.py index 7d46fe0..284b88f 100644 --- a/CTFd-Universal_flag_submitter/utils.py +++ b/CTFd-Universal_flag_submitter/utils.py @@ -9,10 +9,12 @@ def add_submission(submission_type, challenge_id, request): """ - Générique pour ajouter une soumission de fail ou solve. - submission_type: 'fail' ou 'solve' pour déterminer le type de soumission. - challenge_id: ID du challenge concerné. - request: objet de requête Flask. + Generic function to add a submission to the database + + Args: + submission_type (string): The type of submission to add + challenge_id (int): The ID of the challenge + request: The request object """ # Récupérer l'utilisateur diff --git a/CTFd-Writeup/__init__.py b/CTFd-Writeup/__init__.py index 6b17f48..92e7cdb 100644 --- a/CTFd-Writeup/__init__.py +++ b/CTFd-Writeup/__init__.py @@ -4,6 +4,11 @@ from CTFd.utils import config, get_config, set_config def load(app): + """ + Load the plugin into CTFd, registers the blueprint and creates the database table + Overrides the challenge.html template if the plugin is enabled + """ + app.db.create_all() app.register_blueprint(plugin_blueprint) dir_path = Path(__file__).parent.resolve() diff --git a/CTFd-Writeup/decorators.py b/CTFd-Writeup/decorators.py index ead93fb..41ec950 100644 --- a/CTFd-Writeup/decorators.py +++ b/CTFd-Writeup/decorators.py @@ -4,9 +4,7 @@ def plugin_enabled(f): """ - Decorator that requires the plugin enabled - :param f: - :return: + Decorator to prevent access to a route if the plugin is disabled """ @functools.wraps(f) def plugin_enabled_wrapper(*args, **kwargs): diff --git a/CTFd-Writeup/models.py b/CTFd-Writeup/models.py index 6e28370..13f32bb 100644 --- a/CTFd-Writeup/models.py +++ b/CTFd-Writeup/models.py @@ -4,6 +4,9 @@ class WriteupModel(db.Model): + """ + Model for the writeups table + """ __tablename__ = "writeups" id = Column(Integer, ForeignKey('challenges.id', ondelete="CASCADE"), primary_key=True) content = Column(Text) diff --git a/CTFd-Writeup/routes.py b/CTFd-Writeup/routes.py index a3479f2..4271b77 100644 --- a/CTFd-Writeup/routes.py +++ b/CTFd-Writeup/routes.py @@ -16,6 +16,9 @@ @plugin_enabled @authed_only def view_writeup(challenge_id): + """ + View the writeup for a challenge if it is unlocked + """ if challenge_is_unlocked(challenge_id): return render_template('custom-page.html', content=get_writeup(challenge_id)) else: @@ -24,6 +27,9 @@ def view_writeup(challenge_id): @plugin_blueprint.route("/admin/writeup", methods=["GET"]) @admins_only def view_admin_writeup(): + """ + View the writeup for a challenge + """ infos = get_infos() errors = get_errors() @@ -35,6 +41,9 @@ def view_admin_writeup(): @plugin_blueprint.route("/admin/writeup", methods=["POST"]) @admins_only def admin_update_config(): + """ + Update the plugin configuration and display the writeup configuration page + """ infos = get_infos() errors = get_errors() @@ -51,6 +60,15 @@ def admin_update_config(): @plugin_blueprint.route("/admin/writeup/edit/", methods=["GET", "POST"]) @admins_only def edit_writeup(challenge_id): + """ + Edit the writeup for a challenge + + Args: + challenge_id (int): The challenge ID + + Returns: + render_template: Render the writeup_editor.html template or redirects to the writeup.view_admin_writeup page + """ if request.method == "GET": writeup = WriteupModel.query.filter_by(id=challenge_id).first() challenge = get_challenge_by_id(challenge_id) @@ -90,6 +108,15 @@ def edit_writeup(challenge_id): @plugin_blueprint.route("/admin/writeup/delete/", methods=["POST"]) @admins_only def delete_writeup(challenge_id): + """ + Delete the writeup for a challenge + + Args: + challenge_id (int): The challenge ID + + Returns: + dict: A JSON object containing the status of the deletion + """ writeup = WriteupModel.query.filter_by(id=challenge_id).first_or_404() db.session.delete(writeup) db.session.commit() @@ -105,7 +132,15 @@ def delete_writeup(challenge_id): @plugin_blueprint.route("/admin/writeup/visibility/", methods=["PUT"]) @admins_only def toogle_writeup_visibility(challenge_id): - + """ + Toggle the visibility of a writeup + + Args: + challenge_id (int): The challenge ID + + Returns: + dict: A JSON object containing the status of the visibility change + """ writeup = WriteupModel.query.filter_by(id=challenge_id).first_or_404() writeup.visible = not writeup.visible @@ -121,6 +156,16 @@ def toogle_writeup_visibility(challenge_id): def challenge_is_unlocked(challenge_id): + """ + Check if a challenge is unlocked for the current user + + Args: + challenge_id (int): The challenge ID + + Returns: + bool: Whether the challenge is unlocked or not + """ + user = get_current_user_attrs() team = get_current_team() @@ -130,10 +175,34 @@ def challenge_is_unlocked(challenge_id): return db.session.query(Solves).filter(Solves.user_id == user.id, Solves.challenge_id == challenge_id).count() def get_writeup(challenge_id): + """ + Get the writeup for a challenge + + Args: + challenge_id (int): The challenge ID + + Returns: + query result: The writeup for the challenge + """ return db.session.query(Challenges, WriteupModel).outerjoin(WriteupModel, Challenges.id == WriteupModel.id).filter(Challenges.id == challenge_id, Challenges.state == "visible").first() def get_all_challenges(): + """ + Get all challenges + + Returns: + query result: All challenges + """ return Challenges.query.all() def get_challenge_by_id(challenge_id): + """ + Get a challenge by its ID + + Args: + challenge_id (int): The challenge ID + + Returns: + query result: The challenge + """ return Challenges.query.filter_by(id=challenge_id).first_or_404() diff --git a/team-generator.py b/team-generator.py index d86e296..0349c4b 100644 --- a/team-generator.py +++ b/team-generator.py @@ -6,7 +6,8 @@ TEAMS_PER_GROUP = 12 def random_string(length=6): - """Generate a random password of lowercase letters. + """ + Generate a random password of lowercase letters. Args: length (int, optional): The length of the random string. Defaults to 6.