From e5518b54bd61ada7462983a97a907ea900006a1b Mon Sep 17 00:00:00 2001 From: Kevin Chung Date: Wed, 21 Jun 2023 20:31:25 -0400 Subject: [PATCH 1/4] Improve rendering long submisisons admin panel (#2338) * Truncate submissions in the Admin Panel but have some ways to show them fully expanded and add a copy to clipboard button * Closes #2243 --- .../admin/assets/js/pages/submissions.js | 48 +++++++++++++++++++ .../admin/static/js/pages/submissions.dev.js | 2 +- .../admin/static/js/pages/submissions.min.js | 2 +- CTFd/themes/admin/templates/submissions.html | 25 +++++++++- 4 files changed, 73 insertions(+), 4 deletions(-) diff --git a/CTFd/themes/admin/assets/js/pages/submissions.js b/CTFd/themes/admin/assets/js/pages/submissions.js index d0f4fd2b..0ec44362 100644 --- a/CTFd/themes/admin/assets/js/pages/submissions.js +++ b/CTFd/themes/admin/assets/js/pages/submissions.js @@ -61,7 +61,55 @@ function deleteSelectedSubmissions(_event) { }); } +function showFlagsToggle(_event) { + const urlParams = new URLSearchParams(window.location.search); + if (urlParams.has("full")) { + urlParams.delete("full"); + } else { + urlParams.set("full", "true"); + } + window.location.href = `${window.location.pathname}?${urlParams.toString()}`; +} + +function showFlag(event) { + let target = $(event.currentTarget); + let eye = target.find("i"); + let flag = target.parent().find("pre"); + if (!flag.hasClass("full-flag")) { + flag.text(flag.attr("title")); + flag.addClass("full-flag"); + eye.addClass("fa-eye-slash"); + eye.removeClass("fa-eye"); + } else { + flag.text(flag.attr("title").substring(0, 42) + "..."); + flag.removeClass("full-flag"); + eye.addClass("fa-eye"); + eye.removeClass("fa-eye-slash"); + } +} + +function copyFlag(event) { + let target = $(event.currentTarget); + let flag = target.parent().find("pre"); + let text = flag.attr("title"); + navigator.clipboard.writeText(text); + + $(event.currentTarget).tooltip({ + title: "Copied!", + trigger: "manual" + }); + $(event.currentTarget).tooltip("show"); + + setTimeout(function() { + $(event.currentTarget).tooltip("hide"); + }, 1500); +} + $(() => { + $("#show-full-flags-button").click(showFlagsToggle); + $("#show-short-flags-button").click(showFlagsToggle); + $(".show-flag").click(showFlag); + $(".copy-flag").click(copyFlag); $(".delete-correct-submission").click(deleteCorrectSubmission); $("#submission-delete-button").click(deleteSelectedSubmissions); }); diff --git a/CTFd/themes/admin/static/js/pages/submissions.dev.js b/CTFd/themes/admin/static/js/pages/submissions.dev.js index d79d0cdb..841169b9 100644 --- a/CTFd/themes/admin/static/js/pages/submissions.dev.js +++ b/CTFd/themes/admin/static/js/pages/submissions.dev.js @@ -162,7 +162,7 @@ /***/ (function(module, exports, __webpack_require__) { ; -eval("\n\n__webpack_require__(/*! ./main */ \"./CTFd/themes/admin/assets/js/pages/main.js\");\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd */ \"./CTFd/themes/core/assets/js/CTFd.js\"));\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _utils = __webpack_require__(/*! core/utils */ \"./CTFd/themes/core/assets/js/utils.js\");\n\nvar _ezq = __webpack_require__(/*! core/ezq */ \"./CTFd/themes/core/assets/js/ezq.js\");\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nfunction _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof Symbol === \"undefined\" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === \"number\") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it[\"return\"] != null) it[\"return\"](); } finally { if (didErr) throw err; } } }; }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\n\nfunction deleteCorrectSubmission(_event) {\n var key_id = (0, _jquery[\"default\"])(this).data(\"submission-id\");\n var $elem = (0, _jquery[\"default\"])(this).parent().parent();\n var chal_name = $elem.find(\".chal\").text().trim();\n var team_name = $elem.find(\".team\").text().trim();\n var row = (0, _jquery[\"default\"])(this).parent().parent();\n (0, _ezq.ezQuery)({\n title: \"Delete Submission\",\n body: \"Are you sure you want to delete correct submission from {0} for challenge {1}\".format(\"\" + (0, _utils.htmlEntities)(team_name) + \"\", \"\" + (0, _utils.htmlEntities)(chal_name) + \"\"),\n success: function success() {\n _CTFd[\"default\"].api.delete_submission({\n submissionId: key_id\n }).then(function (response) {\n if (response.success) {\n row.remove();\n }\n });\n }\n });\n}\n\nfunction deleteSelectedSubmissions(_event) {\n var submissionIDs = (0, _jquery[\"default\"])(\"input[data-submission-id]:checked\").map(function () {\n return (0, _jquery[\"default\"])(this).data(\"submission-id\");\n });\n var target = submissionIDs.length === 1 ? \"submission\" : \"submissions\";\n (0, _ezq.ezQuery)({\n title: \"Delete Submissions\",\n body: \"Are you sure you want to delete \".concat(submissionIDs.length, \" \").concat(target, \"?\"),\n success: function success() {\n var reqs = [];\n\n var _iterator = _createForOfIteratorHelper(submissionIDs),\n _step;\n\n try {\n for (_iterator.s(); !(_step = _iterator.n()).done;) {\n var subId = _step.value;\n reqs.push(_CTFd[\"default\"].api.delete_submission({\n submissionId: subId\n }));\n }\n } catch (err) {\n _iterator.e(err);\n } finally {\n _iterator.f();\n }\n\n Promise.all(reqs).then(function (_responses) {\n window.location.reload();\n });\n }\n });\n}\n\n(0, _jquery[\"default\"])(function () {\n (0, _jquery[\"default\"])(\".delete-correct-submission\").click(deleteCorrectSubmission);\n (0, _jquery[\"default\"])(\"#submission-delete-button\").click(deleteSelectedSubmissions);\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/submissions.js?"); +eval("\n\n__webpack_require__(/*! ./main */ \"./CTFd/themes/admin/assets/js/pages/main.js\");\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd */ \"./CTFd/themes/core/assets/js/CTFd.js\"));\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _utils = __webpack_require__(/*! core/utils */ \"./CTFd/themes/core/assets/js/utils.js\");\n\nvar _ezq = __webpack_require__(/*! core/ezq */ \"./CTFd/themes/core/assets/js/ezq.js\");\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nfunction _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof Symbol === \"undefined\" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === \"number\") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it[\"return\"] != null) it[\"return\"](); } finally { if (didErr) throw err; } } }; }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }\n\nfunction deleteCorrectSubmission(_event) {\n var key_id = (0, _jquery[\"default\"])(this).data(\"submission-id\");\n var $elem = (0, _jquery[\"default\"])(this).parent().parent();\n var chal_name = $elem.find(\".chal\").text().trim();\n var team_name = $elem.find(\".team\").text().trim();\n var row = (0, _jquery[\"default\"])(this).parent().parent();\n (0, _ezq.ezQuery)({\n title: \"Delete Submission\",\n body: \"Are you sure you want to delete correct submission from {0} for challenge {1}\".format(\"\" + (0, _utils.htmlEntities)(team_name) + \"\", \"\" + (0, _utils.htmlEntities)(chal_name) + \"\"),\n success: function success() {\n _CTFd[\"default\"].api.delete_submission({\n submissionId: key_id\n }).then(function (response) {\n if (response.success) {\n row.remove();\n }\n });\n }\n });\n}\n\nfunction deleteSelectedSubmissions(_event) {\n var submissionIDs = (0, _jquery[\"default\"])(\"input[data-submission-id]:checked\").map(function () {\n return (0, _jquery[\"default\"])(this).data(\"submission-id\");\n });\n var target = submissionIDs.length === 1 ? \"submission\" : \"submissions\";\n (0, _ezq.ezQuery)({\n title: \"Delete Submissions\",\n body: \"Are you sure you want to delete \".concat(submissionIDs.length, \" \").concat(target, \"?\"),\n success: function success() {\n var reqs = [];\n\n var _iterator = _createForOfIteratorHelper(submissionIDs),\n _step;\n\n try {\n for (_iterator.s(); !(_step = _iterator.n()).done;) {\n var subId = _step.value;\n reqs.push(_CTFd[\"default\"].api.delete_submission({\n submissionId: subId\n }));\n }\n } catch (err) {\n _iterator.e(err);\n } finally {\n _iterator.f();\n }\n\n Promise.all(reqs).then(function (_responses) {\n window.location.reload();\n });\n }\n });\n}\n\nfunction showFlagsToggle(_event) {\n var urlParams = new URLSearchParams(window.location.search);\n\n if (urlParams.has(\"full\")) {\n urlParams[\"delete\"](\"full\");\n } else {\n urlParams.set(\"full\", \"true\");\n }\n\n window.location.href = \"\".concat(window.location.pathname, \"?\").concat(urlParams.toString());\n}\n\nfunction showFlag(event) {\n var target = (0, _jquery[\"default\"])(event.currentTarget);\n var eye = target.find(\"i\");\n var flag = target.parent().find(\"pre\");\n\n if (!flag.hasClass(\"full-flag\")) {\n flag.text(flag.attr(\"title\"));\n flag.addClass(\"full-flag\");\n eye.addClass(\"fa-eye-slash\");\n eye.removeClass(\"fa-eye\");\n } else {\n flag.text(flag.attr(\"title\").substring(0, 42) + \"...\");\n flag.removeClass(\"full-flag\");\n eye.addClass(\"fa-eye\");\n eye.removeClass(\"fa-eye-slash\");\n }\n}\n\nfunction copyFlag(event) {\n var target = (0, _jquery[\"default\"])(event.currentTarget);\n var flag = target.parent().find(\"pre\");\n var text = flag.attr(\"title\");\n navigator.clipboard.writeText(text);\n (0, _jquery[\"default\"])(event.currentTarget).tooltip({\n title: \"Copied!\",\n trigger: \"manual\"\n });\n (0, _jquery[\"default\"])(event.currentTarget).tooltip(\"show\");\n setTimeout(function () {\n (0, _jquery[\"default\"])(event.currentTarget).tooltip(\"hide\");\n }, 1500);\n}\n\n(0, _jquery[\"default\"])(function () {\n (0, _jquery[\"default\"])(\"#show-full-flags-button\").click(showFlagsToggle);\n (0, _jquery[\"default\"])(\"#show-short-flags-button\").click(showFlagsToggle);\n (0, _jquery[\"default\"])(\".show-flag\").click(showFlag);\n (0, _jquery[\"default\"])(\".copy-flag\").click(copyFlag);\n (0, _jquery[\"default\"])(\".delete-correct-submission\").click(deleteCorrectSubmission);\n (0, _jquery[\"default\"])(\"#submission-delete-button\").click(deleteSelectedSubmissions);\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/submissions.js?"); /***/ }) diff --git a/CTFd/themes/admin/static/js/pages/submissions.min.js b/CTFd/themes/admin/static/js/pages/submissions.min.js index 43a0d5a7..d98aeb67 100644 --- a/CTFd/themes/admin/static/js/pages/submissions.min.js +++ b/CTFd/themes/admin/static/js/pages/submissions.min.js @@ -1 +1 @@ -!function(d){function e(e){for(var t,i,o=e[0],n=e[1],r=e[2],a=0,s=[];a=e.length?{done:!0}:{done:!1,value:e[n++]}},e:function(e){throw e},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var r,a=!0,s=!1;return{s:function(){i=e[Symbol.iterator]()},n:function(){var e=i.next();return a=e.done,e},e:function(e){s=!0,r=e},f:function(){try{a||null==i.return||i.return()}finally{if(s)throw r}}}}function p(e,t){(null==t||t>e.length)&&(t=e.length);for(var i=0,o=new Array(t);i"+(0,d.htmlEntities)(n)+"",""+(0,d.htmlEntities)(o)+""),success:function(){a.default.api.delete_submission({submissionId:t}).then(function(e){e.success&&r.remove()})}})}function l(e){var n=(0,s.default)("input[data-submission-id]:checked").map(function(){return(0,s.default)(this).data("submission-id")}),t=1===n.length?"submission":"submissions";(0,c.ezQuery)({title:"Delete Submissions",body:"Are you sure you want to delete ".concat(n.length," ").concat(t,"?"),success:function(){var e,t=[],i=r(n);try{for(i.s();!(e=i.n()).done;){var o=e.value;t.push(a.default.api.delete_submission({submissionId:o}))}}catch(e){i.e(e)}finally{i.f()}Promise.all(t).then(function(e){window.location.reload()})}})}(0,s.default)(function(){(0,s.default)(".delete-correct-submission").click(n),(0,s.default)("#submission-delete-button").click(l)})},"./CTFd/themes/admin/assets/js/styles.js":function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.showMediaLibrary=p,t.bindMarkdownEditor=l,t.bindMarkdownEditors=u,t.default=void 0,i("./node_modules/bootstrap/dist/js/bootstrap.bundle.js");var o=i("./CTFd/themes/core/assets/js/utils.js"),n=c(i("./node_modules/jquery/dist/jquery.js")),r=c(i("./node_modules/easymde/src/js/easymde.js")),a=c(i("./node_modules/vue/dist/vue.esm.browser.js")),s=c(i("./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue")),d=c(i("./node_modules/highlight.js/lib/index.js"));function c(e){return e&&e.__esModule?e:{default:e}}function p(e){var t=a.default.extend(s.default),i=document.createElement("div");document.querySelector("main").appendChild(i);var o=new t({propsData:{editor:e}}).$mount(i);(0,n.default)("#media-modal").on("hidden.bs.modal",function(e){o.$destroy(),(0,n.default)("#media-modal").remove()}),(0,n.default)("#media-modal").modal()}function l(e){var t;!1===e.hasOwnProperty("mde")&&(t=new r.default({autoDownloadFontAwesome:!1,toolbar:["bold","italic","heading","|","quote","unordered-list","ordered-list","|","link","image",{name:"media",action:function(e){p(e)},className:"fas fa-file-upload",title:"Media Library"},"|","preview","guide"],element:e,initialValue:(0,n.default)(e).val(),forceSync:!0,minHeight:"200px",renderingConfig:{codeSyntaxHighlighting:!0,hljs:d.default}}),e.mde=t,e.codemirror=t.codemirror,(0,n.default)(e).on("change keyup paste",function(){t.codemirror.getDoc().setValue((0,n.default)(e).val()),t.codemirror.refresh()}))}function u(){(0,n.default)("textarea.markdown").each(function(e,t){l(t)})}t.default=function(){(0,n.default)(":input").each(function(){(0,n.default)(this).data("initial",(0,n.default)(this).val())}),(0,n.default)(function(){(0,n.default)("tr[data-href], td[data-href]").click(function(){var e;return getSelection().toString()||(e=(0,n.default)(this).attr("data-href"))&&(window.location=e),!1}),(0,n.default)("[data-checkbox]").click(function(e){(0,n.default)(e.target).is("input[type=checkbox]")||(0,n.default)(this).find("input[type=checkbox]").click(),e.stopImmediatePropagation()}),(0,n.default)("[data-checkbox-all]").on("click change",function(e){var t=(0,n.default)(this).prop("checked"),i=(0,n.default)(this).index()+1;(0,n.default)(this).closest("table").find("tr td:nth-child(".concat(i,") input[type=checkbox]")).prop("checked",t),e.stopImmediatePropagation()}),(0,n.default)("tr[data-href] a, tr[data-href] button").click(function(e){(0,n.default)(this).attr("data-dismiss")||e.stopPropagation()}),(0,n.default)(".page-select").change(function(){var e=new URL(window.location);e.searchParams.set("page",this.value),window.location.href=e.toString()}),(0,n.default)('a[data-toggle="tab"]').on("shown.bs.tab",function(e){sessionStorage.setItem("activeTab",(0,n.default)(e.target).attr("href"))});var e,t=sessionStorage.getItem("activeTab");t&&((e=(0,n.default)('.nav-tabs a[href="'.concat(t,'"], .nav-pills a[href="').concat(t,'"]'))).length?e.tab("show"):sessionStorage.removeItem("activeTab")),u(),(0,o.makeSortableTables)(),(0,n.default)('[data-toggle="tooltip"]').tooltip(),document.querySelectorAll("pre code").forEach(function(e){d.default.highlightBlock(e)})})}},"./CTFd/themes/core/assets/js/CTFd.js":function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var o=l(i("./node_modules/jquery/dist/jquery.js")),n=l(i("./node_modules/dayjs/dayjs.min.js")),r=l(i("./node_modules/markdown-it/index.js"));i("./CTFd/themes/core/assets/js/patch.js");var a=l(i("./CTFd/themes/core/assets/js/fetch.js")),s=l(i("./CTFd/themes/core/assets/js/config.js")),d=i("./CTFd/themes/core/assets/js/api.js"),c=l(i("./CTFd/themes/core/assets/js/ezq.js")),p=i("./CTFd/themes/core/assets/js/utils.js");function l(e){return e&&e.__esModule?e:{default:e}}function u(t,e){var i,o=Object.keys(t);return Object.getOwnPropertySymbols&&(i=Object.getOwnPropertySymbols(t),e&&(i=i.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),o.push.apply(o,i)),o}function f(n){for(var e=1;e".concat(e.body,"

")):i.find(".modal-body").append((0,s.default)(e.body));var o=(0,s.default)(l.format(e.button));return e.success&&(0,s.default)(o).click(function(){e.success()}),e.large&&i.find(".modal-dialog").addClass("modal-lg"),i.find(".modal-footer").append(o),i.find("pre code").each(function(e){r.default.highlightBlock(this)}),(0,s.default)("main").append(i),i.modal("show"),(0,s.default)(i).on("hidden.bs.modal",function(){(0,s.default)(this).modal("dispose")}),i}function h(e){(0,s.default)("#ezq--notifications-toast-container").length||(0,s.default)("body").append((0,s.default)("
").attr({id:"ezq--notifications-toast-container"}).css({position:"fixed",bottom:"0",right:"0","min-width":"20%"}));var t,i=d.format(e.title,e.body),o=(0,s.default)(i);e.onclose&&(0,s.default)(o).find("button[data-dismiss=toast]").click(function(){e.onclose()}),e.onclick&&((t=(0,s.default)(o).find(".toast-body")).addClass("cursor-pointer"),t.click(function(){e.onclick()}));var n=!1!==e.autohide,r=!1!==e.animation,a=e.delay||1e4;return(0,s.default)("#ezq--notifications-toast-container").prepend(o),o.toast({autohide:n,delay:a,animation:r}),o.toast("show"),o}function g(e){var t=a.format(e.title),i=(0,s.default)(t);"string"==typeof e.body?i.find(".modal-body").append("

".concat(e.body,"

")):i.find(".modal-body").append((0,s.default)(e.body));var o=(0,s.default)(f),n=(0,s.default)(u);return i.find(".modal-footer").append(n),i.find(".modal-footer").append(o),i.find("pre code").each(function(e){r.default.highlightBlock(this)}),(0,s.default)("main").append(i),(0,s.default)(i).on("hidden.bs.modal",function(){(0,s.default)(this).modal("dispose")}),(0,s.default)(o).click(function(){e.success()}),i.modal("show"),i}function v(e){if(e.target){var t=(0,s.default)(e.target);return t.find(".progress-bar").css("width",e.width+"%"),t}var i=c.format(e.width),o=a.format(e.title),n=(0,s.default)(o);return n.find(".modal-body").append((0,s.default)(i)),(0,s.default)("main").append(n),n.modal("show")}function y(e){var t={success:p,error:n}[e.type].format(e.body);return(0,s.default)(t)}var j={ezAlert:m,ezToast:h,ezQuery:g,ezProgressBar:v,ezBadge:y};t.default=j},"./CTFd/themes/core/assets/js/fetch.js":function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,i("./node_modules/whatwg-fetch/fetch.js");var o,n=(o=i("./CTFd/themes/core/assets/js/config.js"))&&o.__esModule?o:{default:o};var r=window.fetch;t.default=function(e,t){return void 0===t&&(t={method:"GET",credentials:"same-origin",headers:{}}),e=n.default.urlRoot+e,void 0===t.headers&&(t.headers={}),t.credentials="same-origin",t.headers.Accept="application/json",t.headers["Content-Type"]="application/json",t.headers["CSRF-Token"]=n.default.csrfNonce,r(e,t)}},"./CTFd/themes/core/assets/js/patch.js":function(e,t,i){var o,s=(o=i("./node_modules/q/q.js"))&&o.__esModule?o:{default:o},n=i("./CTFd/themes/core/assets/js/api.js");function a(t,e){var i,o=Object.keys(t);return Object.getOwnPropertySymbols&&(i=Object.getOwnPropertySymbols(t),e&&(i=i.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),o.push.apply(o,i)),o}function r(n){for(var e=1;e'),(0,a.default)("th.sort-col").click(function(){var n,e=(0,a.default)(this).parents("table").eq(0),t=e.find("tr:gt(0)").toArray().sort((n=(0,a.default)(this).index(),function(e,t){var i=r(e,n),o=r(t,n);return a.default.isNumeric(i)&&a.default.isNumeric(o)?i-o:i.toString().localeCompare(o)}));this.asc=!this.asc,this.asc||(t=t.reverse());for(var i=0;i").text(e).html()};var o,a=(o=i("./node_modules/jquery/dist/jquery.js"))&&o.__esModule?o:{default:o};function n(){this.id=Math.random(),this.isMaster=!1,this.others={},window.addEventListener("storage",this,!1),window.addEventListener("unload",this,!1),this.broadcast("hello");var t=this;this._checkTimeout=setTimeout(function e(){t.check(),t._checkTimeout=setTimeout(e,9e3)},500),this._pingTimeout=setTimeout(function e(){t.sendPing(),t._pingTimeout=setTimeout(e,17e3)},17e3)}a.default.fn.serializeJSON=function(i){var o={},n=(0,a.default)(this),e=n.serializeArray();return(e=(e=e.concat(n.find("input[type=checkbox]:checked").map(function(){return{name:this.name,value:!0}}).get())).concat(n.find("input[type=checkbox]:not(:checked)").map(function(){return{name:this.name,value:!1}}).get())).map(function(e){var t;i&&(null===e.value||""===e.value)&&(t=n.find(":input[name='".concat(e.name,"']"))).data("initial")===t.val()||(o[e.name]=e.value)}),o},String.prototype.format=String.prototype.f=function(){for(var e=this,t=arguments.length;t--;)e=e.replace(new RegExp("\\{"+t+"\\}","gm"),arguments[t]);return e},String.prototype.hashCode=function(){var e,t,i=0;if(0==this.length)return i;for(e=0,t=this.length;e=e.length?{done:!0}:{done:!1,value:e[n++]}},e:function(e){throw e},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var r,a=!0,s=!1;return{s:function(){i=e[Symbol.iterator]()},n:function(){var e=i.next();return a=e.done,e},e:function(e){s=!0,r=e},f:function(){try{a||null==i.return||i.return()}finally{if(s)throw r}}}}function p(e,t){(null==t||t>e.length)&&(t=e.length);for(var i=0,o=new Array(t);i"+(0,d.htmlEntities)(n)+"",""+(0,d.htmlEntities)(o)+""),success:function(){a.default.api.delete_submission({submissionId:t}).then(function(e){e.success&&r.remove()})}})}function l(e){var n=(0,s.default)("input[data-submission-id]:checked").map(function(){return(0,s.default)(this).data("submission-id")}),t=1===n.length?"submission":"submissions";(0,c.ezQuery)({title:"Delete Submissions",body:"Are you sure you want to delete ".concat(n.length," ").concat(t,"?"),success:function(){var e,t=[],i=r(n);try{for(i.s();!(e=i.n()).done;){var o=e.value;t.push(a.default.api.delete_submission({submissionId:o}))}}catch(e){i.e(e)}finally{i.f()}Promise.all(t).then(function(e){window.location.reload()})}})}function u(e){var t=new URLSearchParams(window.location.search);t.has("full")?t.delete("full"):t.set("full","true"),window.location.href="".concat(window.location.pathname,"?").concat(t.toString())}function f(e){var t=(0,s.default)(e.currentTarget),i=t.find("i"),o=t.parent().find("pre");o.hasClass("full-flag")?(o.text(o.attr("title").substring(0,42)+"..."),o.removeClass("full-flag"),i.addClass("fa-eye"),i.removeClass("fa-eye-slash")):(o.text(o.attr("title")),o.addClass("full-flag"),i.addClass("fa-eye-slash"),i.removeClass("fa-eye"))}function m(e){var t=(0,s.default)(e.currentTarget).parent().find("pre").attr("title");navigator.clipboard.writeText(t),(0,s.default)(e.currentTarget).tooltip({title:"Copied!",trigger:"manual"}),(0,s.default)(e.currentTarget).tooltip("show"),setTimeout(function(){(0,s.default)(e.currentTarget).tooltip("hide")},1500)}(0,s.default)(function(){(0,s.default)("#show-full-flags-button").click(u),(0,s.default)("#show-short-flags-button").click(u),(0,s.default)(".show-flag").click(f),(0,s.default)(".copy-flag").click(m),(0,s.default)(".delete-correct-submission").click(n),(0,s.default)("#submission-delete-button").click(l)})},"./CTFd/themes/admin/assets/js/styles.js":function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.showMediaLibrary=p,t.bindMarkdownEditor=l,t.bindMarkdownEditors=u,t.default=void 0,i("./node_modules/bootstrap/dist/js/bootstrap.bundle.js");var o=i("./CTFd/themes/core/assets/js/utils.js"),n=c(i("./node_modules/jquery/dist/jquery.js")),r=c(i("./node_modules/easymde/src/js/easymde.js")),a=c(i("./node_modules/vue/dist/vue.esm.browser.js")),s=c(i("./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue")),d=c(i("./node_modules/highlight.js/lib/index.js"));function c(e){return e&&e.__esModule?e:{default:e}}function p(e){var t=a.default.extend(s.default),i=document.createElement("div");document.querySelector("main").appendChild(i);var o=new t({propsData:{editor:e}}).$mount(i);(0,n.default)("#media-modal").on("hidden.bs.modal",function(e){o.$destroy(),(0,n.default)("#media-modal").remove()}),(0,n.default)("#media-modal").modal()}function l(e){var t;!1===e.hasOwnProperty("mde")&&(t=new r.default({autoDownloadFontAwesome:!1,toolbar:["bold","italic","heading","|","quote","unordered-list","ordered-list","|","link","image",{name:"media",action:function(e){p(e)},className:"fas fa-file-upload",title:"Media Library"},"|","preview","guide"],element:e,initialValue:(0,n.default)(e).val(),forceSync:!0,minHeight:"200px",renderingConfig:{codeSyntaxHighlighting:!0,hljs:d.default}}),e.mde=t,e.codemirror=t.codemirror,(0,n.default)(e).on("change keyup paste",function(){t.codemirror.getDoc().setValue((0,n.default)(e).val()),t.codemirror.refresh()}))}function u(){(0,n.default)("textarea.markdown").each(function(e,t){l(t)})}t.default=function(){(0,n.default)(":input").each(function(){(0,n.default)(this).data("initial",(0,n.default)(this).val())}),(0,n.default)(function(){(0,n.default)("tr[data-href], td[data-href]").click(function(){var e;return getSelection().toString()||(e=(0,n.default)(this).attr("data-href"))&&(window.location=e),!1}),(0,n.default)("[data-checkbox]").click(function(e){(0,n.default)(e.target).is("input[type=checkbox]")||(0,n.default)(this).find("input[type=checkbox]").click(),e.stopImmediatePropagation()}),(0,n.default)("[data-checkbox-all]").on("click change",function(e){var t=(0,n.default)(this).prop("checked"),i=(0,n.default)(this).index()+1;(0,n.default)(this).closest("table").find("tr td:nth-child(".concat(i,") input[type=checkbox]")).prop("checked",t),e.stopImmediatePropagation()}),(0,n.default)("tr[data-href] a, tr[data-href] button").click(function(e){(0,n.default)(this).attr("data-dismiss")||e.stopPropagation()}),(0,n.default)(".page-select").change(function(){var e=new URL(window.location);e.searchParams.set("page",this.value),window.location.href=e.toString()}),(0,n.default)('a[data-toggle="tab"]').on("shown.bs.tab",function(e){sessionStorage.setItem("activeTab",(0,n.default)(e.target).attr("href"))});var e,t=sessionStorage.getItem("activeTab");t&&((e=(0,n.default)('.nav-tabs a[href="'.concat(t,'"], .nav-pills a[href="').concat(t,'"]'))).length?e.tab("show"):sessionStorage.removeItem("activeTab")),u(),(0,o.makeSortableTables)(),(0,n.default)('[data-toggle="tooltip"]').tooltip(),document.querySelectorAll("pre code").forEach(function(e){d.default.highlightBlock(e)})})}},"./CTFd/themes/core/assets/js/CTFd.js":function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var o=l(i("./node_modules/jquery/dist/jquery.js")),n=l(i("./node_modules/dayjs/dayjs.min.js")),r=l(i("./node_modules/markdown-it/index.js"));i("./CTFd/themes/core/assets/js/patch.js");var a=l(i("./CTFd/themes/core/assets/js/fetch.js")),s=l(i("./CTFd/themes/core/assets/js/config.js")),d=i("./CTFd/themes/core/assets/js/api.js"),c=l(i("./CTFd/themes/core/assets/js/ezq.js")),p=i("./CTFd/themes/core/assets/js/utils.js");function l(e){return e&&e.__esModule?e:{default:e}}function u(t,e){var i,o=Object.keys(t);return Object.getOwnPropertySymbols&&(i=Object.getOwnPropertySymbols(t),e&&(i=i.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),o.push.apply(o,i)),o}function f(n){for(var e=1;e".concat(e.body,"

")):i.find(".modal-body").append((0,s.default)(e.body));var o=(0,s.default)(l.format(e.button));return e.success&&(0,s.default)(o).click(function(){e.success()}),e.large&&i.find(".modal-dialog").addClass("modal-lg"),i.find(".modal-footer").append(o),i.find("pre code").each(function(e){r.default.highlightBlock(this)}),(0,s.default)("main").append(i),i.modal("show"),(0,s.default)(i).on("hidden.bs.modal",function(){(0,s.default)(this).modal("dispose")}),i}function h(e){(0,s.default)("#ezq--notifications-toast-container").length||(0,s.default)("body").append((0,s.default)("
").attr({id:"ezq--notifications-toast-container"}).css({position:"fixed",bottom:"0",right:"0","min-width":"20%"}));var t,i=d.format(e.title,e.body),o=(0,s.default)(i);e.onclose&&(0,s.default)(o).find("button[data-dismiss=toast]").click(function(){e.onclose()}),e.onclick&&((t=(0,s.default)(o).find(".toast-body")).addClass("cursor-pointer"),t.click(function(){e.onclick()}));var n=!1!==e.autohide,r=!1!==e.animation,a=e.delay||1e4;return(0,s.default)("#ezq--notifications-toast-container").prepend(o),o.toast({autohide:n,delay:a,animation:r}),o.toast("show"),o}function g(e){var t=a.format(e.title),i=(0,s.default)(t);"string"==typeof e.body?i.find(".modal-body").append("

".concat(e.body,"

")):i.find(".modal-body").append((0,s.default)(e.body));var o=(0,s.default)(f),n=(0,s.default)(u);return i.find(".modal-footer").append(n),i.find(".modal-footer").append(o),i.find("pre code").each(function(e){r.default.highlightBlock(this)}),(0,s.default)("main").append(i),(0,s.default)(i).on("hidden.bs.modal",function(){(0,s.default)(this).modal("dispose")}),(0,s.default)(o).click(function(){e.success()}),i.modal("show"),i}function v(e){if(e.target){var t=(0,s.default)(e.target);return t.find(".progress-bar").css("width",e.width+"%"),t}var i=c.format(e.width),o=a.format(e.title),n=(0,s.default)(o);return n.find(".modal-body").append((0,s.default)(i)),(0,s.default)("main").append(n),n.modal("show")}function y(e){var t={success:p,error:n}[e.type].format(e.body);return(0,s.default)(t)}var j={ezAlert:m,ezToast:h,ezQuery:g,ezProgressBar:v,ezBadge:y};t.default=j},"./CTFd/themes/core/assets/js/fetch.js":function(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,i("./node_modules/whatwg-fetch/fetch.js");var o,n=(o=i("./CTFd/themes/core/assets/js/config.js"))&&o.__esModule?o:{default:o};var r=window.fetch;t.default=function(e,t){return void 0===t&&(t={method:"GET",credentials:"same-origin",headers:{}}),e=n.default.urlRoot+e,void 0===t.headers&&(t.headers={}),t.credentials="same-origin",t.headers.Accept="application/json",t.headers["Content-Type"]="application/json",t.headers["CSRF-Token"]=n.default.csrfNonce,r(e,t)}},"./CTFd/themes/core/assets/js/patch.js":function(e,t,i){var o,s=(o=i("./node_modules/q/q.js"))&&o.__esModule?o:{default:o},n=i("./CTFd/themes/core/assets/js/api.js");function a(t,e){var i,o=Object.keys(t);return Object.getOwnPropertySymbols&&(i=Object.getOwnPropertySymbols(t),e&&(i=i.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),o.push.apply(o,i)),o}function r(n){for(var e=1;e'),(0,a.default)("th.sort-col").click(function(){var n,e=(0,a.default)(this).parents("table").eq(0),t=e.find("tr:gt(0)").toArray().sort((n=(0,a.default)(this).index(),function(e,t){var i=r(e,n),o=r(t,n);return a.default.isNumeric(i)&&a.default.isNumeric(o)?i-o:i.toString().localeCompare(o)}));this.asc=!this.asc,this.asc||(t=t.reverse());for(var i=0;i").text(e).html()};var o,a=(o=i("./node_modules/jquery/dist/jquery.js"))&&o.__esModule?o:{default:o};function n(){this.id=Math.random(),this.isMaster=!1,this.others={},window.addEventListener("storage",this,!1),window.addEventListener("unload",this,!1),this.broadcast("hello");var t=this;this._checkTimeout=setTimeout(function e(){t.check(),t._checkTimeout=setTimeout(e,9e3)},500),this._pingTimeout=setTimeout(function e(){t.sendPing(),t._pingTimeout=setTimeout(e,17e3)},17e3)}a.default.fn.serializeJSON=function(i){var o={},n=(0,a.default)(this),e=n.serializeArray();return(e=(e=e.concat(n.find("input[type=checkbox]:checked").map(function(){return{name:this.name,value:!0}}).get())).concat(n.find("input[type=checkbox]:not(:checked)").map(function(){return{name:this.name,value:!1}}).get())).map(function(e){var t;i&&(null===e.value||""===e.value)&&(t=n.find(":input[name='".concat(e.name,"']"))).data("initial")===t.val()||(o[e.name]=e.value)}),o},String.prototype.format=String.prototype.f=function(){for(var e=this,t=arguments.length;t--;)e=e.replace(new RegExp("\\{"+t+"\\}","gm"),arguments[t]);return e},String.prototype.hashCode=function(){var e,t,i=0;if(0==this.length)return i;for(e=0,t=this.length;e
- + {% else %} + + {% endif %} +
@@ -109,7 +118,19 @@ {{ sub.type }} -
{{ sub.provided }}
+ + {% if request.args.get('full') %} +
{{ sub.provided }}
+ {% else %} +
{{ sub.provided | truncate(45, True) }}
+ {% if sub.provided | length > 50 %} + + {% endif %} + {% endif %} From eac44adf6948a6d4a12490efd79ad65c3b12ebbe Mon Sep 17 00:00:00 2001 From: Kevin Chung Date: Thu, 22 Jun 2023 00:20:32 -0400 Subject: [PATCH 2/4] Add a description field to api tokens and make api tokens start with a 'ctfd_' prefix (#2337) * Add a description field for API tokens * API tokens now start with a `ctfd_` prefix to make them easier to identify * Closes #2184 --- CTFd/api/v1/tokens.py | 5 +++- CTFd/forms/self.py | 3 ++- CTFd/models/__init__.py | 1 + CTFd/schemas/tokens.py | 12 ++++++++-- CTFd/themes/core/templates/settings.html | 7 +++++- CTFd/utils/security/auth.py | 8 ++++--- ..._add_description_column_to_tokens_table.py | 23 +++++++++++++++++++ 7 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 migrations/versions/9e6f6578ca84_add_description_column_to_tokens_table.py diff --git a/CTFd/api/v1/tokens.py b/CTFd/api/v1/tokens.py index 2c2011a4..975b8708 100644 --- a/CTFd/api/v1/tokens.py +++ b/CTFd/api/v1/tokens.py @@ -85,11 +85,14 @@ class TokenList(Resource): def post(self): req = request.get_json() expiration = req.get("expiration") + description = req.get("description") if expiration: expiration = datetime.datetime.strptime(expiration, "%Y-%m-%d") user = get_current_user() - token = generate_user_token(user, expiration=expiration) + token = generate_user_token( + user, expiration=expiration, description=description + ) # Explicitly use admin view so that user's can see the value of their token schema = TokenSchema(view="admin") diff --git a/CTFd/forms/self.py b/CTFd/forms/self.py index c527138b..d325bc5d 100644 --- a/CTFd/forms/self.py +++ b/CTFd/forms/self.py @@ -1,6 +1,6 @@ from flask import session from flask_babel import lazy_gettext as _l -from wtforms import PasswordField, SelectField, StringField +from wtforms import PasswordField, SelectField, StringField, TextAreaField from wtforms.fields.html5 import DateField, URLField from CTFd.constants.languages import SELECT_LANGUAGE_LIST @@ -50,4 +50,5 @@ def SettingsForm(*args, **kwargs): class TokensForm(BaseForm): expiration = DateField(_l("Expiration")) + description = TextAreaField("Usage Description") submit = SubmitField(_l("Generate")) diff --git a/CTFd/models/__init__.py b/CTFd/models/__init__.py index 45f9d21b..a09bbc66 100644 --- a/CTFd/models/__init__.py +++ b/CTFd/models/__init__.py @@ -916,6 +916,7 @@ class Tokens(db.Model): db.DateTime, default=lambda: datetime.datetime.utcnow() + datetime.timedelta(days=30), ) + description = db.Column(db.Text) value = db.Column(db.String(128), unique=True) user = db.relationship("Users", foreign_keys="Tokens.user_id", lazy="select") diff --git a/CTFd/schemas/tokens.py b/CTFd/schemas/tokens.py index d6ae4d36..094a01db 100644 --- a/CTFd/schemas/tokens.py +++ b/CTFd/schemas/tokens.py @@ -9,8 +9,16 @@ class TokenSchema(ma.ModelSchema): dump_only = ("id", "expiration", "type") views = { - "admin": ["id", "type", "user_id", "created", "expiration", "value"], - "user": ["id", "type", "created", "expiration"], + "admin": [ + "id", + "type", + "user_id", + "created", + "expiration", + "description", + "value", + ], + "user": ["id", "type", "created", "expiration", "description"], } def __init__(self, view=None, *args, **kwargs): diff --git a/CTFd/themes/core/templates/settings.html b/CTFd/themes/core/templates/settings.html index 74b29cc4..1095786e 100644 --- a/CTFd/themes/core/templates/settings.html +++ b/CTFd/themes/core/templates/settings.html @@ -81,7 +81,10 @@ {{ form.expiration.label }} {{ form.expiration(class="form-control") }}
- +
+ {{ form.description.label }} + {{ form.description(class="form-control", rows="3") }} +
{{ form.submit(class="btn btn-md btn-primary btn-outlined") }}
@@ -96,6 +99,7 @@ Created Expiration + Description Delete @@ -104,6 +108,7 @@ + {{ token.description | default('', true) }} diff --git a/CTFd/utils/security/auth.py b/CTFd/utils/security/auth.py index d098c4a3..6d62b5da 100644 --- a/CTFd/utils/security/auth.py +++ b/CTFd/utils/security/auth.py @@ -34,13 +34,15 @@ def logout_user(): session.clear() -def generate_user_token(user, expiration=None): +def generate_user_token(user, expiration=None, description=None): temp_token = True while temp_token is not None: - value = hexencode(os.urandom(32)) + value = "ctfd_" + hexencode(os.urandom(32)) temp_token = UserTokens.query.filter_by(value=value).first() - token = UserTokens(user_id=user.id, expiration=expiration, value=value) + token = UserTokens( + user_id=user.id, expiration=expiration, description=description, value=value + ) db.session.add(token) db.session.commit() return token diff --git a/migrations/versions/9e6f6578ca84_add_description_column_to_tokens_table.py b/migrations/versions/9e6f6578ca84_add_description_column_to_tokens_table.py new file mode 100644 index 00000000..da2dbedf --- /dev/null +++ b/migrations/versions/9e6f6578ca84_add_description_column_to_tokens_table.py @@ -0,0 +1,23 @@ +"""Add description column to tokens table + +Revision ID: 9e6f6578ca84 +Revises: 0def790057c1 +Create Date: 2023-06-21 23:22:34.179636 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "9e6f6578ca84" +down_revision = "0def790057c1" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column("tokens", sa.Column("description", sa.Text(), nullable=True)) + + +def downgrade(): + op.drop_column("tokens", "description") From 6e0072b9028bc7c878e3a74be66754fbbcb7d32c Mon Sep 17 00:00:00 2001 From: Kevin Chung Date: Thu, 22 Jun 2023 00:24:12 -0400 Subject: [PATCH 3/4] Squashed 'CTFd/themes/core-beta/' changes from 5ce3003b..bb4edfb6 bb4edfb6 Add description to TokensForm 50070166 Fix issue with missing endtrans tag 34c58129 Update README.md git-subtree-dir: CTFd/themes/core-beta git-subtree-split: bb4edfb6d4535406f7038099501d144d0cc998da --- README.md | 4 ++++ templates/settings.html | 9 +++++++++ templates/teams/public.html | 22 +++++++++++----------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 3fa13b9e..17c0d870 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ git subtree add --prefix CTFd/themes/core-beta git@github.com:CTFd/core-beta.git git subtree pull --prefix CTFd/themes/core-beta git@github.com:CTFd/core-beta.git main --squash ``` +### Subtree Gotcha + +Make sure to use Merge Commits when dealing with the subtree here. For some reason Github's squash and commit uses the wrong line ending which causes issues with the subtree script: https://stackoverflow.com/a/47190256. + ## Todo - Document how we are using Vite diff --git a/templates/settings.html b/templates/settings.html index d7ce93ba..3af06e0a 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -164,6 +164,11 @@ {{ form.expiration(class="form-control") }}
+
+ {{ form.description.label(class="form-label") }} + {{ form.description(class="form-control", rows="3") }} +
+
{{ form.submit(class="btn btn-block btn-primary float-end px-4") }} @@ -218,6 +223,7 @@ {% trans %}Created{% endtrans %} {% trans %}Expiration{% endtrans %} + {% trans %}Description{% endtrans %} {% trans %}Delete{% endtrans %} @@ -230,6 +236,9 @@ + + {{ token.description | default('', true) }} + - {% trans %}User Name{% trans %} + {% trans %}User Name{% endtrans %} {% trans %}Score{% endtrans %} @@ -169,15 +169,15 @@
-
-
@@ -198,9 +198,9 @@