From 22c132358ef15e89b93af84326962ab1c53cb029 Mon Sep 17 00:00:00 2001 From: Kevin Chung Date: Mon, 17 Feb 2020 02:17:25 -0500 Subject: [PATCH] 2.3.0 (#1248) 2.3.0 / 2020-02-17 ================== **General** * During setup, admins can register their email address with the CTFd LLC newsletter for news and updates * Fix editting hints from the admin panel * Allow admins to insert HTML code directly into the header and footer (end of body tag) of pages. This replaces and supercedes the custom CSS feature. * The `views.custom_css` route has been removed. * Admins can now customize the content of outgoing emails and inject certain variables into email content. * The `manage.py` script can now manipulate the CTFd Configs table via the `get_config` and `set_config` commands. (e.g. `python manage.py get_config ctf_theme` and `python manage.py set_config ctf_theme core`) **Themes** * Themes should now reference the `theme_header` and `theme_footer` configs instead of the `views.custom_css` endpoint to allow for user customizations. See the `base.html` file of the core theme. **Plugins** * Make `ezq` functions available to `CTFd.js` under `CTFd.ui.ezq` **Miscellaneous** * Python imports sorted with `isort` and import order enforced * Black formatter running on a majority of Python code --- CHANGELOG.md | 22 + CTFd/__init__.py | 26 +- CTFd/admin/__init__.py | 75 +- CTFd/admin/challenges.py | 15 +- CTFd/admin/notifications.py | 5 +- CTFd/admin/pages.py | 5 +- CTFd/admin/scoreboard.py | 3 +- CTFd/admin/statistics.py | 9 +- CTFd/admin/submissions.py | 7 +- CTFd/admin/teams.py | 10 +- CTFd/admin/users.py | 10 +- CTFd/api/__init__.py | 22 +- CTFd/api/v1/awards.py | 3 +- CTFd/api/v1/challenges.py | 55 +- CTFd/api/v1/config.py | 9 +- CTFd/api/v1/files.py | 3 +- CTFd/api/v1/flags.py | 5 +- CTFd/api/v1/hints.py | 7 +- CTFd/api/v1/notifications.py | 4 +- CTFd/api/v1/pages.py | 6 +- CTFd/api/v1/scoreboard.py | 8 +- CTFd/api/v1/statistics/__init__.py | 2 +- CTFd/api/v1/statistics/challenges.py | 9 +- CTFd/api/v1/statistics/submissions.py | 5 +- CTFd/api/v1/statistics/teams.py | 3 +- CTFd/api/v1/statistics/users.py | 7 +- CTFd/api/v1/submissions.py | 2 +- CTFd/api/v1/tags.py | 3 +- CTFd/api/v1/teams.py | 16 +- CTFd/api/v1/tokens.py | 10 +- CTFd/api/v1/unlocks.py | 11 +- CTFd/api/v1/users.py | 29 +- CTFd/auth.py | 48 +- CTFd/challenges.py | 15 +- CTFd/events/__init__.py | 3 +- CTFd/models/__init__.py | 12 +- CTFd/plugins/__init__.py | 17 +- CTFd/plugins/challenges/__init__.py | 19 +- CTFd/plugins/dynamic_challenges/__init__.py | 27 +- CTFd/plugins/flags/__init__.py | 4 +- CTFd/schemas/awards.py | 2 +- CTFd/schemas/challenges.py | 2 +- CTFd/schemas/config.py | 2 +- CTFd/schemas/files.py | 2 +- CTFd/schemas/flags.py | 2 +- CTFd/schemas/hints.py | 2 +- CTFd/schemas/notifications.py | 2 +- CTFd/schemas/pages.py | 3 +- CTFd/schemas/submissions.py | 3 +- CTFd/schemas/tags.py | 2 +- CTFd/schemas/teams.py | 12 +- CTFd/schemas/tokens.py | 2 +- CTFd/schemas/unlocks.py | 2 +- CTFd/schemas/users.py | 12 +- CTFd/scoreboard.py | 3 +- CTFd/teams.py | 15 +- .../admin/assets/js/challenges/hints.js | 4 +- CTFd/themes/admin/static/js/core.dev.js | 2 +- .../admin/static/js/pages/challenge.dev.js | 2 +- .../admin/static/js/pages/challenge.min.js | 2 +- .../admin/static/js/pages/configs.min.js | 2 +- .../admin/static/js/pages/editor.min.js | 2 +- CTFd/themes/admin/static/js/pages/main.min.js | 2 +- .../static/js/pages/notifications.min.js | 2 +- .../themes/admin/static/js/pages/pages.min.js | 2 +- .../themes/admin/static/js/pages/reset.min.js | 2 +- .../admin/static/js/pages/scoreboard.min.js | 2 +- .../admin/static/js/pages/statistics.min.js | 2 +- .../admin/static/js/pages/submissions.min.js | 2 +- CTFd/themes/admin/static/js/pages/team.min.js | 2 +- CTFd/themes/admin/static/js/pages/user.min.js | 2 +- .../themes/admin/static/js/pages/users.min.js | 2 +- .../admin/templates/configs/appearance.html | 18 +- .../themes/admin/templates/configs/email.html | 136 +- CTFd/themes/core/assets/js/CTFd.js | 5 + CTFd/themes/core/assets/js/pages/setup.js | 21 + CTFd/themes/core/static/js/core.dev.js | 2 +- .../core/static/js/pages/challenges.min.js | 2 +- CTFd/themes/core/static/js/pages/main.min.js | 2 +- .../core/static/js/pages/notifications.min.js | 2 +- .../core/static/js/pages/scoreboard.min.js | 2 +- .../core/static/js/pages/settings.min.js | 2 +- CTFd/themes/core/static/js/pages/setup.dev.js | 2 +- CTFd/themes/core/static/js/pages/setup.min.js | 2 +- CTFd/themes/core/static/js/pages/stats.min.js | 2 +- .../core/static/js/pages/teams/private.min.js | 2 +- CTFd/themes/core/templates/base.html | 4 +- CTFd/themes/core/templates/setup.html | 10 +- CTFd/users.py | 6 +- CTFd/utils/__init__.py | 3 +- CTFd/utils/config/__init__.py | 10 +- CTFd/utils/crypto/__init__.py | 6 +- CTFd/utils/dates/__init__.py | 4 +- CTFd/utils/decorators/__init__.py | 21 +- CTFd/utils/decorators/modes.py | 8 +- CTFd/utils/decorators/visibility.py | 8 +- CTFd/utils/email/__init__.py | 108 +- CTFd/utils/email/mailgun.py | 3 +- CTFd/utils/email/smtp.py | 5 +- CTFd/utils/encoding/__init__.py | 7 +- CTFd/utils/events/__init__.py | 8 +- CTFd/utils/exports/__init__.py | 46 +- CTFd/utils/helpers/__init__.py | 3 +- CTFd/utils/initialization/__init__.py | 65 +- CTFd/utils/logging/__init__.py | 6 +- CTFd/utils/migrations/__init__.py | 13 +- CTFd/utils/modes/__init__.py | 5 +- CTFd/utils/plugins/__init__.py | 7 +- CTFd/utils/scores/__init__.py | 4 +- CTFd/utils/security/auth.py | 12 +- CTFd/utils/security/csrf.py | 3 +- CTFd/utils/security/passwords.py | 4 +- CTFd/utils/security/signing.py | 4 +- CTFd/utils/sessions/__init__.py | 10 +- CTFd/utils/updates/__init__.py | 20 +- CTFd/utils/uploads/__init__.py | 6 +- CTFd/utils/uploads/uploaders.py | 16 +- CTFd/utils/user/__init__.py | 10 +- CTFd/utils/validators/__init__.py | 12 +- CTFd/views.py | 138 +- Makefile | 6 +- development.txt | 1 + docs/conf.py | 59 +- export.py | 4 +- manage.py | 2 +- migrations/1_2_0_upgrade_2_0_0.py | 348 +++-- migrations/env.py | 56 +- .../versions/080d29b15cd3_add_tokens_table.py | 30 +- ...093835a1051_add_default_email_templates.py | 70 + .../4e4d5a9ea000_add_type_to_awards.py | 16 +- .../versions/8369118943a1_initial_revision.py | 407 ++--- ...32_add_theme_code_injections_to_configs.py | 46 + ...4d_add_ondelete_cascade_to_foreign_keys.py | 338 ++-- ...5551cd26764_add_captain_column_to_teams.py | 46 +- package.json | 2 +- populate.py | 1372 ++++++++++++++--- serve.py | 17 +- tests/admin/test_challenges.py | 4 +- tests/admin/test_config.py | 19 +- tests/admin/test_export_csv.py | 2 +- tests/admin/test_users.py | 4 +- tests/admin/test_views.py | 9 +- tests/api/test_tokens.py | 10 +- tests/api/v1/teams/test_scoring.py | 8 +- tests/api/v1/teams/test_team_members.py | 6 +- tests/api/v1/test_awards.py | 4 +- tests/api/v1/test_challenges.py | 21 +- tests/api/v1/test_csrf.py | 1 + tests/api/v1/test_files.py | 9 +- tests/api/v1/test_flags.py | 2 +- tests/api/v1/test_hints.py | 4 +- tests/api/v1/test_notifications.py | 4 +- tests/api/v1/test_pages.py | 2 +- tests/api/v1/test_scoreboard.py | 4 +- tests/api/v1/test_submissions.py | 2 +- tests/api/v1/test_tags.py | 2 +- tests/api/v1/test_teams.py | 21 +- tests/api/v1/test_tokens.py | 14 +- tests/api/v1/test_users.py | 28 +- tests/api/v1/user/test_admin_access.py | 2 +- tests/api/v1/user/test_challenges.py | 7 +- tests/api/v1/user/test_hints.py | 9 +- tests/api/v1/users/test_scoring.py | 4 +- tests/challenges/test_dynamic.py | 6 +- tests/helpers.py | 34 +- tests/oauth/test_redirect.py | 6 +- tests/oauth/test_teams.py | 2 +- tests/teams/test_auth.py | 8 +- tests/teams/test_challenges.py | 4 +- tests/teams/test_hidden_team_scores.py | 9 +- tests/teams/test_hints.py | 8 +- tests/teams/test_scoreboard.py | 4 +- tests/teams/test_teams.py | 10 +- tests/test_config.py | 2 +- tests/test_plugin_utils.py | 18 +- tests/test_setup.py | 9 +- tests/test_themes.py | 25 +- tests/test_views.py | 21 +- tests/users/test_auth.py | 15 +- tests/users/test_challenges.py | 9 +- tests/users/test_hints.py | 11 +- tests/users/test_profile.py | 2 +- tests/users/test_scoreboard.py | 11 +- tests/users/test_settings.py | 2 +- tests/users/test_submissions.py | 2 +- tests/users/test_users.py | 4 +- tests/utils/__init__.py | 2 +- tests/utils/test_ctftime.py | 13 +- tests/utils/test_email.py | 53 +- tests/utils/test_encoding.py | 4 +- tests/utils/test_events.py | 16 +- tests/utils/test_exports.py | 29 +- tests/utils/test_passwords.py | 2 +- tests/utils/test_plugins.py | 4 +- tests/utils/test_updates.py | 7 +- tests/utils/test_uploaders.py | 8 +- tests/utils/test_validators.py | 3 +- 197 files changed, 3253 insertions(+), 1580 deletions(-) create mode 100644 migrations/versions/1093835a1051_add_default_email_templates.py create mode 100644 migrations/versions/a03403986a32_add_theme_code_injections_to_configs.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f23f092..cf7cdc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +2.3.0 / 2020-02-17 +================== + +**General** +* During setup, admins can register their email address with the CTFd LLC newsletter for news and updates +* Fix editting hints from the admin panel +* Allow admins to insert HTML code directly into the header and footer (end of body tag) of pages. This replaces and supercedes the custom CSS feature. + * The `views.custom_css` route has been removed. +* Admins can now customize the content of outgoing emails and inject certain variables into email content. +* The `manage.py` script can now manipulate the CTFd Configs table via the `get_config` and `set_config` commands. (e.g. `python manage.py get_config ctf_theme` and `python manage.py set_config ctf_theme core`) + +**Themes** +* Themes should now reference the `theme_header` and `theme_footer` configs instead of the `views.custom_css` endpoint to allow for user customizations. See the `base.html` file of the core theme. + +**Plugins** +* Make `ezq` functions available to `CTFd.js` under `CTFd.ui.ezq` + +**Miscellaneous** +* Python imports sorted with `isort` and import order enforced +* Black formatter running on a majority of Python code + + 2.2.3 / 2020-01-21 ================== diff --git a/CTFd/__init__.py b/CTFd/__init__.py index 2e4b8ab..469323a 100644 --- a/CTFd/__init__.py +++ b/CTFd/__init__.py @@ -1,36 +1,36 @@ -import sys +import datetime import os - +import sys from distutils.version import StrictVersion + from flask import Flask, Request from flask_migrate import upgrade -from werkzeug.utils import cached_property -from werkzeug.middleware.proxy_fix import ProxyFix from jinja2 import FileSystemLoader from jinja2.sandbox import SandboxedEnvironment from six.moves import input +from werkzeug.middleware.proxy_fix import ProxyFix +from werkzeug.utils import cached_property from CTFd import utils -from CTFd.utils.migrations import migrations, create_database, stamp_latest_revision -from CTFd.utils.sessions import CachingSessionInterface -from CTFd.utils.updates import update_check +from CTFd.plugins import init_plugins +from CTFd.utils.crypto import sha256 from CTFd.utils.initialization import ( + init_events, + init_logs, init_request_processors, init_template_filters, init_template_globals, - init_logs, - init_events, ) -from CTFd.utils.crypto import sha256 -from CTFd.plugins import init_plugins -import datetime +from CTFd.utils.migrations import create_database, migrations, stamp_latest_revision +from CTFd.utils.sessions import CachingSessionInterface +from CTFd.utils.updates import update_check # Hack to support Unicode in Python 2 properly if sys.version_info[0] < 3: reload(sys) # noqa: F821 sys.setdefaultencoding("utf-8") -__version__ = "2.2.3" +__version__ = "2.3.0" class CTFdRequest(Request): diff --git a/CTFd/admin/__init__.py b/CTFd/admin/__init__.py index 89e64be..4216614 100644 --- a/CTFd/admin/__init__.py +++ b/CTFd/admin/__init__.py @@ -1,54 +1,51 @@ -from flask import ( - current_app as app, - render_template, - request, - redirect, - url_for, - Blueprint, - abort, - render_template_string, - send_file, -) - -from CTFd.utils.decorators import admins_only -from CTFd.utils.user import is_admin -from CTFd.utils.security.auth import logout_user -from CTFd.utils import config as ctf_config, get_config, set_config -from CTFd.cache import cache, clear_config -from CTFd.utils.helpers import get_errors -from CTFd.utils.exports import ( - export_ctf as export_ctf_util, - import_ctf as import_ctf_util, -) -from CTFd.models import ( - db, - get_class_by_tablename, - Users, - Teams, - Configs, - Submissions, - Solves, - Awards, - Unlocks, - Tracking, -) +import csv import datetime import os -import six -import csv +import six +from flask import Blueprint, abort +from flask import current_app as app +from flask import ( + redirect, + render_template, + render_template_string, + request, + send_file, + url_for, +) + +from CTFd.cache import cache, clear_config +from CTFd.models import ( + Awards, + Configs, + Solves, + Submissions, + Teams, + Tracking, + Unlocks, + Users, + db, + get_class_by_tablename, +) +from CTFd.utils import config as ctf_config +from CTFd.utils import get_config, set_config +from CTFd.utils.decorators import admins_only +from CTFd.utils.exports import export_ctf as export_ctf_util +from CTFd.utils.exports import import_ctf as import_ctf_util +from CTFd.utils.helpers import get_errors +from CTFd.utils.security.auth import logout_user +from CTFd.utils.user import is_admin admin = Blueprint("admin", __name__) - from CTFd.admin import challenges # noqa: F401 +from CTFd.admin import notifications # noqa: F401 from CTFd.admin import pages # noqa: F401 from CTFd.admin import scoreboard # noqa: F401 from CTFd.admin import statistics # noqa: F401 +from CTFd.admin import submissions # noqa: F401 from CTFd.admin import teams # noqa: F401 from CTFd.admin import users # noqa: F401 -from CTFd.admin import submissions # noqa: F401 -from CTFd.admin import notifications # noqa: F401 @admin.route("/admin", methods=["GET"]) diff --git a/CTFd/admin/challenges.py b/CTFd/admin/challenges.py index e00d4b6..b391b67 100644 --- a/CTFd/admin/challenges.py +++ b/CTFd/admin/challenges.py @@ -1,11 +1,14 @@ -from flask import current_app as app, render_template, render_template_string, url_for -from CTFd.utils.decorators import admins_only -from CTFd.utils import binary_type -from CTFd.models import Solves, Challenges, Flags -from CTFd.plugins.challenges import get_chal_class -from CTFd.admin import admin import os + import six +from flask import current_app as app +from flask import render_template, render_template_string, url_for + +from CTFd.admin import admin +from CTFd.models import Challenges, Flags, Solves +from CTFd.plugins.challenges import get_chal_class +from CTFd.utils import binary_type +from CTFd.utils.decorators import admins_only @admin.route("/admin/challenges") diff --git a/CTFd/admin/notifications.py b/CTFd/admin/notifications.py index bf2fa25..273416e 100644 --- a/CTFd/admin/notifications.py +++ b/CTFd/admin/notifications.py @@ -1,7 +1,8 @@ from flask import render_template -from CTFd.utils.decorators import admins_only -from CTFd.models import Notifications + from CTFd.admin import admin +from CTFd.models import Notifications +from CTFd.utils.decorators import admins_only @admin.route("/admin/notifications") diff --git a/CTFd/admin/pages.py b/CTFd/admin/pages.py index 7f7bb03..0540b76 100644 --- a/CTFd/admin/pages.py +++ b/CTFd/admin/pages.py @@ -1,9 +1,10 @@ from flask import render_template, request -from CTFd.utils.decorators import admins_only + +from CTFd.admin import admin from CTFd.models import Pages from CTFd.schemas.pages import PageSchema from CTFd.utils import markdown -from CTFd.admin import admin +from CTFd.utils.decorators import admins_only @admin.route("/admin/pages") diff --git a/CTFd/admin/scoreboard.py b/CTFd/admin/scoreboard.py index 71e2cb8..b1c7374 100644 --- a/CTFd/admin/scoreboard.py +++ b/CTFd/admin/scoreboard.py @@ -1,7 +1,8 @@ from flask import render_template -from CTFd.utils.decorators import admins_only + from CTFd.admin import admin from CTFd.scoreboard import get_standings +from CTFd.utils.decorators import admins_only @admin.route("/admin/scoreboard") diff --git a/CTFd/admin/statistics.py b/CTFd/admin/statistics.py index 831172a..6c54a21 100644 --- a/CTFd/admin/statistics.py +++ b/CTFd/admin/statistics.py @@ -1,9 +1,10 @@ from flask import render_template -from CTFd.utils.decorators import admins_only -from CTFd.utils.updates import update_check -from CTFd.utils.modes import get_model -from CTFd.models import db, Solves, Challenges, Fails, Tracking, Teams, Users + from CTFd.admin import admin +from CTFd.models import Challenges, Fails, Solves, Teams, Tracking, Users, db +from CTFd.utils.decorators import admins_only +from CTFd.utils.modes import get_model +from CTFd.utils.updates import update_check @admin.route("/admin/statistics", methods=["GET"]) diff --git a/CTFd/admin/submissions.py b/CTFd/admin/submissions.py index 6b9e862..f409a81 100644 --- a/CTFd/admin/submissions.py +++ b/CTFd/admin/submissions.py @@ -1,8 +1,9 @@ from flask import render_template, request -from CTFd.utils.decorators import admins_only -from CTFd.models import Challenges, Submissions -from CTFd.utils.modes import get_model + from CTFd.admin import admin +from CTFd.models import Challenges, Submissions +from CTFd.utils.decorators import admins_only +from CTFd.utils.modes import get_model @admin.route("/admin/submissions", defaults={"submission_type": None}) diff --git a/CTFd/admin/teams.py b/CTFd/admin/teams.py index 1120c28..a8fb505 100644 --- a/CTFd/admin/teams.py +++ b/CTFd/admin/teams.py @@ -1,11 +1,11 @@ from flask import render_template, request -from CTFd.utils.decorators import admins_only -from CTFd.models import db, Teams, Challenges, Tracking -from CTFd.admin import admin -from CTFd.utils.helpers import get_errors - from sqlalchemy.sql import not_ +from CTFd.admin import admin +from CTFd.models import Challenges, Teams, Tracking, db +from CTFd.utils.decorators import admins_only +from CTFd.utils.helpers import get_errors + @admin.route("/admin/teams") @admins_only diff --git a/CTFd/admin/users.py b/CTFd/admin/users.py index 9c6e219..758dc95 100644 --- a/CTFd/admin/users.py +++ b/CTFd/admin/users.py @@ -1,12 +1,12 @@ from flask import render_template, request +from sqlalchemy.sql import not_ + +from CTFd.admin import admin +from CTFd.models import Challenges, Tracking, Users, db from CTFd.utils import get_config from CTFd.utils.decorators import admins_only -from CTFd.utils.modes import TEAMS_MODE -from CTFd.models import db, Users, Challenges, Tracking -from CTFd.admin import admin from CTFd.utils.helpers import get_errors - -from sqlalchemy.sql import not_ +from CTFd.utils.modes import TEAMS_MODE @admin.route("/admin/users") diff --git a/CTFd/api/__init__.py b/CTFd/api/__init__.py index 17256e3..efe51c3 100644 --- a/CTFd/api/__init__.py +++ b/CTFd/api/__init__.py @@ -1,22 +1,22 @@ from flask import Blueprint, current_app from flask_restplus import Api -from CTFd.api.v1.challenges import challenges_namespace -from CTFd.api.v1.teams import teams_namespace -from CTFd.api.v1.users import users_namespace +from CTFd.api.v1.awards import awards_namespace +from CTFd.api.v1.challenges import challenges_namespace +from CTFd.api.v1.config import configs_namespace +from CTFd.api.v1.files import files_namespace +from CTFd.api.v1.flags import flags_namespace +from CTFd.api.v1.hints import hints_namespace +from CTFd.api.v1.notifications import notifications_namespace +from CTFd.api.v1.pages import pages_namespace from CTFd.api.v1.scoreboard import scoreboard_namespace from CTFd.api.v1.statistics import statistics_namespace from CTFd.api.v1.submissions import submissions_namespace from CTFd.api.v1.tags import tags_namespace -from CTFd.api.v1.awards import awards_namespace -from CTFd.api.v1.hints import hints_namespace -from CTFd.api.v1.flags import flags_namespace -from CTFd.api.v1.files import files_namespace -from CTFd.api.v1.config import configs_namespace -from CTFd.api.v1.notifications import notifications_namespace -from CTFd.api.v1.pages import pages_namespace -from CTFd.api.v1.unlocks import unlocks_namespace +from CTFd.api.v1.teams import teams_namespace from CTFd.api.v1.tokens import tokens_namespace +from CTFd.api.v1.unlocks import unlocks_namespace +from CTFd.api.v1.users import users_namespace api = Blueprint("api", __name__, url_prefix="/api/v1") CTFd_API_v1 = Api(api, version="v1", doc=current_app.config.get("SWAGGER_UI")) diff --git a/CTFd/api/v1/awards.py b/CTFd/api/v1/awards.py index d0e0c2a..6591c24 100644 --- a/CTFd/api/v1/awards.py +++ b/CTFd/api/v1/awards.py @@ -1,7 +1,8 @@ from flask import request from flask_restplus import Namespace, Resource + from CTFd.cache import clear_standings -from CTFd.models import db, Awards +from CTFd.models import Awards, db from CTFd.schemas.awards import AwardSchema from CTFd.utils.decorators import admins_only diff --git a/CTFd/api/v1/challenges.py b/CTFd/api/v1/challenges.py index 5e7d1e2..a8f1fc9 100644 --- a/CTFd/api/v1/challenges.py +++ b/CTFd/api/v1/challenges.py @@ -1,48 +1,37 @@ -from flask import request, abort, url_for +import datetime + +from flask import abort, request, url_for from flask_restplus import Namespace, Resource -from CTFd.models import ( - db, - Challenges, - HintUnlocks, - Tags, - Hints, - Flags, - Solves, - Fails, - ChallengeFiles as ChallengeFilesModel, +from sqlalchemy.sql import and_ + +from CTFd.cache import clear_standings +from CTFd.models import ChallengeFiles as ChallengeFilesModel +from CTFd.models import Challenges, Fails, Flags, Hints, HintUnlocks, Solves, Tags, db +from CTFd.plugins.challenges import CHALLENGE_CLASSES, get_chal_class +from CTFd.schemas.flags import FlagSchema +from CTFd.schemas.hints import HintSchema +from CTFd.schemas.tags import TagSchema +from CTFd.utils import config, get_config +from CTFd.utils import user as current_user +from CTFd.utils.config.visibility import ( + accounts_visible, + challenges_visible, + scores_visible, ) -from CTFd.plugins.challenges import CHALLENGE_CLASSES -from CTFd.utils.dates import isoformat +from CTFd.utils.dates import ctf_ended, ctf_paused, ctftime, isoformat, unix_time_to_utc from CTFd.utils.decorators import ( + admins_only, during_ctf_time_only, require_verified_emails, - admins_only, ) from CTFd.utils.decorators.visibility import ( check_challenge_visibility, check_score_visibility, ) -from CTFd.cache import clear_standings -from CTFd.utils.config.visibility import ( - scores_visible, - accounts_visible, - challenges_visible, -) -from CTFd.utils.user import is_admin, authed -from CTFd.utils.modes import get_model, generate_account_url -from CTFd.schemas.tags import TagSchema -from CTFd.schemas.hints import HintSchema -from CTFd.schemas.flags import FlagSchema -from CTFd.utils import config, get_config -from CTFd.utils import user as current_user -from CTFd.utils.user import get_current_team -from CTFd.utils.user import get_current_user -from CTFd.plugins.challenges import get_chal_class -from CTFd.utils.dates import ctf_ended, ctf_paused, ctftime, unix_time_to_utc from CTFd.utils.logging import log +from CTFd.utils.modes import generate_account_url, get_model from CTFd.utils.security.signing import serialize -from sqlalchemy.sql import and_ -import datetime +from CTFd.utils.user import authed, get_current_team, get_current_user, is_admin challenges_namespace = Namespace( "challenges", description="Endpoint to retrieve Challenges" diff --git a/CTFd/api/v1/config.py b/CTFd/api/v1/config.py index 51a1f57..5b9be70 100644 --- a/CTFd/api/v1/config.py +++ b/CTFd/api/v1/config.py @@ -1,10 +1,11 @@ from flask import request from flask_restplus import Namespace, Resource -from CTFd.models import db, Configs -from CTFd.schemas.config import ConfigSchema -from CTFd.utils.decorators import admins_only -from CTFd.utils import get_config, set_config + from CTFd.cache import clear_config, clear_standings +from CTFd.models import Configs, db +from CTFd.schemas.config import ConfigSchema +from CTFd.utils import get_config, set_config +from CTFd.utils.decorators import admins_only configs_namespace = Namespace("configs", description="Endpoint to retrieve Configs") diff --git a/CTFd/api/v1/files.py b/CTFd/api/v1/files.py index ef50024..731f267 100644 --- a/CTFd/api/v1/files.py +++ b/CTFd/api/v1/files.py @@ -1,6 +1,7 @@ from flask import request from flask_restplus import Namespace, Resource -from CTFd.models import db, Files + +from CTFd.models import Files, db from CTFd.schemas.files import FileSchema from CTFd.utils import uploads from CTFd.utils.decorators import admins_only diff --git a/CTFd/api/v1/flags.py b/CTFd/api/v1/flags.py index c7977af..0819a2e 100644 --- a/CTFd/api/v1/flags.py +++ b/CTFd/api/v1/flags.py @@ -1,8 +1,9 @@ from flask import request from flask_restplus import Namespace, Resource -from CTFd.models import db, Flags + +from CTFd.models import Flags, db +from CTFd.plugins.flags import FLAG_CLASSES, get_flag_class from CTFd.schemas.flags import FlagSchema -from CTFd.plugins.flags import get_flag_class, FLAG_CLASSES from CTFd.utils.decorators import admins_only flags_namespace = Namespace("flags", description="Endpoint to retrieve Flags") diff --git a/CTFd/api/v1/hints.py b/CTFd/api/v1/hints.py index d7abf2c..3bc62e2 100644 --- a/CTFd/api/v1/hints.py +++ b/CTFd/api/v1/hints.py @@ -1,9 +1,10 @@ from flask import request from flask_restplus import Namespace, Resource -from CTFd.models import db, Hints, HintUnlocks -from CTFd.utils.user import get_current_user, is_admin + +from CTFd.models import Hints, HintUnlocks, db from CTFd.schemas.hints import HintSchema -from CTFd.utils.decorators import during_ctf_time_only, admins_only, authed_only +from CTFd.utils.decorators import admins_only, authed_only, during_ctf_time_only +from CTFd.utils.user import get_current_user, is_admin hints_namespace = Namespace("hints", description="Endpoint to retrieve Hints") diff --git a/CTFd/api/v1/notifications.py b/CTFd/api/v1/notifications.py index f3dffc1..8dbb94b 100644 --- a/CTFd/api/v1/notifications.py +++ b/CTFd/api/v1/notifications.py @@ -1,8 +1,8 @@ from flask import current_app, request from flask_restplus import Namespace, Resource -from CTFd.models import db, Notifications -from CTFd.schemas.notifications import NotificationSchema +from CTFd.models import Notifications, db +from CTFd.schemas.notifications import NotificationSchema from CTFd.utils.decorators import admins_only notifications_namespace = Namespace( diff --git a/CTFd/api/v1/pages.py b/CTFd/api/v1/pages.py index 5d6f53d..dedec07 100644 --- a/CTFd/api/v1/pages.py +++ b/CTFd/api/v1/pages.py @@ -1,9 +1,9 @@ from flask import request from flask_restplus import Namespace, Resource -from CTFd.models import db, Pages -from CTFd.schemas.pages import PageSchema -from CTFd.cache import clear_pages +from CTFd.cache import clear_pages +from CTFd.models import Pages, db +from CTFd.schemas.pages import PageSchema from CTFd.utils.decorators import admins_only pages_namespace = Namespace("pages", description="Endpoint to retrieve Pages") diff --git a/CTFd/api/v1/scoreboard.py b/CTFd/api/v1/scoreboard.py index d87c49d..3fb6f52 100644 --- a/CTFd/api/v1/scoreboard.py +++ b/CTFd/api/v1/scoreboard.py @@ -1,15 +1,15 @@ from flask_restplus import Namespace, Resource -from CTFd.models import Solves, Awards, Teams from CTFd.cache import cache, make_cache_key -from CTFd.utils.scores import get_standings +from CTFd.models import Awards, Solves, Teams from CTFd.utils import get_config -from CTFd.utils.modes import generate_account_url, get_mode_as_word, TEAMS_MODE -from CTFd.utils.dates import unix_time_to_utc, isoformat +from CTFd.utils.dates import isoformat, unix_time_to_utc from CTFd.utils.decorators.visibility import ( check_account_visibility, check_score_visibility, ) +from CTFd.utils.modes import TEAMS_MODE, generate_account_url, get_mode_as_word +from CTFd.utils.scores import get_standings scoreboard_namespace = Namespace( "scoreboard", description="Endpoint to retrieve scores" diff --git a/CTFd/api/v1/statistics/__init__.py b/CTFd/api/v1/statistics/__init__.py index 2c0dc59..935218c 100644 --- a/CTFd/api/v1/statistics/__init__.py +++ b/CTFd/api/v1/statistics/__init__.py @@ -5,6 +5,6 @@ statistics_namespace = Namespace( ) from CTFd.api.v1.statistics import challenges # noqa: F401 +from CTFd.api.v1.statistics import submissions # noqa: F401 from CTFd.api.v1.statistics import teams # noqa: F401 from CTFd.api.v1.statistics import users # noqa: F401 -from CTFd.api.v1.statistics import submissions # noqa: F401 diff --git a/CTFd/api/v1/statistics/challenges.py b/CTFd/api/v1/statistics/challenges.py index b121992..8b6ac64 100644 --- a/CTFd/api/v1/statistics/challenges.py +++ b/CTFd/api/v1/statistics/challenges.py @@ -1,11 +1,12 @@ from flask_restplus import Resource -from CTFd.models import db, Challenges, Solves -from CTFd.utils.modes import get_model -from CTFd.utils.decorators import admins_only -from CTFd.api.v1.statistics import statistics_namespace from sqlalchemy import func from sqlalchemy.sql import or_ +from CTFd.api.v1.statistics import statistics_namespace +from CTFd.models import Challenges, Solves, db +from CTFd.utils.decorators import admins_only +from CTFd.utils.modes import get_model + @statistics_namespace.route("/challenges/") class ChallengePropertyCounts(Resource): diff --git a/CTFd/api/v1/statistics/submissions.py b/CTFd/api/v1/statistics/submissions.py index c088e05..97f76ea 100644 --- a/CTFd/api/v1/statistics/submissions.py +++ b/CTFd/api/v1/statistics/submissions.py @@ -1,8 +1,9 @@ from flask_restplus import Resource +from sqlalchemy import func + +from CTFd.api.v1.statistics import statistics_namespace from CTFd.models import Submissions from CTFd.utils.decorators import admins_only -from CTFd.api.v1.statistics import statistics_namespace -from sqlalchemy import func @statistics_namespace.route("/submissions/") diff --git a/CTFd/api/v1/statistics/teams.py b/CTFd/api/v1/statistics/teams.py index 3611102..8aa3278 100644 --- a/CTFd/api/v1/statistics/teams.py +++ b/CTFd/api/v1/statistics/teams.py @@ -1,7 +1,8 @@ from flask_restplus import Resource + +from CTFd.api.v1.statistics import statistics_namespace from CTFd.models import Teams from CTFd.utils.decorators import admins_only -from CTFd.api.v1.statistics import statistics_namespace @statistics_namespace.route("/teams") diff --git a/CTFd/api/v1/statistics/users.py b/CTFd/api/v1/statistics/users.py index fcc3567..86902c9 100644 --- a/CTFd/api/v1/statistics/users.py +++ b/CTFd/api/v1/statistics/users.py @@ -1,9 +1,10 @@ from flask_restplus import Resource -from CTFd.models import Users -from CTFd.api.v1.statistics import statistics_namespace -from CTFd.utils.decorators import admins_only from sqlalchemy import func +from CTFd.api.v1.statistics import statistics_namespace +from CTFd.models import Users +from CTFd.utils.decorators import admins_only + @statistics_namespace.route("/users") class UserStatistics(Resource): diff --git a/CTFd/api/v1/submissions.py b/CTFd/api/v1/submissions.py index 55ddbdb..a88ac39 100644 --- a/CTFd/api/v1/submissions.py +++ b/CTFd/api/v1/submissions.py @@ -2,7 +2,7 @@ from flask import request from flask_restplus import Namespace, Resource from CTFd.cache import clear_standings -from CTFd.models import db, Submissions +from CTFd.models import Submissions, db from CTFd.schemas.submissions import SubmissionSchema from CTFd.utils.decorators import admins_only diff --git a/CTFd/api/v1/tags.py b/CTFd/api/v1/tags.py index e5ef8cf..4693921 100644 --- a/CTFd/api/v1/tags.py +++ b/CTFd/api/v1/tags.py @@ -1,6 +1,7 @@ from flask import request from flask_restplus import Namespace, Resource -from CTFd.models import db, Tags + +from CTFd.models import Tags, db from CTFd.schemas.tags import TagSchema from CTFd.utils.decorators import admins_only diff --git a/CTFd/api/v1/teams.py b/CTFd/api/v1/teams.py index 6e579dc..1213c6d 100644 --- a/CTFd/api/v1/teams.py +++ b/CTFd/api/v1/teams.py @@ -1,17 +1,19 @@ -from flask import session, request, abort +import copy + +from flask import abort, request, session from flask_restplus import Namespace, Resource -from CTFd.models import db, Users, Teams, Submissions, Awards, Unlocks -from CTFd.schemas.teams import TeamSchema -from CTFd.schemas.submissions import SubmissionSchema -from CTFd.schemas.awards import AwardSchema + from CTFd.cache import clear_standings +from CTFd.models import Awards, Submissions, Teams, Unlocks, Users, db +from CTFd.schemas.awards import AwardSchema +from CTFd.schemas.submissions import SubmissionSchema +from CTFd.schemas.teams import TeamSchema +from CTFd.utils.decorators import admins_only, authed_only, require_team from CTFd.utils.decorators.visibility import ( check_account_visibility, check_score_visibility, ) from CTFd.utils.user import get_current_team, is_admin -from CTFd.utils.decorators import authed_only, admins_only, require_team -import copy teams_namespace = Namespace("teams", description="Endpoint to retrieve Teams") diff --git a/CTFd/api/v1/tokens.py b/CTFd/api/v1/tokens.py index 8e2b4b5..a91f23e 100644 --- a/CTFd/api/v1/tokens.py +++ b/CTFd/api/v1/tokens.py @@ -1,11 +1,13 @@ +import datetime + from flask import request, session from flask_restplus import Namespace, Resource -from CTFd.models import db, Tokens -from CTFd.utils.user import get_current_user, is_admin + +from CTFd.models import Tokens, db from CTFd.schemas.tokens import TokenSchema +from CTFd.utils.decorators import authed_only, require_verified_emails from CTFd.utils.security.auth import generate_user_token -from CTFd.utils.decorators import require_verified_emails, authed_only -import datetime +from CTFd.utils.user import get_current_user, is_admin tokens_namespace = Namespace("tokens", description="Endpoint to retrieve Tokens") diff --git a/CTFd/api/v1/unlocks.py b/CTFd/api/v1/unlocks.py index 228e6a4..33aae35 100644 --- a/CTFd/api/v1/unlocks.py +++ b/CTFd/api/v1/unlocks.py @@ -1,16 +1,17 @@ from flask import request from flask_restplus import Namespace, Resource + from CTFd.cache import clear_standings -from CTFd.models import db, get_class_by_tablename, Unlocks -from CTFd.utils.user import get_current_user -from CTFd.schemas.unlocks import UnlockSchema +from CTFd.models import Unlocks, db, get_class_by_tablename from CTFd.schemas.awards import AwardSchema +from CTFd.schemas.unlocks import UnlockSchema from CTFd.utils.decorators import ( - during_ctf_time_only, - require_verified_emails, admins_only, authed_only, + during_ctf_time_only, + require_verified_emails, ) +from CTFd.utils.user import get_current_user unlocks_namespace = Namespace("unlocks", description="Endpoint to retrieve Unlocks") diff --git a/CTFd/api/v1/users.py b/CTFd/api/v1/users.py index 70861a0..71436ba 100644 --- a/CTFd/api/v1/users.py +++ b/CTFd/api/v1/users.py @@ -1,29 +1,28 @@ -from flask import session, request, abort +from flask import abort, request, session from flask_restplus import Namespace, Resource + +from CTFd.cache import clear_standings from CTFd.models import ( - db, - Users, - Solves, Awards, + Notifications, + Solves, + Submissions, Tracking, Unlocks, - Submissions, - Notifications, + Users, + db, ) -from CTFd.utils.decorators import authed_only, admins_only, ratelimit -from CTFd.cache import clear_standings +from CTFd.schemas.awards import AwardSchema +from CTFd.schemas.submissions import SubmissionSchema +from CTFd.schemas.users import UserSchema from CTFd.utils.config import get_mail_provider -from CTFd.utils.email import sendmail, user_created_notification -from CTFd.utils.user import get_current_user, is_admin +from CTFd.utils.decorators import admins_only, authed_only, ratelimit from CTFd.utils.decorators.visibility import ( check_account_visibility, check_score_visibility, ) - -from CTFd.schemas.submissions import SubmissionSchema -from CTFd.schemas.awards import AwardSchema -from CTFd.schemas.users import UserSchema - +from CTFd.utils.email import sendmail, user_created_notification +from CTFd.utils.user import get_current_user, is_admin users_namespace = Namespace("users", description="Endpoint to retrieve Users") diff --git a/CTFd/auth.py b/CTFd/auth.py index 7829ff1..34a9d16 100644 --- a/CTFd/auth.py +++ b/CTFd/auth.py @@ -1,33 +1,25 @@ -from flask import ( - current_app as app, - render_template, - request, - redirect, - url_for, - session, - Blueprint, -) -from itsdangerous.exc import BadTimeSignature, SignatureExpired, BadSignature +import base64 -from CTFd.models import db, Users, Teams +import requests +from flask import Blueprint +from flask import current_app as app +from flask import redirect, render_template, request, session, url_for +from itsdangerous.exc import BadSignature, BadTimeSignature, SignatureExpired -from CTFd.utils import get_config, get_app_config -from CTFd.utils.decorators import ratelimit +from CTFd.models import Teams, Users, db +from CTFd.utils import config, email, get_app_config, get_config from CTFd.utils import user as current_user -from CTFd.utils import config, validators -from CTFd.utils import email -from CTFd.utils.security.auth import login_user, logout_user -from CTFd.utils.crypto import verify_password -from CTFd.utils.logging import log -from CTFd.utils.decorators.visibility import check_registration_visibility +from CTFd.utils import validators from CTFd.utils.config import is_teams_mode from CTFd.utils.config.visibility import registration_visible -from CTFd.utils.modes import TEAMS_MODE -from CTFd.utils.security.signing import unserialize +from CTFd.utils.crypto import verify_password +from CTFd.utils.decorators import ratelimit +from CTFd.utils.decorators.visibility import check_registration_visibility from CTFd.utils.helpers import error_for, get_errors - -import base64 -import requests +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 auth = Blueprint("auth", __name__) @@ -61,6 +53,7 @@ def confirm(data=None): name=user.name, ) db.session.commit() + email.successful_registration_notification(user.email) db.session.close() if current_user.authed(): return redirect(url_for("challenges.listing")) @@ -251,12 +244,7 @@ def register(): if ( config.can_send_mail() ): # We want to notify the user that they have registered. - email.sendmail( - request.form["email"], - "You've successfully registered for {}".format( - get_config("ctf_name") - ), - ) + email.successful_registration_notification(user.email) log("registrations", "[{date}] {ip} - {name} registered with {email}") db.session.close() diff --git a/CTFd/challenges.py b/CTFd/challenges.py index 2525240..6519291 100644 --- a/CTFd/challenges.py +++ b/CTFd/challenges.py @@ -1,12 +1,13 @@ -from flask import render_template, Blueprint -from CTFd.utils.decorators import ( - during_ctf_time_only, - require_verified_emails, - require_team, -) -from CTFd.utils.decorators.visibility import check_challenge_visibility +from flask import Blueprint, render_template + from CTFd.utils import config, get_config from CTFd.utils.dates import ctf_ended, ctf_paused, view_after_ctf +from CTFd.utils.decorators import ( + during_ctf_time_only, + require_team, + require_verified_emails, +) +from CTFd.utils.decorators.visibility import check_challenge_visibility from CTFd.utils.helpers import get_errors, get_infos challenges = Blueprint("challenges", __name__) diff --git a/CTFd/events/__init__.py b/CTFd/events/__init__.py index 09a2262..fcfac6d 100644 --- a/CTFd/events/__init__.py +++ b/CTFd/events/__init__.py @@ -1,4 +1,5 @@ -from flask import current_app, Blueprint, Response, stream_with_context +from flask import Blueprint, Response, current_app, stream_with_context + from CTFd.utils import get_app_config from CTFd.utils.decorators import authed_only, ratelimit diff --git a/CTFd/models/__init__.py b/CTFd/models/__init__.py index 99c9ec0..828ae67 100644 --- a/CTFd/models/__init__.py +++ b/CTFd/models/__init__.py @@ -1,12 +1,14 @@ -from flask_sqlalchemy import SQLAlchemy +import datetime + +import six from flask_marshmallow import Marshmallow -from sqlalchemy.orm import validates, column_property +from flask_sqlalchemy import SQLAlchemy from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.orm import column_property, validates + +from CTFd.cache import cache from CTFd.utils.crypto import hash_password from CTFd.utils.humanize.numbers import ordinalize -from CTFd.cache import cache -import datetime -import six db = SQLAlchemy() ma = Marshmallow() diff --git a/CTFd/plugins/__init__.py b/CTFd/plugins/__init__.py index dca1f8f..42da0da 100644 --- a/CTFd/plugins/__init__.py +++ b/CTFd/plugins/__init__.py @@ -1,19 +1,22 @@ import glob import importlib import os - from collections import namedtuple -from flask import current_app as app, send_file, send_from_directory + +from flask import current_app as app +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 +from CTFd.utils.plugins import override_template as utils_override_template from CTFd.utils.plugins import ( - override_template as utils_override_template, - register_script as utils_register_plugin_script, - register_stylesheet as utils_register_plugin_stylesheet, register_admin_script as utils_register_admin_plugin_script, +) +from CTFd.utils.plugins import ( register_admin_stylesheet as utils_register_admin_plugin_stylesheet, ) -from CTFd.utils.config.pages import get_pages - +from CTFd.utils.plugins import register_script as utils_register_plugin_script +from CTFd.utils.plugins import register_stylesheet as utils_register_plugin_stylesheet Menu = namedtuple("Menu", ["title", "route"]) diff --git a/CTFd/plugins/challenges/__init__.py b/CTFd/plugins/challenges/__init__.py index 7dea80e..d6de41a 100644 --- a/CTFd/plugins/challenges/__init__.py +++ b/CTFd/plugins/challenges/__init__.py @@ -1,18 +1,19 @@ -from CTFd.plugins import register_plugin_assets_directory -from CTFd.plugins.flags import get_flag_class +from flask import Blueprint + from CTFd.models import ( - db, - Solves, + ChallengeFiles, + Challenges, Fails, Flags, - Challenges, - ChallengeFiles, - Tags, Hints, + Solves, + Tags, + db, ) -from CTFd.utils.user import get_ip +from CTFd.plugins import register_plugin_assets_directory +from CTFd.plugins.flags import get_flag_class from CTFd.utils.uploads import delete_file -from flask import Blueprint +from CTFd.utils.user import get_ip class BaseChallenge(object): diff --git a/CTFd/plugins/dynamic_challenges/__init__.py b/CTFd/plugins/dynamic_challenges/__init__.py index a763c45..f5edceb 100644 --- a/CTFd/plugins/dynamic_challenges/__init__.py +++ b/CTFd/plugins/dynamic_challenges/__init__.py @@ -1,22 +1,25 @@ from __future__ import division # Use floating point for math calculations -from CTFd.plugins.challenges import BaseChallenge, CHALLENGE_CLASSES -from CTFd.plugins import register_plugin_assets_directory -from CTFd.plugins.flags import get_flag_class + +import math + +from flask import Blueprint + from CTFd.models import ( - db, - Solves, + ChallengeFiles, + Challenges, Fails, Flags, - Challenges, - ChallengeFiles, - Tags, Hints, + Solves, + Tags, + db, ) -from CTFd.utils.user import get_ip -from CTFd.utils.uploads import delete_file +from CTFd.plugins import register_plugin_assets_directory +from CTFd.plugins.challenges import CHALLENGE_CLASSES, BaseChallenge +from CTFd.plugins.flags import get_flag_class from CTFd.utils.modes import get_model -from flask import Blueprint -import math +from CTFd.utils.uploads import delete_file +from CTFd.utils.user import get_ip class DynamicValueChallenge(BaseChallenge): diff --git a/CTFd/plugins/flags/__init__.py b/CTFd/plugins/flags/__init__.py index 5847693..0d2ca32 100644 --- a/CTFd/plugins/flags/__init__.py +++ b/CTFd/plugins/flags/__init__.py @@ -1,7 +1,7 @@ -from CTFd.plugins import register_plugin_assets_directory - import re +from CTFd.plugins import register_plugin_assets_directory + class BaseFlag(object): name = None diff --git a/CTFd/schemas/awards.py b/CTFd/schemas/awards.py index 4cfccc4..728e902 100644 --- a/CTFd/schemas/awards.py +++ b/CTFd/schemas/awards.py @@ -1,4 +1,4 @@ -from CTFd.models import ma, Awards +from CTFd.models import Awards, ma from CTFd.utils import string_types diff --git a/CTFd/schemas/challenges.py b/CTFd/schemas/challenges.py index 24d8bb4..ca1b877 100644 --- a/CTFd/schemas/challenges.py +++ b/CTFd/schemas/challenges.py @@ -1,4 +1,4 @@ -from CTFd.models import ma, Challenges +from CTFd.models import Challenges, ma class ChallengeSchema(ma.ModelSchema): diff --git a/CTFd/schemas/config.py b/CTFd/schemas/config.py index 138303f..068bd00 100644 --- a/CTFd/schemas/config.py +++ b/CTFd/schemas/config.py @@ -1,4 +1,4 @@ -from CTFd.models import ma, Configs +from CTFd.models import Configs, ma from CTFd.utils import string_types diff --git a/CTFd/schemas/files.py b/CTFd/schemas/files.py index b11736e..c401aa6 100644 --- a/CTFd/schemas/files.py +++ b/CTFd/schemas/files.py @@ -1,4 +1,4 @@ -from CTFd.models import ma, Files +from CTFd.models import Files, ma from CTFd.utils import string_types diff --git a/CTFd/schemas/flags.py b/CTFd/schemas/flags.py index 0c10f9d..3af60bc 100644 --- a/CTFd/schemas/flags.py +++ b/CTFd/schemas/flags.py @@ -1,4 +1,4 @@ -from CTFd.models import ma, Flags +from CTFd.models import Flags, ma from CTFd.utils import string_types diff --git a/CTFd/schemas/hints.py b/CTFd/schemas/hints.py index b28970f..dbbaab9 100644 --- a/CTFd/schemas/hints.py +++ b/CTFd/schemas/hints.py @@ -1,4 +1,4 @@ -from CTFd.models import ma, Hints +from CTFd.models import Hints, ma from CTFd.utils import string_types diff --git a/CTFd/schemas/notifications.py b/CTFd/schemas/notifications.py index b0d1a63..21a9376 100644 --- a/CTFd/schemas/notifications.py +++ b/CTFd/schemas/notifications.py @@ -1,4 +1,4 @@ -from CTFd.models import ma, Notifications +from CTFd.models import Notifications, ma from CTFd.utils import string_types diff --git a/CTFd/schemas/pages.py b/CTFd/schemas/pages.py index f534b08..0e1801a 100644 --- a/CTFd/schemas/pages.py +++ b/CTFd/schemas/pages.py @@ -1,5 +1,6 @@ from marshmallow import pre_load -from CTFd.models import ma, Pages + +from CTFd.models import Pages, ma from CTFd.utils import string_types diff --git a/CTFd/schemas/submissions.py b/CTFd/schemas/submissions.py index 2e546a2..22b3dad 100644 --- a/CTFd/schemas/submissions.py +++ b/CTFd/schemas/submissions.py @@ -1,6 +1,7 @@ from marshmallow import fields + +from CTFd.models import Submissions, ma from CTFd.schemas.challenges import ChallengeSchema -from CTFd.models import ma, Submissions from CTFd.utils import string_types diff --git a/CTFd/schemas/tags.py b/CTFd/schemas/tags.py index cf75ad1..ac4039e 100644 --- a/CTFd/schemas/tags.py +++ b/CTFd/schemas/tags.py @@ -1,4 +1,4 @@ -from CTFd.models import ma, Tags +from CTFd.models import Tags, ma from CTFd.utils import string_types diff --git a/CTFd/schemas/teams.py b/CTFd/schemas/teams.py index 9f6d128..be02c5f 100644 --- a/CTFd/schemas/teams.py +++ b/CTFd/schemas/teams.py @@ -1,11 +1,11 @@ -from marshmallow import validate, ValidationError, pre_load +from marshmallow import ValidationError, pre_load, validate from marshmallow_sqlalchemy import field_for -from CTFd.models import ma, Teams, Users -from CTFd.utils.validators import validate_country_code -from CTFd.utils import get_config -from CTFd.utils.user import is_admin, get_current_team, get_current_user + +from CTFd.models import Teams, Users, ma +from CTFd.utils import get_config, string_types from CTFd.utils.crypto import verify_password -from CTFd.utils import string_types +from CTFd.utils.user import get_current_team, get_current_user, is_admin +from CTFd.utils.validators import validate_country_code class TeamSchema(ma.ModelSchema): diff --git a/CTFd/schemas/tokens.py b/CTFd/schemas/tokens.py index 1ed2fba..d6ae4d3 100644 --- a/CTFd/schemas/tokens.py +++ b/CTFd/schemas/tokens.py @@ -1,4 +1,4 @@ -from CTFd.models import ma, Tokens +from CTFd.models import Tokens, ma from CTFd.utils import string_types diff --git a/CTFd/schemas/unlocks.py b/CTFd/schemas/unlocks.py index 84ae37b..964b453 100644 --- a/CTFd/schemas/unlocks.py +++ b/CTFd/schemas/unlocks.py @@ -1,4 +1,4 @@ -from CTFd.models import ma, Unlocks +from CTFd.models import Unlocks, ma from CTFd.utils import string_types diff --git a/CTFd/schemas/users.py b/CTFd/schemas/users.py index e9236c0..0714825 100644 --- a/CTFd/schemas/users.py +++ b/CTFd/schemas/users.py @@ -1,12 +1,12 @@ -from marshmallow import validate, ValidationError, pre_load +from marshmallow import ValidationError, pre_load, validate from marshmallow_sqlalchemy import field_for -from CTFd.models import ma, Users -from CTFd.utils import get_config -from CTFd.utils.validators import validate_country_code -from CTFd.utils.user import is_admin, get_current_user + +from CTFd.models import Users, ma +from CTFd.utils import get_config, string_types from CTFd.utils.crypto import verify_password from CTFd.utils.email import check_email_is_whitelisted -from CTFd.utils import string_types +from CTFd.utils.user import get_current_user, is_admin +from CTFd.utils.validators import validate_country_code class UserSchema(ma.ModelSchema): diff --git a/CTFd/scoreboard.py b/CTFd/scoreboard.py index 22ea03a..a49fecd 100644 --- a/CTFd/scoreboard.py +++ b/CTFd/scoreboard.py @@ -1,9 +1,8 @@ -from flask import render_template, Blueprint +from flask import Blueprint, render_template from CTFd.cache import cache, make_cache_key from CTFd.utils import config from CTFd.utils.decorators.visibility import check_score_visibility - from CTFd.utils.scores import get_standings scoreboard = Blueprint("scoreboard", __name__) diff --git a/CTFd/teams.py b/CTFd/teams.py index 43cc67f..c26e41a 100644 --- a/CTFd/teams.py +++ b/CTFd/teams.py @@ -1,15 +1,16 @@ -from flask import render_template, request, redirect, url_for, Blueprint -from CTFd.models import db, Teams +from flask import Blueprint, redirect, render_template, request, url_for + +from CTFd.models import Teams, db +from CTFd.utils import config, get_config +from CTFd.utils.crypto import verify_password from CTFd.utils.decorators import authed_only, ratelimit from CTFd.utils.decorators.modes import require_team_mode -from CTFd.utils import config, get_config -from CTFd.utils.user import get_current_user -from CTFd.utils.crypto import verify_password from CTFd.utils.decorators.visibility import ( check_account_visibility, check_score_visibility, ) from CTFd.utils.helpers import get_errors, get_infos +from CTFd.utils.user import get_current_user teams = Blueprint("teams", __name__) @@ -72,7 +73,9 @@ def join(): name=team.name, limit=team_size_limit ) ) - return render_template("teams/join_team.html", infos=infos, errors=errors) + return render_template( + "teams/join_team.html", infos=infos, errors=errors + ) user.team_id = team.id db.session.commit() diff --git a/CTFd/themes/admin/assets/js/challenges/hints.js b/CTFd/themes/admin/assets/js/challenges/hints.js index d2bc18e..228aaac 100644 --- a/CTFd/themes/admin/assets/js/challenges/hints.js +++ b/CTFd/themes/admin/assets/js/challenges/hints.js @@ -118,8 +118,8 @@ export function editHint(event) { const params = $(this).serializeJSON(true); params["challenge"] = CHALLENGE_ID; - const method = "POST"; - const url = "/api/v1/hints"; + let method = "POST"; + let url = "/api/v1/hints"; if (params.id) { method = "PATCH"; url = "/api/v1/hints/" + params.id; diff --git a/CTFd/themes/admin/static/js/core.dev.js b/CTFd/themes/admin/static/js/core.dev.js index 5afa1ee..8b7c6f5 100644 --- a/CTFd/themes/admin/static/js/core.dev.js +++ b/CTFd/themes/admin/static/js/core.dev.js @@ -32,7 +32,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n /***/ (function(module, exports, __webpack_require__) { ; -eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _fetch = _interopRequireDefault(__webpack_require__(/*! ./fetch */ \"./CTFd/themes/core/assets/js/fetch.js\"));\n\nvar _config = _interopRequireDefault(__webpack_require__(/*! ./config */ \"./CTFd/themes/core/assets/js/config.js\"));\n\nvar _api = __webpack_require__(/*! ./api */ \"./CTFd/themes/core/assets/js/api.js\");\n\n__webpack_require__(/*! ./patch */ \"./CTFd/themes/core/assets/js/patch.js\");\n\nvar _markdownIt = _interopRequireDefault(__webpack_require__(/*! markdown-it */ \"./node_modules/markdown-it/index.js\"));\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { if (i % 2) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } else { Object.defineProperties(target, Object.getOwnPropertyDescriptors(arguments[i])); } } return target; }\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nvar api = new _api.API(\"/\");\nvar user = {};\nvar _internal = {};\nvar lib = {\n $: _jquery.default,\n markdown: markdown\n};\nvar initialized = false;\n\nvar init = function init(data) {\n if (initialized) {\n return;\n }\n\n initialized = true;\n _config.default.urlRoot = data.urlRoot || _config.default.urlRoot;\n _config.default.csrfNonce = data.csrfNonce || _config.default.csrfNonce;\n _config.default.userMode = data.userMode || _config.default.userMode;\n api.domain = _config.default.urlRoot + \"/api/v1\";\n user.id = data.userId;\n};\n\nvar plugin = {\n run: function run(f) {\n f(CTFd);\n }\n};\n\nfunction markdown(config) {\n // Merge passed config with original. Default to original.\n var md_config = _objectSpread({}, {\n html: true,\n linkify: true\n }, {}, config);\n\n var md = (0, _markdownIt.default)(md_config);\n\n md.renderer.rules.link_open = function (tokens, idx, options, env, self) {\n tokens[idx].attrPush([\"target\", \"_blank\"]);\n return self.renderToken(tokens, idx, options);\n };\n\n return md;\n}\n\nvar CTFd = {\n init: init,\n config: _config.default,\n fetch: _fetch.default,\n user: user,\n api: api,\n lib: lib,\n _internal: _internal,\n plugin: plugin\n};\nvar _default = CTFd;\nexports.default = _default;\n\n//# sourceURL=webpack:///./CTFd/themes/core/assets/js/CTFd.js?"); +eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _fetch = _interopRequireDefault(__webpack_require__(/*! ./fetch */ \"./CTFd/themes/core/assets/js/fetch.js\"));\n\nvar _config = _interopRequireDefault(__webpack_require__(/*! ./config */ \"./CTFd/themes/core/assets/js/config.js\"));\n\nvar _api = __webpack_require__(/*! ./api */ \"./CTFd/themes/core/assets/js/api.js\");\n\n__webpack_require__(/*! ./patch */ \"./CTFd/themes/core/assets/js/patch.js\");\n\nvar _markdownIt = _interopRequireDefault(__webpack_require__(/*! markdown-it */ \"./node_modules/markdown-it/index.js\"));\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _ezq = _interopRequireDefault(__webpack_require__(/*! ./ezq */ \"./CTFd/themes/core/assets/js/ezq.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { if (i % 2) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } else { Object.defineProperties(target, Object.getOwnPropertyDescriptors(arguments[i])); } } return target; }\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nvar api = new _api.API(\"/\");\nvar user = {};\nvar _internal = {};\nvar ui = {\n ezq: _ezq.default\n};\nvar lib = {\n $: _jquery.default,\n markdown: markdown\n};\nvar initialized = false;\n\nvar init = function init(data) {\n if (initialized) {\n return;\n }\n\n initialized = true;\n _config.default.urlRoot = data.urlRoot || _config.default.urlRoot;\n _config.default.csrfNonce = data.csrfNonce || _config.default.csrfNonce;\n _config.default.userMode = data.userMode || _config.default.userMode;\n api.domain = _config.default.urlRoot + \"/api/v1\";\n user.id = data.userId;\n};\n\nvar plugin = {\n run: function run(f) {\n f(CTFd);\n }\n};\n\nfunction markdown(config) {\n // Merge passed config with original. Default to original.\n var md_config = _objectSpread({}, {\n html: true,\n linkify: true\n }, {}, config);\n\n var md = (0, _markdownIt.default)(md_config);\n\n md.renderer.rules.link_open = function (tokens, idx, options, env, self) {\n tokens[idx].attrPush([\"target\", \"_blank\"]);\n return self.renderToken(tokens, idx, options);\n };\n\n return md;\n}\n\nvar CTFd = {\n init: init,\n config: _config.default,\n fetch: _fetch.default,\n user: user,\n ui: ui,\n api: api,\n lib: lib,\n _internal: _internal,\n plugin: plugin\n};\nvar _default = CTFd;\nexports.default = _default;\n\n//# sourceURL=webpack:///./CTFd/themes/core/assets/js/CTFd.js?"); /***/ }), diff --git a/CTFd/themes/admin/static/js/pages/challenge.dev.js b/CTFd/themes/admin/static/js/pages/challenge.dev.js index a802729..16777f8 100644 --- a/CTFd/themes/admin/static/js/pages/challenge.dev.js +++ b/CTFd/themes/admin/static/js/pages/challenge.dev.js @@ -186,7 +186,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n /***/ (function(module, exports, __webpack_require__) { ; -eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.showHintModal = showHintModal;\nexports.showEditHintModal = showEditHintModal;\nexports.deleteHint = deleteHint;\nexports.editHint = editHint;\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd */ \"./CTFd/themes/core/assets/js/CTFd.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 _readOnlyError(name) { throw new Error(\"\\\"\" + name + \"\\\" is read-only\"); }\n\nfunction hint(id) {\n return _CTFd.default.fetch(\"/api/v1/hints/\" + id + \"?preview=true\", {\n method: \"GET\",\n credentials: \"same-origin\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n }\n });\n}\n\nfunction loadhint(hintid) {\n var md = _CTFd.default.lib.markdown();\n\n hint(hintid).then(function (response) {\n if (response.data.content) {\n (0, _ezq.ezAlert)({\n title: \"Hint\",\n body: md.render(response.data.content),\n button: \"Got it!\"\n });\n } else {\n (0, _ezq.ezAlert)({\n title: \"Error\",\n body: \"Error loading hint!\",\n button: \"OK\"\n });\n }\n });\n}\n\nfunction showHintModal(event) {\n event.preventDefault();\n (0, _jquery.default)(\"#hint-edit-modal form\").find(\"input, textarea\").val(\"\"); // Markdown Preview\n\n (0, _jquery.default)(\"#new-hint-edit\").on(\"shown.bs.tab\", function (event) {\n if (event.target.hash == \"#hint-preview\") {\n var renderer = _CTFd.default.lib.markdown();\n\n var editor_value = (0, _jquery.default)(\"#hint-write textarea\").val();\n (0, _jquery.default)(event.target.hash).html(renderer.render(editor_value));\n }\n });\n (0, _jquery.default)(\"#hint-edit-modal\").modal();\n}\n\nfunction showEditHintModal(event) {\n event.preventDefault();\n var hint_id = (0, _jquery.default)(this).attr(\"hint-id\");\n\n _CTFd.default.fetch(\"/api/v1/hints/\" + hint_id + \"?preview=true\", {\n method: \"GET\",\n credentials: \"same-origin\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n }\n }).then(function (response) {\n return response.json();\n }).then(function (response) {\n if (response.success) {\n (0, _jquery.default)(\"#hint-edit-form input[name=content],textarea[name=content]\").val(response.data.content);\n (0, _jquery.default)(\"#hint-edit-form input[name=cost]\").val(response.data.cost);\n (0, _jquery.default)(\"#hint-edit-form input[name=id]\").val(response.data.id); // Markdown Preview\n\n (0, _jquery.default)(\"#new-hint-edit\").on(\"shown.bs.tab\", function (event) {\n if (event.target.hash == \"#hint-preview\") {\n var renderer = _CTFd.default.lib.markdown();\n\n var editor_value = (0, _jquery.default)(\"#hint-write textarea\").val();\n (0, _jquery.default)(event.target.hash).html(renderer.render(editor_value));\n }\n });\n (0, _jquery.default)(\"#hint-edit-modal\").modal();\n }\n });\n}\n\nfunction deleteHint(event) {\n event.preventDefault();\n var hint_id = (0, _jquery.default)(this).attr(\"hint-id\");\n var row = (0, _jquery.default)(this).parent().parent();\n (0, _ezq.ezQuery)({\n title: \"Delete Hint\",\n body: \"Are you sure you want to delete this hint?\",\n success: function success() {\n _CTFd.default.fetch(\"/api/v1/hints/\" + hint_id, {\n method: \"DELETE\"\n }).then(function (response) {\n return response.json();\n }).then(function (data) {\n if (data.success) {\n row.remove();\n }\n });\n }\n });\n}\n\nfunction editHint(event) {\n event.preventDefault();\n var params = (0, _jquery.default)(this).serializeJSON(true);\n params[\"challenge\"] = CHALLENGE_ID;\n var method = \"POST\";\n var url = \"/api/v1/hints\";\n\n if (params.id) {\n method = (_readOnlyError(\"method\"), \"PATCH\");\n url = (_readOnlyError(\"url\"), \"/api/v1/hints/\" + params.id);\n }\n\n _CTFd.default.fetch(url, {\n method: method,\n credentials: \"same-origin\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(params)\n }).then(function (response) {\n return response.json();\n }).then(function (data) {\n if (data.success) {\n // TODO: Refresh hints on submit.\n window.location.reload();\n }\n });\n}\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/challenges/hints.js?"); +eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.showHintModal = showHintModal;\nexports.showEditHintModal = showEditHintModal;\nexports.deleteHint = deleteHint;\nexports.editHint = editHint;\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd */ \"./CTFd/themes/core/assets/js/CTFd.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 hint(id) {\n return _CTFd.default.fetch(\"/api/v1/hints/\" + id + \"?preview=true\", {\n method: \"GET\",\n credentials: \"same-origin\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n }\n });\n}\n\nfunction loadhint(hintid) {\n var md = _CTFd.default.lib.markdown();\n\n hint(hintid).then(function (response) {\n if (response.data.content) {\n (0, _ezq.ezAlert)({\n title: \"Hint\",\n body: md.render(response.data.content),\n button: \"Got it!\"\n });\n } else {\n (0, _ezq.ezAlert)({\n title: \"Error\",\n body: \"Error loading hint!\",\n button: \"OK\"\n });\n }\n });\n}\n\nfunction showHintModal(event) {\n event.preventDefault();\n (0, _jquery.default)(\"#hint-edit-modal form\").find(\"input, textarea\").val(\"\"); // Markdown Preview\n\n (0, _jquery.default)(\"#new-hint-edit\").on(\"shown.bs.tab\", function (event) {\n if (event.target.hash == \"#hint-preview\") {\n var renderer = _CTFd.default.lib.markdown();\n\n var editor_value = (0, _jquery.default)(\"#hint-write textarea\").val();\n (0, _jquery.default)(event.target.hash).html(renderer.render(editor_value));\n }\n });\n (0, _jquery.default)(\"#hint-edit-modal\").modal();\n}\n\nfunction showEditHintModal(event) {\n event.preventDefault();\n var hint_id = (0, _jquery.default)(this).attr(\"hint-id\");\n\n _CTFd.default.fetch(\"/api/v1/hints/\" + hint_id + \"?preview=true\", {\n method: \"GET\",\n credentials: \"same-origin\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n }\n }).then(function (response) {\n return response.json();\n }).then(function (response) {\n if (response.success) {\n (0, _jquery.default)(\"#hint-edit-form input[name=content],textarea[name=content]\").val(response.data.content);\n (0, _jquery.default)(\"#hint-edit-form input[name=cost]\").val(response.data.cost);\n (0, _jquery.default)(\"#hint-edit-form input[name=id]\").val(response.data.id); // Markdown Preview\n\n (0, _jquery.default)(\"#new-hint-edit\").on(\"shown.bs.tab\", function (event) {\n if (event.target.hash == \"#hint-preview\") {\n var renderer = _CTFd.default.lib.markdown();\n\n var editor_value = (0, _jquery.default)(\"#hint-write textarea\").val();\n (0, _jquery.default)(event.target.hash).html(renderer.render(editor_value));\n }\n });\n (0, _jquery.default)(\"#hint-edit-modal\").modal();\n }\n });\n}\n\nfunction deleteHint(event) {\n event.preventDefault();\n var hint_id = (0, _jquery.default)(this).attr(\"hint-id\");\n var row = (0, _jquery.default)(this).parent().parent();\n (0, _ezq.ezQuery)({\n title: \"Delete Hint\",\n body: \"Are you sure you want to delete this hint?\",\n success: function success() {\n _CTFd.default.fetch(\"/api/v1/hints/\" + hint_id, {\n method: \"DELETE\"\n }).then(function (response) {\n return response.json();\n }).then(function (data) {\n if (data.success) {\n row.remove();\n }\n });\n }\n });\n}\n\nfunction editHint(event) {\n event.preventDefault();\n var params = (0, _jquery.default)(this).serializeJSON(true);\n params[\"challenge\"] = CHALLENGE_ID;\n var method = \"POST\";\n var url = \"/api/v1/hints\";\n\n if (params.id) {\n method = \"PATCH\";\n url = \"/api/v1/hints/\" + params.id;\n }\n\n _CTFd.default.fetch(url, {\n method: method,\n credentials: \"same-origin\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(params)\n }).then(function (response) {\n return response.json();\n }).then(function (data) {\n if (data.success) {\n // TODO: Refresh hints on submit.\n window.location.reload();\n }\n });\n}\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/challenges/hints.js?"); /***/ }), diff --git a/CTFd/themes/admin/static/js/pages/challenge.min.js b/CTFd/themes/admin/static/js/pages/challenge.min.js index c07d6c6..ac5b2ee 100644 --- a/CTFd/themes/admin/static/js/pages/challenge.min.js +++ b/CTFd/themes/admin/static/js/pages/challenge.min.js @@ -1 +1 @@ -!function(d){function e(e){for(var t,o,n=e[0],s=e[1],a=e[2],i=0,l=[];i -- ");for(var s in o.append(n),t)t.hasOwnProperty(s)&&(n=(0,a.default)("".format(s,t[s].name)),o.append(n));(0,a.default)("#flag-edit-modal").modal()}),(0,a.default)("#flag-edit-modal form").submit(function(e){e.preventDefault();var t=(0,a.default)(this).serializeJSON(!0);t.challenge=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,a.default)("#flag-edit-modal").modal()},t.editFlagModal=function(e){e.preventDefault();var n=(0,a.default)(this).attr("flag-id"),s=(0,a.default)(this).parent().parent();a.default.get(i.default.config.urlRoot+"/api/v1/flags/"+n,function(e){var o=e.data;a.default.get(i.default.config.urlRoot+o.templates.update,function(e){(0,a.default)("#edit-flags form").empty(),(0,a.default)("#edit-flags form").off();var t=l.default.compile(e);(0,a.default)("#edit-flags form").append(t.render(o)),(0,a.default)("#edit-flags form").submit(function(e){e.preventDefault();var t=(0,a.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,a.default)(s).find(".flag-content").text(e.data.content),(0,a.default)("#edit-flags").modal("toggle"))})}),(0,a.default)("#edit-flags").modal()})})},t.flagTypeSelect=function(e){e.preventDefault();var t=(0,a.default)(this).find("option:selected").text();a.default.get(i.default.config.urlRoot+"/api/v1/flags/types/"+t,function(e){var t=e.data;a.default.get(i.default.config.urlRoot+t.templates.create,function(e){var t=l.default.compile(e);(0,a.default)("#create-keys-entry-div").html(t.render()),(0,a.default)("#create-keys-button-div").show()})})};var a=s(o("./node_modules/jquery/dist/jquery.js")),i=s(o("./CTFd/themes/core/assets/js/CTFd.js")),l=s(o("./node_modules/nunjucks/browser/nunjucks.js")),n=o("./CTFd/themes/core/assets/js/ezq.js");function s(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,s.default)("#hint-edit-modal form").find("input, textarea").val(""),(0,s.default)("#new-hint-edit").on("shown.bs.tab",function(e){if("#hint-preview"==e.target.hash){var t=a.default.lib.markdown(),o=(0,s.default)("#hint-write textarea").val();(0,s.default)(e.target.hash).html(t.render(o))}}),(0,s.default)("#hint-edit-modal").modal()},t.showEditHintModal=function(e){e.preventDefault();var t=(0,s.default)(this).attr("hint-id");a.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,s.default)("#hint-edit-form input[name=content],textarea[name=content]").val(e.data.content),(0,s.default)("#hint-edit-form input[name=cost]").val(e.data.cost),(0,s.default)("#hint-edit-form input[name=id]").val(e.data.id),(0,s.default)("#new-hint-edit").on("shown.bs.tab",function(e){if("#hint-preview"==e.target.hash){var t=a.default.lib.markdown(),o=(0,s.default)("#hint-write textarea").val();(0,s.default)(e.target.hash).html(t.render(o))}}),(0,s.default)("#hint-edit-modal").modal())})},t.deleteHint=function(e){e.preventDefault();var t=(0,s.default)(this).attr("hint-id"),o=(0,s.default)(this).parent().parent();(0,n.ezQuery)({title:"Delete Hint",body:"Are you sure you want to delete this hint?",success:function(){a.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,s.default)(this).serializeJSON(!0);t.challenge=CHALLENGE_ID;var o="POST",n="/api/v1/hints";t.id&&(l("method"),o="PATCH",l("url"),n="/api/v1/hints/"+t.id);a.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 s=i(o("./node_modules/jquery/dist/jquery.js")),a=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}}function l(e){throw new Error('"'+e+'" is read-only')}},"./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,s.default)("#prerequisite-add-form").serializeJSON();if(!t.prerequisite)return;CHALLENGE_REQUIREMENTS.prerequisites.push(parseInt(t.prerequisite));var o={requirements:CHALLENGE_REQUIREMENTS};a.default.fetch("/api/v1/challenges/"+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,s.default)(this).attr("challenge-id"),o=(0,s.default)(this).parent().parent();CHALLENGE_REQUIREMENTS.prerequisites.pop(t);var n={requirements:CHALLENGE_REQUIREMENTS};a.default.fetch("/api/v1/challenges/"+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 s=n(o("./node_modules/jquery/dist/jquery.js")),a=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:CHALLENGE_ID};s.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=a(o("./node_modules/jquery/dist/jquery.js")),s=a(o("./CTFd/themes/core/assets/js/CTFd.js"));function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=(0,n.default)(this),o=t.attr("tag-id");s.default.api.delete_tag({tagId:o}).then(function(e){e.success&&t.parent().remove()})}},"./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")),s=o("./CTFd/themes/core/assets/js/ezq.js"),a=f(o("./node_modules/nunjucks/browser/nunjucks.js")),d=f(o("./CTFd/themes/core/assets/js/helpers.js")),r=o("./CTFd/themes/admin/assets/js/challenges/files.js"),c=o("./CTFd/themes/admin/assets/js/challenges/tags.js"),u=o("./CTFd/themes/admin/assets/js/challenges/requirements.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 j(t){i.default.api.get_hint({hintId:t,preview:!0}).then(function(e){e.data.content?function(e){(0,s.ezAlert)({title:"Hint",body:h.render(e.content),button:"Got it!"})}(e.data):displayUnlock(t)})}var h=i.default.lib.markdown();function _(e,t){var o=e.data,n=(0,l.default)("#result-message"),s=(0,l.default)("#result-notification"),a=(0,l.default)("#submission-input");s.removeClass(),n.text(o.message),"authentication_required"!==o.status?("incorrect"===o.status?(s.addClass("alert alert-danger alert-dismissable text-center"),s.slideDown(),a.removeClass("correct"),a.addClass("wrong"),setTimeout(function(){a.removeClass("wrong")},3e3)):"correct"===o.status?(s.addClass("alert alert-success alert-dismissable text-center"),s.slideDown(),(0,l.default)(".challenge-solves").text(parseInt((0,l.default)(".challenge-solves").text().split(" ")[0])+1+" Solves"),a.val(""),a.removeClass("wrong"),a.addClass("correct")):"already_solved"===o.status?(s.addClass("alert alert-info alert-dismissable text-center"),s.slideDown(),a.addClass("correct")):"paused"===o.status?(s.addClass("alert alert-warning alert-dismissable text-center"),s.slideDown()):"ratelimited"===o.status&&(s.addClass("alert alert-warning alert-dismissable text-center"),s.slideDown(),a.addClass("too-fast"),setTimeout(function(){a.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 g(o){i.default._internal.challenge={},l.default.getScript(i.default.config.urlRoot+o.scripts.view,function(){l.default.get(i.default.config.urlRoot+o.templates.create,function(e){var t=a.default.compile(e);(0,l.default)("#create-chal-entry-div").html(t.render({nonce:i.default.config.csrfNonce,script_root:i.default.config.urlRoot})),l.default.getScript(i.default.config.urlRoot+o.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(s){s.preventDefault();var a=(0,l.default)(s.target).serializeJSON(!0),o={challenge_id:a.challenge_id,content:a.flag||"",type:a.flag_type,data:a.flag_data?a.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=s.target,n={challenge:a.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/"+a.challenge_id,{method:"PATCH",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({state:a.state})}).then(function(e){return e.json()}).then(function(e){e.success&&setTimeout(function(){window.location=i.default.config.urlRoot+"/admin/challenges/"+a.challenge_id},700)})})}function y(e){var t=(0,l.default)(this).find("option:selected").data("meta");void 0!==t?g(t):(0,l.default)("#create-chal-entry-div").empty()}(0,l.default)(function(){if((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/"+CHALLENGE_ID,function(e){var o=i.default._internal.challenge,n=e.data;n.solves=null,l.default.getScript(i.default.config.urlRoot+n.type_data.scripts.view,function(){l.default.get(i.default.config.urlRoot+n.type_data.templates.view,function(e){(0,l.default)("#challenge-window").empty();var t=a.default.compile(e);o.data=n,o.preRender(),n.description=o.render(n.description),n.script_root=i.default.config.urlRoot,(0,l.default)("#challenge-window").append(t.render(n)),(0,l.default)(".challenge-solves").click(function(e){getsolves((0,l.default)("#challenge-id").val())}),(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)("#submission-input").removeClass("wrong"),(0,l.default)("#submission-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){j((0,l.default)(this).data("hint-id"))}),(0,l.default)("#submit-key").click(function(e){e.preventDefault(),(0,l.default)("#submit-key").addClass("disabled-button"),(0,l.default)("#submit-key").prop("disabled",!0),i.default._internal.challenge.submit(!0).then(_)}),(0,l.default)("#submission-input").keyup(function(e){13==e.keyCode&&(0,l.default)("#submit-key").click()}),(0,l.default)(".input-field").bind({focus:function(){(0,l.default)(this).parent().addClass("input--filled"),$label=(0,l.default)(this).siblings(".input-label")},blur:function(){""===(0,l.default)(this).val()&&((0,l.default)(this).parent().removeClass("input--filled"),$label=(0,l.default)(this).siblings(".input-label"),$label.removeClass("input--hide"))}}),o.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,s.ezQuery)({title:"Delete Challenge",body:"Are you sure you want to delete {0}".format(""+(0,n.htmlEntities)(CHALLENGE_NAME)+""),success:function(){i.default.fetch("/api/v1/challenges/"+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/"+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/"+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&&(0,s.ezToast)({title:"Success",body:"Your challenge has been updated!"})})}0===e.data.length&&"visible"===o.state?(0,s.ezQuery)({title:"Missing Flags",body:"This challenge does not have any flags meaning it is 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)(".nav-tabs a").click(function(e){(0,l.default)(this).tab("show"),window.location.hash=this.hash}),window.location.hash){var e=window.location.hash.replace("<>[]'\"","");(0,l.default)('nav a[href="'+e+'"]').tab("show")}(0,l.default)("#tags-add-input").keyup(c.addTag),(0,l.default)(".delete-tag").click(c.deleteTag),(0,l.default)("#prerequisite-add-form").submit(u.addRequirement),(0,l.default)(".delete-requirement").click(u.deleteRequirement),(0,l.default)("#file-add-form").submit(r.addFile),(0,l.default)(".delete-file").click(r.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 s=t[n],a=(0,l.default)("