diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eb4b1d3..637e17bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +# 3.0.0a2 / 2020-07-09 + +**General** + +- Accept additional profile fields during registration (affiliation, website, country) + - This does not add additional inputs. Themes or additional JavaScript can add the form inputs. + +**Admin Panel** + +- Redesign the challenge creation form to use a radio button with challenge type selection instead of a select input + +**API** + +- Admins can no longer ban themselves through `PATCH /api/v1/users/[user_id]` + +**Themes** + +- Spinner centering has been switched from a hard coded margin in CSS to flexbox CSS classes from Bootstrap + +**Plugins** + +- Revert plugin menu (`register_admin_plugin_menu_bar`, `register_user_page_menu_bar`) changes to 2.x code + +**Miscellaneous** + +- Fix issue with `Configs.ctf_name` returning incorrect value +- Add prerender step back into challenges.js +- Better handling of missing challenge types. Missing challenge types no longer bring down all other challenges. + # 3.0.0a1 / 2020-07-01 **General** diff --git a/CTFd/__init__.py b/CTFd/__init__.py index 75aad0f8..1b551c2f 100644 --- a/CTFd/__init__.py +++ b/CTFd/__init__.py @@ -26,7 +26,7 @@ from CTFd.utils.migrations import create_database, migrations, stamp_latest_revi from CTFd.utils.sessions import CachingSessionInterface from CTFd.utils.updates import update_check -__version__ = "3.0.0a1" +__version__ = "3.0.0a2" class CTFdRequest(Request): diff --git a/CTFd/admin/challenges.py b/CTFd/admin/challenges.py index 5da07b3a..973e7ca3 100644 --- a/CTFd/admin/challenges.py +++ b/CTFd/admin/challenges.py @@ -1,8 +1,8 @@ -from flask import render_template, request, url_for +from flask import abort, render_template, request, url_for from CTFd.admin import admin from CTFd.models import Challenges, Flags, Solves -from CTFd.plugins.challenges import get_chal_class +from CTFd.plugins.challenges import CHALLENGE_CLASSES, get_chal_class from CTFd.utils.decorators import admins_only @@ -44,7 +44,14 @@ def challenges_detail(challenge_id): .all() ) flags = Flags.query.filter_by(challenge_id=challenge.id).all() - challenge_class = get_chal_class(challenge.type) + + try: + challenge_class = get_chal_class(challenge.type) + except KeyError: + abort( + 500, + f"The underlying challenge type ({challenge.type}) is not installed. This challenge can not be loaded.", + ) update_j2 = render_template( challenge_class.templates["update"].lstrip("/"), challenge=challenge @@ -67,4 +74,5 @@ def challenges_detail(challenge_id): @admin.route("/admin/challenges/new") @admins_only def challenges_new(): - return render_template("admin/challenges/new.html") + types = CHALLENGE_CLASSES.keys() + return render_template("admin/challenges/new.html", types=types) diff --git a/CTFd/api/v1/challenges.py b/CTFd/api/v1/challenges.py index 5b5621f9..38db09d7 100644 --- a/CTFd/api/v1/challenges.py +++ b/CTFd/api/v1/challenges.py @@ -187,7 +187,13 @@ class ChallengeList(Resource): # Fallthrough to continue continue - challenge_type = get_chal_class(challenge.type) + try: + challenge_type = get_chal_class(challenge.type) + except KeyError: + # Challenge type does not exist. Fall through to next challenge. + continue + + # Challenge passes all checks, add it to response response.append( { "id": challenge.id, @@ -268,7 +274,13 @@ class Challenge(Resource): and_(Challenges.state != "hidden", Challenges.state != "locked"), ).first_or_404() - chal_class = get_chal_class(chal.type) + try: + chal_class = get_chal_class(chal.type) + except KeyError: + abort( + 500, + f"The underlying challenge type ({chal.type}) is not installed. This challenge can not be loaded.", + ) if chal.requirements: requirements = chal.requirements.get("prerequisites", []) diff --git a/CTFd/api/v1/users.py b/CTFd/api/v1/users.py index 36a11465..9d857e92 100644 --- a/CTFd/api/v1/users.py +++ b/CTFd/api/v1/users.py @@ -1,6 +1,6 @@ from typing import List -from flask import abort, request +from flask import abort, request, session from flask_restx import Namespace, Resource from CTFd.api.v1.helpers.models import build_model_filters @@ -218,6 +218,16 @@ class UserPublic(Resource): user = Users.query.filter_by(id=user_id).first_or_404() data = request.get_json() data["id"] = user_id + + # Admins should not be able to ban themselves + if data["id"] == session["id"] and ( + data.get("banned") is True or data.get("banned") == "true" + ): + return ( + {"success": False, "errors": {"id": "You cannot ban yourself"}}, + 400, + ) + schema = UserSchema(view="admin", instance=user, partial=True) response = schema.load(data) if response.errors: diff --git a/CTFd/auth.py b/CTFd/auth.py index fbed3007..f4ec1da4 100644 --- a/CTFd/auth.py +++ b/CTFd/auth.py @@ -22,6 +22,7 @@ from CTFd.utils.logging import log from CTFd.utils.modes import TEAMS_MODE from CTFd.utils.security.auth import login_user, logout_user from CTFd.utils.security.signing import unserialize +from CTFd.utils.validators import ValidationError auth = Blueprint("auth", __name__) @@ -189,6 +190,10 @@ def register(): email_address = request.form.get("email", "").strip().lower() password = request.form.get("password", "").strip() + website = request.form.get("website") + affiliation = request.form.get("affiliation") + country = request.form.get("country") + name_len = len(name) == 0 names = Users.query.add_columns("name", "id").filter_by(name=name).first() emails = ( @@ -201,6 +206,25 @@ def register(): valid_email = validators.validate_email(email_address) team_name_email_check = validators.validate_email(name) + if country: + try: + validators.validate_country_code(country) + valid_country = True + except ValidationError: + valid_country = False + else: + valid_country = True + + if website: + valid_website = validators.validate_url(website) + else: + valid_website = True + + if affiliation: + valid_affiliation = len(affiliation) < 128 + else: + valid_affiliation = True + if not valid_email: errors.append("Please enter a valid email address") if email.check_email_is_whitelisted(email_address) is False: @@ -221,6 +245,12 @@ def register(): errors.append("Pick a shorter password") if name_len: errors.append("Pick a longer user name") + if valid_website is False: + errors.append("Websites must be a proper URL starting with http or https") + if valid_country is False: + errors.append("Invalid country") + if valid_affiliation is False: + errors.append("Please provide a shorter affiliation") if len(errors) > 0: return render_template( @@ -233,6 +263,14 @@ def register(): else: with app.app_context(): user = Users(name=name, email=email_address, password=password) + + if website: + user.website = website + if affiliation: + user.affiliation = affiliation + if country: + user.country = country + db.session.add(user) db.session.commit() db.session.flush() diff --git a/CTFd/constants/config.py b/CTFd/constants/config.py index 37372240..0c65bb7b 100644 --- a/CTFd/constants/config.py +++ b/CTFd/constants/config.py @@ -45,7 +45,7 @@ class _ConfigsWrapper: @property def ctf_name(self): - return get_config("theme_header", default="CTFd") + return get_config("ctf_name", default="CTFd") @property def theme_header(self): diff --git a/CTFd/errors.py b/CTFd/errors.py index d6fe386c..3676aad8 100644 --- a/CTFd/errors.py +++ b/CTFd/errors.py @@ -1,4 +1,5 @@ from flask import render_template +from werkzeug.exceptions import InternalServerError # 404 @@ -13,7 +14,10 @@ def forbidden(error): # 500 def general_error(error): - return render_template("errors/500.html"), 500 + if error.description == InternalServerError.description: + error.description = "An Internal Server Error has occurred" + + return render_template("errors/500.html", error=error.description), 500 # 502 diff --git a/CTFd/models/__init__.py b/CTFd/models/__init__.py index 2b2b8d4b..c86416d4 100644 --- a/CTFd/models/__init__.py +++ b/CTFd/models/__init__.py @@ -1,4 +1,5 @@ import datetime +from collections import defaultdict from flask_marshmallow import Marshmallow from flask_sqlalchemy import SQLAlchemy @@ -77,7 +78,22 @@ class Challenges(db.Model): hints = db.relationship("Hints", backref="challenge") flags = db.relationship("Flags", backref="challenge") - __mapper_args__ = {"polymorphic_identity": "standard", "polymorphic_on": type} + class alt_defaultdict(defaultdict): + """ + This slightly modified defaultdict is intended to allow SQLAlchemy to + not fail when querying Challenges that contain a missing challenge type. + + e.g. Challenges.query.all() should not fail if `type` is `a_missing_type` + """ + + def __missing__(self, key): + return self["standard"] + + __mapper_args__ = { + "polymorphic_identity": "standard", + "polymorphic_on": type, + "_polymorphic_map": alt_defaultdict(), + } @property def html(self): diff --git a/CTFd/plugins/__init__.py b/CTFd/plugins/__init__.py index 6ad24060..724a7c75 100644 --- a/CTFd/plugins/__init__.py +++ b/CTFd/plugins/__init__.py @@ -4,7 +4,7 @@ import os from collections import namedtuple from flask import current_app as app -from flask import send_file, send_from_directory, url_for +from flask import send_file, send_from_directory from CTFd.utils.config.pages import get_pages from CTFd.utils.decorators import admins_only as admins_only_wrapper @@ -114,9 +114,6 @@ def register_admin_plugin_menu_bar(title, route): :param route: A string that is the href used by the link :return: """ - if (route.startswith("http://") or route.startswith("https://")) is False: - route = url_for("views.static_html", route=route) - am = Menu(title=title, route=route) app.admin_plugin_menu_bar.append(am) @@ -138,9 +135,6 @@ def register_user_page_menu_bar(title, route): :param route: A string that is the href used by the link :return: """ - if (route.startswith("http://") or route.startswith("https://")) is False: - route = url_for("views.static_html", route=route) - p = Menu(title=title, route=route) app.plugin_menu_bar.append(p) diff --git a/CTFd/themes/admin/assets/css/includes/easymde.scss b/CTFd/themes/admin/assets/css/includes/easymde.scss index ccbff297..fa6f8581 100644 --- a/CTFd/themes/admin/assets/css/includes/easymde.scss +++ b/CTFd/themes/admin/assets/css/includes/easymde.scss @@ -11,7 +11,6 @@ .CodeMirror-scroll { overflow-y: hidden; overflow-x: auto; - height: 200px; } .editor-toolbar { diff --git a/CTFd/themes/admin/assets/js/pages/challenge.js b/CTFd/themes/admin/assets/js/pages/challenge.js index d104549b..f33e153e 100644 --- a/CTFd/themes/admin/assets/js/pages/challenge.js +++ b/CTFd/themes/admin/assets/js/pages/challenge.js @@ -237,17 +237,6 @@ function handleChallengeOptions(event) { }); } -function createChallenge(_event) { - const challenge = $(this) - .find("option:selected") - .data("meta"); - if (challenge === undefined) { - $("#create-chal-entry-div").empty(); - return; - } - loadChalTemplate(challenge); -} - $(() => { $(".preview-challenge").click(function(_e) { window.challenge = new Object(); @@ -430,29 +419,12 @@ $(() => { $(".edit-flag").click(editFlagModal); $.get(CTFd.config.urlRoot + "/api/v1/challenges/types", function(response) { - $("#create-chals-select").empty(); const data = response.data; - const chal_type_amt = Object.keys(data).length; - if (chal_type_amt > 1) { - const option = ""; - $("#create-chals-select").append(option); - for (const key in data) { - const challenge = data[key]; - const option = $("\";\n (0, _jquery.default)(\"#create-chals-select\").append(option);\n\n for (var key in data) {\n var challenge = data[key];\n\n var _option = (0, _jquery.default)(\"");for(var a in o.append(n),t)t.hasOwnProperty(a)&&(n=(0,s.default)("".format(a,t[a].name)),o.append(n));(0,s.default)("#flag-edit-modal").modal()}),(0,s.default)("#flag-edit-modal form").submit(function(e){e.preventDefault();var t=(0,s.default)(this).serializeJSON(!0);t.challenge=window.CHALLENGE_ID,i.default.fetch("/api/v1/flags",{method:"POST",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(t)}).then(function(e){return e.json()}).then(function(e){window.location.reload()})}),(0,s.default)("#flag-edit-modal").modal()},t.editFlagModal=function(e){e.preventDefault();var n=(0,s.default)(this).attr("flag-id"),a=(0,s.default)(this).parent().parent();s.default.get(i.default.config.urlRoot+"/api/v1/flags/"+n,function(e){var o=e.data;s.default.get(i.default.config.urlRoot+o.templates.update,function(e){(0,s.default)("#edit-flags form").empty(),(0,s.default)("#edit-flags form").off();var t=l.default.compile(e);(0,s.default)("#edit-flags form").append(t.render(o)),(0,s.default)("#edit-flags form").submit(function(e){e.preventDefault();var t=(0,s.default)("#edit-flags form").serializeJSON();i.default.fetch("/api/v1/flags/"+n,{method:"PATCH",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(t)}).then(function(e){return e.json()}).then(function(e){e.success&&((0,s.default)(a).find(".flag-content").text(e.data.content),(0,s.default)("#edit-flags").modal("toggle"))})}),(0,s.default)("#edit-flags").modal()})})},t.flagTypeSelect=function(e){e.preventDefault();var t=(0,s.default)(this).find("option:selected").text();s.default.get(i.default.config.urlRoot+"/api/v1/flags/types/"+t,function(e){var t=e.data;s.default.get(i.default.config.urlRoot+t.templates.create,function(e){var t=l.default.compile(e);(0,s.default)("#create-keys-entry-div").html(t.render()),(0,s.default)("#create-keys-button-div").show()})})};var s=a(o("./node_modules/jquery/dist/jquery.js")),i=a(o("./CTFd/themes/core/assets/js/CTFd.js")),l=a(o("./node_modules/nunjucks/browser/nunjucks.js")),n=o("./CTFd/themes/core/assets/js/ezq.js");function a(e){return e&&e.__esModule?e:{default:e}}},"./CTFd/themes/admin/assets/js/challenges/hints.js":function(e,t,o){Object.defineProperty(t,"__esModule",{value:!0}),t.showHintModal=function(e){e.preventDefault(),(0,a.default)("#hint-edit-modal form").find("input, textarea").val("").trigger("change"),(0,a.default)("#hint-edit-form textarea").each(function(e,t){t.hasOwnProperty("codemirror")&&t.codemirror.refresh()}),(0,a.default)("#hint-edit-modal").modal()},t.showEditHintModal=function(e){e.preventDefault();var t=(0,a.default)(this).attr("hint-id");s.default.fetch("/api/v1/hints/"+t+"?preview=true",{method:"GET",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"}}).then(function(e){return e.json()}).then(function(e){e.success&&((0,a.default)("#hint-edit-form input[name=content],textarea[name=content]").val(e.data.content).trigger("change"),(0,a.default)("#hint-edit-modal").on("shown.bs.modal",function(){(0,a.default)("#hint-edit-form textarea").each(function(e,t){t.hasOwnProperty("codemirror")&&t.codemirror.refresh()})}).on("hide.bs.modal",function(){(0,a.default)("#hint-edit-form textarea").each(function(e,t){(0,a.default)(t).val("").trigger("change"),t.hasOwnProperty("codemirror")&&t.codemirror.refresh()})}),(0,a.default)("#hint-edit-form input[name=cost]").val(e.data.cost),(0,a.default)("#hint-edit-form input[name=id]").val(e.data.id),(0,a.default)("#hint-edit-modal").modal())})},t.deleteHint=function(e){e.preventDefault();var t=(0,a.default)(this).attr("hint-id"),o=(0,a.default)(this).parent().parent();(0,n.ezQuery)({title:"Delete Hint",body:"Are you sure you want to delete this hint?",success:function(){s.default.fetch("/api/v1/hints/"+t,{method:"DELETE"}).then(function(e){return e.json()}).then(function(e){e.success&&o.remove()})}})},t.editHint=function(e){e.preventDefault();var t=(0,a.default)(this).serializeJSON(!0);t.challenge=window.CHALLENGE_ID;var o="POST",n="/api/v1/hints";t.id&&(o="PATCH",n="/api/v1/hints/"+t.id);s.default.fetch(n,{method:o,credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(t)}).then(function(e){return e.json()}).then(function(e){e.success&&window.location.reload()})};var a=i(o("./node_modules/jquery/dist/jquery.js")),s=i(o("./CTFd/themes/core/assets/js/CTFd.js")),n=o("./CTFd/themes/core/assets/js/ezq.js");function i(e){return e&&e.__esModule?e:{default:e}}},"./CTFd/themes/admin/assets/js/challenges/requirements.js":function(e,t,o){Object.defineProperty(t,"__esModule",{value:!0}),t.addRequirement=function(e){e.preventDefault();var t=(0,a.default)("#prerequisite-add-form").serializeJSON();if(!t.prerequisite)return;window.CHALLENGE_REQUIREMENTS.prerequisites.push(parseInt(t.prerequisite));var o={requirements:window.CHALLENGE_REQUIREMENTS};s.default.fetch("/api/v1/challenges/"+window.CHALLENGE_ID,{method:"PATCH",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(o)}).then(function(e){return e.json()}).then(function(e){e.success&&window.location.reload()})},t.deleteRequirement=function(e){var t=(0,a.default)(this).attr("challenge-id"),o=(0,a.default)(this).parent().parent();window.CHALLENGE_REQUIREMENTS.prerequisites.pop(t);var n={requirements:window.CHALLENGE_REQUIREMENTS};s.default.fetch("/api/v1/challenges/"+window.CHALLENGE_ID,{method:"PATCH",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(n)}).then(function(e){return e.json()}).then(function(e){e.success&&o.remove()})};var a=n(o("./node_modules/jquery/dist/jquery.js")),s=n(o("./CTFd/themes/core/assets/js/CTFd.js"));function n(e){return e&&e.__esModule?e:{default:e}}},"./CTFd/themes/admin/assets/js/challenges/tags.js":function(e,t,o){Object.defineProperty(t,"__esModule",{value:!0}),t.deleteTag=i,t.addTag=function(e){if(13!=e.keyCode)return;var t=(0,n.default)(this),o={value:t.val(),challenge:window.CHALLENGE_ID};a.default.api.post_tag_list({},o).then(function(e){if(e.success){var t=(0,n.default)("{0}×".format(e.data.value,e.data.id));(0,n.default)("#challenge-tags").append(t),t.click(i)}}),t.val("")};var n=s(o("./node_modules/jquery/dist/jquery.js")),a=s(o("./CTFd/themes/core/assets/js/CTFd.js"));function s(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=(0,n.default)(this),o=t.attr("tag-id");a.default.api.delete_tag({tagId:o}).then(function(e){e.success&&t.parent().remove()})}},"./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue":function(e,t,o){o.r(t);var n=o("./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue?vue&type=template&id=50f8d42a&"),a=o("./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue?vue&type=script&lang=js&");for(var s in a)"default"!==s&&function(e){o.d(t,e,function(){return a[e]})}(s);var i=o("./node_modules/vue-loader/lib/runtime/componentNormalizer.js"),l=Object(i.a)(a.default,n.a,n.b,!1,null,null,null);l.options.__file="CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue",t.default=l.exports},"./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue?vue&type=script&lang=js&":function(e,t,o){o.r(t);var n=o("./node_modules/babel-loader/lib/index.js?!./node_modules/vue-loader/lib/index.js?!./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue?vue&type=script&lang=js&"),a=o.n(n);for(var s in n)"default"!==s&&function(e){o.d(t,e,function(){return n[e]})}(s);t.default=a.a},"./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue?vue&type=template&id=50f8d42a&":function(e,t,o){function n(){var o=this,e=o.$createElement,n=o._self._c||e;return n("div",{staticClass:"modal fade",attrs:{id:"media-modal",tabindex:"-1"}},[n("div",{staticClass:"modal-dialog modal-lg"},[n("div",{staticClass:"modal-content"},[o._m(0),o._v(" "),n("div",{staticClass:"modal-body"},[n("div",{staticClass:"modal-header"},[n("div",{staticClass:"container"},[n("div",{staticClass:"row mh-100"},[n("div",{staticClass:"col-md-6",attrs:{id:"media-library-list"}},o._l(o.files,function(t){return n("div",{key:t.id,staticClass:"media-item-wrapper"},[n("a",{attrs:{href:"javascript:void(0)"},on:{click:function(e){return o.selectFile(t),!1}}},[n("i",{class:o.getIconClass(t.location),attrs:{"aria-hidden":"true"}}),o._v(" "),n("small",{staticClass:"media-item-title"},[o._v(o._s(t.location.split("/").pop()))])])])}),0),o._v(" "),n("div",{staticClass:"col-md-6",attrs:{id:"media-library-details"}},[n("h4",{staticClass:"text-center"},[o._v("Media Details")]),o._v(" "),n("div",{attrs:{id:"media-item"}},[n("div",{staticClass:"text-center",attrs:{id:"media-icon"}},[this.selectedFile?n("div",["far fa-file-image"===o.getIconClass(this.selectedFile.location)?n("div",[n("img",{staticStyle:{"max-width":"100%","max-height":"100%","object-fit":"contain"},attrs:{src:o.buildSelectedFileUrl()}})]):n("div",[n("i",{class:o.getIconClass(this.selectedFile.location)+" fa-4x",attrs:{"aria-hidden":"true"}})])]):o._e()]),o._v(" "),n("br"),o._v(" "),this.selectedFile?n("div",{staticClass:"text-center",attrs:{id:"media-filename"}},[n("a",{attrs:{href:o.buildSelectedFileUrl(),target:"_blank"}},[o._v("\n "+o._s(this.selectedFile.location.split("/").pop())+"\n ")])]):o._e(),o._v(" "),n("br"),o._v(" "),n("div",{staticClass:"form-group"},[this.selectedFile?n("div",[o._v("\n Link:\n "),n("input",{staticClass:"form-control",attrs:{type:"text",id:"media-link",readonly:""},domProps:{value:o.buildSelectedFileUrl()}})]):n("div",[o._v("\n Link:\n "),n("input",{staticClass:"form-control",attrs:{type:"text",id:"media-link",readonly:""}})])]),o._v(" "),n("div",{staticClass:"form-group text-center"},[n("div",{staticClass:"row"},[n("div",{staticClass:"col-md-6"},[n("button",{staticClass:"btn btn-success w-100",attrs:{id:"media-insert","data-toggle":"tooltip","data-placement":"top",title:"Insert link into editor"},on:{click:o.insertSelectedFile}},[o._v("\n Insert\n ")])]),o._v(" "),n("div",{staticClass:"col-md-3"},[n("button",{staticClass:"btn btn-primary w-100",attrs:{id:"media-download","data-toggle":"tooltip","data-placement":"top",title:"Download file"},on:{click:o.downloadSelectedFile}},[n("i",{staticClass:"fas fa-download"})])]),o._v(" "),n("div",{staticClass:"col-md-3"},[n("button",{staticClass:"btn btn-danger w-100",attrs:{id:"media-delete","data-toggle":"tooltip","data-placement":"top",title:"Delete file"},on:{click:o.deleteSelectedFile}},[n("i",{staticClass:"far fa-trash-alt"})])])])])])])])])]),o._v(" "),o._m(1)]),o._v(" "),n("div",{staticClass:"modal-footer"},[n("div",{staticClass:"float-right"},[n("button",{staticClass:"btn btn-primary media-upload-button",attrs:{type:"submit"},on:{click:o.uploadChosenFiles}},[o._v("\n Upload\n ")])])])])])])}var a=[function(){var e=this,t=e.$createElement,o=e._self._c||t;return o("div",{staticClass:"modal-header"},[o("div",{staticClass:"container"},[o("div",{staticClass:"row"},[o("div",{staticClass:"col-md-12"},[o("h3",{staticClass:"text-center"},[e._v("Media Library")])])])]),e._v(" "),o("button",{staticClass:"close",attrs:{type:"button","data-dismiss":"modal","aria-label":"Close"}},[o("span",{attrs:{"aria-hidden":"true"}},[e._v("×")])])])},function(){var e=this,t=e.$createElement,o=e._self._c||t;return o("form",{attrs:{id:"media-library-upload",enctype:"multipart/form-data"}},[o("div",{staticClass:"form-group"},[o("label",{attrs:{for:"media-files"}},[e._v("\n Upload Files\n ")]),e._v(" "),o("input",{staticClass:"form-control-file",attrs:{type:"file",name:"file",id:"media-files",multiple:""}}),e._v(" "),o("sub",{staticClass:"help-block"},[e._v("\n Attach multiple files using Control+Click or Cmd+Click.\n ")])]),e._v(" "),o("input",{attrs:{type:"hidden",value:"page",name:"type"}})])}];n._withStripped=!0,o.d(t,"a",function(){return n}),o.d(t,"b",function(){return a})},"./CTFd/themes/admin/assets/js/pages/challenge.js":function(e,t,o){o("./CTFd/themes/admin/assets/js/pages/main.js");var n=o("./CTFd/themes/core/assets/js/utils.js"),l=f(o("./node_modules/jquery/dist/jquery.js"));o("./node_modules/bootstrap/js/dist/tab.js");var i=f(o("./CTFd/themes/core/assets/js/CTFd.js")),a=o("./CTFd/themes/core/assets/js/ezq.js"),d=f(o("./CTFd/themes/core/assets/js/helpers.js")),s=o("./CTFd/themes/admin/assets/js/challenges/files.js"),r=o("./CTFd/themes/admin/assets/js/challenges/tags.js"),c=o("./CTFd/themes/admin/assets/js/challenges/requirements.js"),u=o("./CTFd/themes/admin/assets/js/styles.js"),m=o("./CTFd/themes/admin/assets/js/challenges/hints.js"),p=o("./CTFd/themes/admin/assets/js/challenges/flags.js");function f(e){return e&&e.__esModule?e:{default:e}}function h(e){i.default.api.get_hint({hintId:e,preview:!0}).then(function(e){e.data.content&&function(e){(0,a.ezAlert)({title:"Hint",body:j.render(e.content),button:"Got it!"})}(e.data)})}var j=i.default.lib.markdown();function g(e,t){var o=e.data,n=(0,l.default)("#result-message"),a=(0,l.default)("#result-notification"),s=(0,l.default)("#submission-input");a.removeClass(),n.text(o.message),"authentication_required"!==o.status?("incorrect"===o.status?(a.addClass("alert alert-danger alert-dismissable text-center"),a.slideDown(),s.removeClass("correct"),s.addClass("wrong"),setTimeout(function(){s.removeClass("wrong")},3e3)):"correct"===o.status?(a.addClass("alert alert-success alert-dismissable text-center"),a.slideDown(),(0,l.default)(".challenge-solves").text(parseInt((0,l.default)(".challenge-solves").text().split(" ")[0])+1+" Solves"),s.val(""),s.removeClass("wrong"),s.addClass("correct")):"already_solved"===o.status?(a.addClass("alert alert-info alert-dismissable text-center"),a.slideDown(),s.addClass("correct")):"paused"===o.status?(a.addClass("alert alert-warning alert-dismissable text-center"),a.slideDown()):"ratelimited"===o.status&&(a.addClass("alert alert-warning alert-dismissable text-center"),a.slideDown(),s.addClass("too-fast"),setTimeout(function(){s.removeClass("too-fast")},3e3)),setTimeout(function(){(0,l.default)(".alert").slideUp(),(0,l.default)("#submit-key").removeClass("disabled-button"),(0,l.default)("#submit-key").prop("disabled",!1)},3e3),t&&t(o)):window.location=i.default.config.urlRoot+"/login?next="+i.default.config.urlRoot+window.location.pathname+window.location.hash}function _(t){i.default._internal.challenge={},l.default.getScript(i.default.config.urlRoot+t.scripts.view,function(){var e=t.create;(0,l.default)("#create-chal-entry-div").html(e),(0,u.bindMarkdownEditors)(),l.default.getScript(i.default.config.urlRoot+t.scripts.create,function(){(0,l.default)("#create-chal-entry-div form").submit(function(e){e.preventDefault();var t=(0,l.default)("#create-chal-entry-div form").serializeJSON();i.default.fetch("/api/v1/challenges",{method:"POST",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(t)}).then(function(e){return e.json()}).then(function(e){e.success&&((0,l.default)("#challenge-create-options #challenge_id").val(e.data.id),(0,l.default)("#challenge-create-options").modal())})})})})}function v(a){a.preventDefault();var s=(0,l.default)(a.target).serializeJSON(!0),o={challenge_id:s.challenge_id,content:s.flag||"",type:s.flag_type,data:s.flag_data?s.flag_data:""};Promise.all([new Promise(function(t,e){0!=o.content.length?i.default.fetch("/api/v1/flags",{method:"POST",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(o)}).then(function(e){t(e.json())}):t()}),new Promise(function(e,t){var o=a.target,n={challenge:s.challenge_id,type:"challenge"};(0,l.default)(o.elements.file).val()&&d.default.files.upload(o,n),e()})]).then(function(e){i.default.fetch("/api/v1/challenges/"+s.challenge_id,{method:"PATCH",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({state:s.state})}).then(function(e){return e.json()}).then(function(e){e.success&&setTimeout(function(){window.location=i.default.config.urlRoot+"/admin/challenges/"+s.challenge_id},700)})})}function y(e){var t=(0,l.default)(this).find("option:selected").data("meta");void 0!==t?_(t):(0,l.default)("#create-chal-entry-div").empty()}(0,l.default)(function(){(0,l.default)(".preview-challenge").click(function(e){window.challenge=new Object,i.default._internal.challenge={},l.default.get(i.default.config.urlRoot+"/api/v1/challenges/"+window.CHALLENGE_ID,function(e){var t=i.default._internal.challenge,o=e.data;o.solves=null,l.default.getScript(i.default.config.urlRoot+o.type_data.scripts.view,function(){(0,l.default)("#challenge-window").empty(),(0,l.default)("#challenge-window").append(o.view),(0,l.default)("#challenge-window #challenge-input").addClass("form-control"),(0,l.default)("#challenge-window #challenge-submit").addClass("btn btn-md btn-outline-secondary float-right"),(0,l.default)(".challenge-solves").hide(),(0,l.default)(".nav-tabs a").click(function(e){e.preventDefault(),(0,l.default)(this).tab("show")}),(0,l.default)("#challenge-window").on("hide.bs.modal",function(e){(0,l.default)("#challenge-input").removeClass("wrong"),(0,l.default)("#challenge-input").removeClass("correct"),(0,l.default)("#incorrect-key").slideUp(),(0,l.default)("#correct-key").slideUp(),(0,l.default)("#already-solved").slideUp(),(0,l.default)("#too-fast").slideUp()}),(0,l.default)(".load-hint").on("click",function(e){h((0,l.default)(this).data("hint-id"))}),(0,l.default)("#challenge-submit").click(function(e){e.preventDefault(),(0,l.default)("#challenge-submit").addClass("disabled-button"),(0,l.default)("#challenge-submit").prop("disabled",!0),i.default._internal.challenge.submit(!0).then(g)}),(0,l.default)("#challenge-input").keyup(function(e){13==e.keyCode&&(0,l.default)("#challenge-submit").click()}),t.postRender(),window.location.replace(window.location.href.split("#")[0]+"#preview"),(0,l.default)("#challenge-window").modal()})})}),(0,l.default)(".delete-challenge").click(function(e){(0,a.ezQuery)({title:"Delete Challenge",body:"Are you sure you want to delete {0}".format(""+(0,n.htmlEntities)(window.CHALLENGE_NAME)+""),success:function(){i.default.fetch("/api/v1/challenges/"+window.CHALLENGE_ID,{method:"DELETE"}).then(function(e){return e.json()}).then(function(e){e.success&&(window.location=i.default.config.urlRoot+"/admin/challenges")})}})}),(0,l.default)("#challenge-update-container > form").submit(function(e){e.preventDefault();var o=(0,l.default)(e.target).serializeJSON(!0);i.default.fetch("/api/v1/challenges/"+window.CHALLENGE_ID+"/flags",{method:"GET",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"}}).then(function(e){return e.json()}).then(function(e){function t(){i.default.fetch("/api/v1/challenges/"+window.CHALLENGE_ID,{method:"PATCH",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(o)}).then(function(e){return e.json()}).then(function(e){if(e.success){switch((0,l.default)(".challenge-state").text(e.data.state),e.data.state){case"visible":(0,l.default)(".challenge-state").removeClass("badge-danger").addClass("badge-success");break;case"hidden":(0,l.default)(".challenge-state").removeClass("badge-success").addClass("badge-danger")}(0,a.ezToast)({title:"Success",body:"Your challenge has been updated!"})}})}0===e.data.length&&"visible"===o.state?(0,a.ezQuery)({title:"Missing Flags",body:"This challenge does not have any flags meaning it may be unsolveable. Are you sure you'd like to update this challenge?",success:t}):t()})}),(0,l.default)("#challenge-create-options form").submit(v),(0,l.default)("#tags-add-input").keyup(r.addTag),(0,l.default)(".delete-tag").click(r.deleteTag),(0,l.default)("#prerequisite-add-form").submit(c.addRequirement),(0,l.default)(".delete-requirement").click(c.deleteRequirement),(0,l.default)("#file-add-form").submit(s.addFile),(0,l.default)(".delete-file").click(s.deleteFile),(0,l.default)("#hint-add-button").click(m.showHintModal),(0,l.default)(".delete-hint").click(m.deleteHint),(0,l.default)(".edit-hint").click(m.showEditHintModal),(0,l.default)("#hint-edit-form").submit(m.editHint),(0,l.default)("#flag-add-button").click(p.addFlagModal),(0,l.default)(".delete-flag").click(p.deleteFlag),(0,l.default)("#flags-create-select").change(p.flagTypeSelect),(0,l.default)(".edit-flag").click(p.editFlagModal),l.default.get(i.default.config.urlRoot+"/api/v1/challenges/types",function(e){(0,l.default)("#create-chals-select").empty();var t=e.data,o=Object.keys(t).length;if(1 -- "),t){var a=t[n],s=(0,l.default)("