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
bulk-clear-sessions 2.3.0
Kevin Chung 2020-02-17 02:17:25 -05:00 committed by GitHub
parent 354954bbe9
commit 22c132358e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
197 changed files with 3253 additions and 1580 deletions

View File

@ -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 2.2.3 / 2020-01-21
================== ==================

View File

@ -1,36 +1,36 @@
import sys import datetime
import os import os
import sys
from distutils.version import StrictVersion from distutils.version import StrictVersion
from flask import Flask, Request from flask import Flask, Request
from flask_migrate import upgrade from flask_migrate import upgrade
from werkzeug.utils import cached_property
from werkzeug.middleware.proxy_fix import ProxyFix
from jinja2 import FileSystemLoader from jinja2 import FileSystemLoader
from jinja2.sandbox import SandboxedEnvironment from jinja2.sandbox import SandboxedEnvironment
from six.moves import input from six.moves import input
from werkzeug.middleware.proxy_fix import ProxyFix
from werkzeug.utils import cached_property
from CTFd import utils from CTFd import utils
from CTFd.utils.migrations import migrations, create_database, stamp_latest_revision from CTFd.plugins import init_plugins
from CTFd.utils.sessions import CachingSessionInterface from CTFd.utils.crypto import sha256
from CTFd.utils.updates import update_check
from CTFd.utils.initialization import ( from CTFd.utils.initialization import (
init_events,
init_logs,
init_request_processors, init_request_processors,
init_template_filters, init_template_filters,
init_template_globals, init_template_globals,
init_logs,
init_events,
) )
from CTFd.utils.crypto import sha256 from CTFd.utils.migrations import create_database, migrations, stamp_latest_revision
from CTFd.plugins import init_plugins from CTFd.utils.sessions import CachingSessionInterface
import datetime from CTFd.utils.updates import update_check
# Hack to support Unicode in Python 2 properly # Hack to support Unicode in Python 2 properly
if sys.version_info[0] < 3: if sys.version_info[0] < 3:
reload(sys) # noqa: F821 reload(sys) # noqa: F821
sys.setdefaultencoding("utf-8") sys.setdefaultencoding("utf-8")
__version__ = "2.2.3" __version__ = "2.3.0"
class CTFdRequest(Request): class CTFdRequest(Request):

View File

@ -1,54 +1,51 @@
from flask import ( import csv
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 datetime import datetime
import os 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__) admin = Blueprint("admin", __name__)
from CTFd.admin import challenges # noqa: F401 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 pages # noqa: F401
from CTFd.admin import scoreboard # noqa: F401 from CTFd.admin import scoreboard # noqa: F401
from CTFd.admin import statistics # 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 teams # noqa: F401
from CTFd.admin import users # 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"]) @admin.route("/admin", methods=["GET"])

View File

@ -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 os
import six 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") @admin.route("/admin/challenges")

View File

@ -1,7 +1,8 @@
from flask import render_template from flask import render_template
from CTFd.utils.decorators import admins_only
from CTFd.models import Notifications
from CTFd.admin import admin from CTFd.admin import admin
from CTFd.models import Notifications
from CTFd.utils.decorators import admins_only
@admin.route("/admin/notifications") @admin.route("/admin/notifications")

View File

@ -1,9 +1,10 @@
from flask import render_template, request 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.models import Pages
from CTFd.schemas.pages import PageSchema from CTFd.schemas.pages import PageSchema
from CTFd.utils import markdown from CTFd.utils import markdown
from CTFd.admin import admin from CTFd.utils.decorators import admins_only
@admin.route("/admin/pages") @admin.route("/admin/pages")

View File

@ -1,7 +1,8 @@
from flask import render_template from flask import render_template
from CTFd.utils.decorators import admins_only
from CTFd.admin import admin from CTFd.admin import admin
from CTFd.scoreboard import get_standings from CTFd.scoreboard import get_standings
from CTFd.utils.decorators import admins_only
@admin.route("/admin/scoreboard") @admin.route("/admin/scoreboard")

View File

@ -1,9 +1,10 @@
from flask import render_template 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.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"]) @admin.route("/admin/statistics", methods=["GET"])

View File

@ -1,8 +1,9 @@
from flask import render_template, request 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.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}) @admin.route("/admin/submissions", defaults={"submission_type": None})

View File

@ -1,11 +1,11 @@
from flask import render_template, request 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 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") @admin.route("/admin/teams")
@admins_only @admins_only

View File

@ -1,12 +1,12 @@
from flask import render_template, request 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 import get_config
from CTFd.utils.decorators import admins_only 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 CTFd.utils.helpers import get_errors
from CTFd.utils.modes import TEAMS_MODE
from sqlalchemy.sql import not_
@admin.route("/admin/users") @admin.route("/admin/users")

View File

@ -1,22 +1,22 @@
from flask import Blueprint, current_app from flask import Blueprint, current_app
from flask_restplus import Api 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.awards import awards_namespace
from CTFd.api.v1.users import users_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.scoreboard import scoreboard_namespace
from CTFd.api.v1.statistics import statistics_namespace from CTFd.api.v1.statistics import statistics_namespace
from CTFd.api.v1.submissions import submissions_namespace from CTFd.api.v1.submissions import submissions_namespace
from CTFd.api.v1.tags import tags_namespace from CTFd.api.v1.tags import tags_namespace
from CTFd.api.v1.awards import awards_namespace from CTFd.api.v1.teams import teams_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.tokens import tokens_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") api = Blueprint("api", __name__, url_prefix="/api/v1")
CTFd_API_v1 = Api(api, version="v1", doc=current_app.config.get("SWAGGER_UI")) CTFd_API_v1 = Api(api, version="v1", doc=current_app.config.get("SWAGGER_UI"))

View File

@ -1,7 +1,8 @@
from flask import request from flask import request
from flask_restplus import Namespace, Resource from flask_restplus import Namespace, Resource
from CTFd.cache import clear_standings 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.schemas.awards import AwardSchema
from CTFd.utils.decorators import admins_only from CTFd.utils.decorators import admins_only

View File

@ -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 flask_restplus import Namespace, Resource
from CTFd.models import ( from sqlalchemy.sql import and_
db,
Challenges, from CTFd.cache import clear_standings
HintUnlocks, from CTFd.models import ChallengeFiles as ChallengeFilesModel
Tags, from CTFd.models import Challenges, Fails, Flags, Hints, HintUnlocks, Solves, Tags, db
Hints, from CTFd.plugins.challenges import CHALLENGE_CLASSES, get_chal_class
Flags, from CTFd.schemas.flags import FlagSchema
Solves, from CTFd.schemas.hints import HintSchema
Fails, from CTFd.schemas.tags import TagSchema
ChallengeFiles as ChallengeFilesModel, 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 ctf_ended, ctf_paused, ctftime, isoformat, unix_time_to_utc
from CTFd.utils.dates import isoformat
from CTFd.utils.decorators import ( from CTFd.utils.decorators import (
admins_only,
during_ctf_time_only, during_ctf_time_only,
require_verified_emails, require_verified_emails,
admins_only,
) )
from CTFd.utils.decorators.visibility import ( from CTFd.utils.decorators.visibility import (
check_challenge_visibility, check_challenge_visibility,
check_score_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.logging import log
from CTFd.utils.modes import generate_account_url, get_model
from CTFd.utils.security.signing import serialize from CTFd.utils.security.signing import serialize
from sqlalchemy.sql import and_ from CTFd.utils.user import authed, get_current_team, get_current_user, is_admin
import datetime
challenges_namespace = Namespace( challenges_namespace = Namespace(
"challenges", description="Endpoint to retrieve Challenges" "challenges", description="Endpoint to retrieve Challenges"

View File

@ -1,10 +1,11 @@
from flask import request from flask import request
from flask_restplus import Namespace, Resource 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.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") configs_namespace = Namespace("configs", description="Endpoint to retrieve Configs")

View File

@ -1,6 +1,7 @@
from flask import request from flask import request
from flask_restplus import Namespace, Resource 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.schemas.files import FileSchema
from CTFd.utils import uploads from CTFd.utils import uploads
from CTFd.utils.decorators import admins_only from CTFd.utils.decorators import admins_only

View File

@ -1,8 +1,9 @@
from flask import request from flask import request
from flask_restplus import Namespace, Resource 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.schemas.flags import FlagSchema
from CTFd.plugins.flags import get_flag_class, FLAG_CLASSES
from CTFd.utils.decorators import admins_only from CTFd.utils.decorators import admins_only
flags_namespace = Namespace("flags", description="Endpoint to retrieve Flags") flags_namespace = Namespace("flags", description="Endpoint to retrieve Flags")

View File

@ -1,9 +1,10 @@
from flask import request from flask import request
from flask_restplus import Namespace, Resource 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.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") hints_namespace = Namespace("hints", description="Endpoint to retrieve Hints")

View File

@ -1,8 +1,8 @@
from flask import current_app, request from flask import current_app, request
from flask_restplus import Namespace, Resource 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 from CTFd.utils.decorators import admins_only
notifications_namespace = Namespace( notifications_namespace = Namespace(

View File

@ -1,9 +1,9 @@
from flask import request from flask import request
from flask_restplus import Namespace, Resource 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 from CTFd.utils.decorators import admins_only
pages_namespace = Namespace("pages", description="Endpoint to retrieve Pages") pages_namespace = Namespace("pages", description="Endpoint to retrieve Pages")

View File

@ -1,15 +1,15 @@
from flask_restplus import Namespace, Resource from flask_restplus import Namespace, Resource
from CTFd.models import Solves, Awards, Teams
from CTFd.cache import cache, make_cache_key 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 import get_config
from CTFd.utils.modes import generate_account_url, get_mode_as_word, TEAMS_MODE from CTFd.utils.dates import isoformat, unix_time_to_utc
from CTFd.utils.dates import unix_time_to_utc, isoformat
from CTFd.utils.decorators.visibility import ( from CTFd.utils.decorators.visibility import (
check_account_visibility, check_account_visibility,
check_score_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_namespace = Namespace(
"scoreboard", description="Endpoint to retrieve scores" "scoreboard", description="Endpoint to retrieve scores"

View File

@ -5,6 +5,6 @@ statistics_namespace = Namespace(
) )
from CTFd.api.v1.statistics import challenges # noqa: F401 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 teams # noqa: F401
from CTFd.api.v1.statistics import users # noqa: F401 from CTFd.api.v1.statistics import users # noqa: F401
from CTFd.api.v1.statistics import submissions # noqa: F401

View File

@ -1,11 +1,12 @@
from flask_restplus import Resource 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 import func
from sqlalchemy.sql import or_ 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/<column>") @statistics_namespace.route("/challenges/<column>")
class ChallengePropertyCounts(Resource): class ChallengePropertyCounts(Resource):

View File

@ -1,8 +1,9 @@
from flask_restplus import Resource from flask_restplus import Resource
from sqlalchemy import func
from CTFd.api.v1.statistics import statistics_namespace
from CTFd.models import Submissions from CTFd.models import Submissions
from CTFd.utils.decorators import admins_only from CTFd.utils.decorators import admins_only
from CTFd.api.v1.statistics import statistics_namespace
from sqlalchemy import func
@statistics_namespace.route("/submissions/<column>") @statistics_namespace.route("/submissions/<column>")

View File

@ -1,7 +1,8 @@
from flask_restplus import Resource from flask_restplus import Resource
from CTFd.api.v1.statistics import statistics_namespace
from CTFd.models import Teams from CTFd.models import Teams
from CTFd.utils.decorators import admins_only from CTFd.utils.decorators import admins_only
from CTFd.api.v1.statistics import statistics_namespace
@statistics_namespace.route("/teams") @statistics_namespace.route("/teams")

View File

@ -1,9 +1,10 @@
from flask_restplus import Resource 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 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") @statistics_namespace.route("/users")
class UserStatistics(Resource): class UserStatistics(Resource):

View File

@ -2,7 +2,7 @@ from flask import request
from flask_restplus import Namespace, Resource from flask_restplus import Namespace, Resource
from CTFd.cache import clear_standings 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.schemas.submissions import SubmissionSchema
from CTFd.utils.decorators import admins_only from CTFd.utils.decorators import admins_only

View File

@ -1,6 +1,7 @@
from flask import request from flask import request
from flask_restplus import Namespace, Resource 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.schemas.tags import TagSchema
from CTFd.utils.decorators import admins_only from CTFd.utils.decorators import admins_only

View File

@ -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 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.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 ( from CTFd.utils.decorators.visibility import (
check_account_visibility, check_account_visibility,
check_score_visibility, check_score_visibility,
) )
from CTFd.utils.user import get_current_team, is_admin 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") teams_namespace = Namespace("teams", description="Endpoint to retrieve Teams")

View File

@ -1,11 +1,13 @@
import datetime
from flask import request, session from flask import request, session
from flask_restplus import Namespace, Resource 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.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.security.auth import generate_user_token
from CTFd.utils.decorators import require_verified_emails, authed_only from CTFd.utils.user import get_current_user, is_admin
import datetime
tokens_namespace = Namespace("tokens", description="Endpoint to retrieve Tokens") tokens_namespace = Namespace("tokens", description="Endpoint to retrieve Tokens")

View File

@ -1,16 +1,17 @@
from flask import request from flask import request
from flask_restplus import Namespace, Resource from flask_restplus import Namespace, Resource
from CTFd.cache import clear_standings from CTFd.cache import clear_standings
from CTFd.models import db, get_class_by_tablename, Unlocks from CTFd.models import Unlocks, db, get_class_by_tablename
from CTFd.utils.user import get_current_user
from CTFd.schemas.unlocks import UnlockSchema
from CTFd.schemas.awards import AwardSchema from CTFd.schemas.awards import AwardSchema
from CTFd.schemas.unlocks import UnlockSchema
from CTFd.utils.decorators import ( from CTFd.utils.decorators import (
during_ctf_time_only,
require_verified_emails,
admins_only, admins_only,
authed_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") unlocks_namespace = Namespace("unlocks", description="Endpoint to retrieve Unlocks")

View File

@ -1,29 +1,28 @@
from flask import session, request, abort from flask import abort, request, session
from flask_restplus import Namespace, Resource from flask_restplus import Namespace, Resource
from CTFd.cache import clear_standings
from CTFd.models import ( from CTFd.models import (
db,
Users,
Solves,
Awards, Awards,
Notifications,
Solves,
Submissions,
Tracking, Tracking,
Unlocks, Unlocks,
Submissions, Users,
Notifications, db,
) )
from CTFd.utils.decorators import authed_only, admins_only, ratelimit from CTFd.schemas.awards import AwardSchema
from CTFd.cache import clear_standings from CTFd.schemas.submissions import SubmissionSchema
from CTFd.schemas.users import UserSchema
from CTFd.utils.config import get_mail_provider from CTFd.utils.config import get_mail_provider
from CTFd.utils.email import sendmail, user_created_notification from CTFd.utils.decorators import admins_only, authed_only, ratelimit
from CTFd.utils.user import get_current_user, is_admin
from CTFd.utils.decorators.visibility import ( from CTFd.utils.decorators.visibility import (
check_account_visibility, check_account_visibility,
check_score_visibility, check_score_visibility,
) )
from CTFd.utils.email import sendmail, user_created_notification
from CTFd.schemas.submissions import SubmissionSchema from CTFd.utils.user import get_current_user, is_admin
from CTFd.schemas.awards import AwardSchema
from CTFd.schemas.users import UserSchema
users_namespace = Namespace("users", description="Endpoint to retrieve Users") users_namespace = Namespace("users", description="Endpoint to retrieve Users")

View File

@ -1,33 +1,25 @@
from flask import ( import base64
current_app as app,
render_template,
request,
redirect,
url_for,
session,
Blueprint,
)
from itsdangerous.exc import BadTimeSignature, SignatureExpired, BadSignature
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.models import Teams, Users, db
from CTFd.utils.decorators import ratelimit from CTFd.utils import config, email, get_app_config, get_config
from CTFd.utils import user as current_user from CTFd.utils import user as current_user
from CTFd.utils import config, validators from CTFd.utils import 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.config import is_teams_mode from CTFd.utils.config import is_teams_mode
from CTFd.utils.config.visibility import registration_visible from CTFd.utils.config.visibility import registration_visible
from CTFd.utils.modes import TEAMS_MODE from CTFd.utils.crypto import verify_password
from CTFd.utils.security.signing import unserialize from CTFd.utils.decorators import ratelimit
from CTFd.utils.decorators.visibility import check_registration_visibility
from CTFd.utils.helpers import error_for, get_errors from CTFd.utils.helpers import error_for, get_errors
from CTFd.utils.logging import log
import base64 from CTFd.utils.modes import TEAMS_MODE
import requests from CTFd.utils.security.auth import login_user, logout_user
from CTFd.utils.security.signing import unserialize
auth = Blueprint("auth", __name__) auth = Blueprint("auth", __name__)
@ -61,6 +53,7 @@ def confirm(data=None):
name=user.name, name=user.name,
) )
db.session.commit() db.session.commit()
email.successful_registration_notification(user.email)
db.session.close() db.session.close()
if current_user.authed(): if current_user.authed():
return redirect(url_for("challenges.listing")) return redirect(url_for("challenges.listing"))
@ -251,12 +244,7 @@ def register():
if ( if (
config.can_send_mail() config.can_send_mail()
): # We want to notify the user that they have registered. ): # We want to notify the user that they have registered.
email.sendmail( email.successful_registration_notification(user.email)
request.form["email"],
"You've successfully registered for {}".format(
get_config("ctf_name")
),
)
log("registrations", "[{date}] {ip} - {name} registered with {email}") log("registrations", "[{date}] {ip} - {name} registered with {email}")
db.session.close() db.session.close()

View File

@ -1,12 +1,13 @@
from flask import render_template, Blueprint from flask import Blueprint, render_template
from CTFd.utils.decorators import (
during_ctf_time_only,
require_verified_emails,
require_team,
)
from CTFd.utils.decorators.visibility import check_challenge_visibility
from CTFd.utils import config, get_config from CTFd.utils import config, get_config
from CTFd.utils.dates import ctf_ended, ctf_paused, view_after_ctf 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 from CTFd.utils.helpers import get_errors, get_infos
challenges = Blueprint("challenges", __name__) challenges = Blueprint("challenges", __name__)

View File

@ -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 import get_app_config
from CTFd.utils.decorators import authed_only, ratelimit from CTFd.utils.decorators import authed_only, ratelimit

View File

@ -1,12 +1,14 @@
from flask_sqlalchemy import SQLAlchemy import datetime
import six
from flask_marshmallow import Marshmallow 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.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.crypto import hash_password
from CTFd.utils.humanize.numbers import ordinalize from CTFd.utils.humanize.numbers import ordinalize
from CTFd.cache import cache
import datetime
import six
db = SQLAlchemy() db = SQLAlchemy()
ma = Marshmallow() ma = Marshmallow()

View File

@ -1,19 +1,22 @@
import glob import glob
import importlib import importlib
import os import os
from collections import namedtuple 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.decorators import admins_only as admins_only_wrapper
from CTFd.utils.plugins import override_template as utils_override_template
from CTFd.utils.plugins import ( 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, register_admin_script as utils_register_admin_plugin_script,
)
from CTFd.utils.plugins import (
register_admin_stylesheet as utils_register_admin_plugin_stylesheet, 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"]) Menu = namedtuple("Menu", ["title", "route"])

View File

@ -1,18 +1,19 @@
from CTFd.plugins import register_plugin_assets_directory from flask import Blueprint
from CTFd.plugins.flags import get_flag_class
from CTFd.models import ( from CTFd.models import (
db, ChallengeFiles,
Solves, Challenges,
Fails, Fails,
Flags, Flags,
Challenges,
ChallengeFiles,
Tags,
Hints, 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 CTFd.utils.uploads import delete_file
from flask import Blueprint from CTFd.utils.user import get_ip
class BaseChallenge(object): class BaseChallenge(object):

View File

@ -1,22 +1,25 @@
from __future__ import division # Use floating point for math calculations 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 import math
from CTFd.plugins.flags import get_flag_class
from flask import Blueprint
from CTFd.models import ( from CTFd.models import (
db, ChallengeFiles,
Solves, Challenges,
Fails, Fails,
Flags, Flags,
Challenges,
ChallengeFiles,
Tags,
Hints, Hints,
Solves,
Tags,
db,
) )
from CTFd.utils.user import get_ip from CTFd.plugins import register_plugin_assets_directory
from CTFd.utils.uploads import delete_file from CTFd.plugins.challenges import CHALLENGE_CLASSES, BaseChallenge
from CTFd.plugins.flags import get_flag_class
from CTFd.utils.modes import get_model from CTFd.utils.modes import get_model
from flask import Blueprint from CTFd.utils.uploads import delete_file
import math from CTFd.utils.user import get_ip
class DynamicValueChallenge(BaseChallenge): class DynamicValueChallenge(BaseChallenge):

View File

@ -1,7 +1,7 @@
from CTFd.plugins import register_plugin_assets_directory
import re import re
from CTFd.plugins import register_plugin_assets_directory
class BaseFlag(object): class BaseFlag(object):
name = None name = None

View File

@ -1,4 +1,4 @@
from CTFd.models import ma, Awards from CTFd.models import Awards, ma
from CTFd.utils import string_types from CTFd.utils import string_types

View File

@ -1,4 +1,4 @@
from CTFd.models import ma, Challenges from CTFd.models import Challenges, ma
class ChallengeSchema(ma.ModelSchema): class ChallengeSchema(ma.ModelSchema):

View File

@ -1,4 +1,4 @@
from CTFd.models import ma, Configs from CTFd.models import Configs, ma
from CTFd.utils import string_types from CTFd.utils import string_types

View File

@ -1,4 +1,4 @@
from CTFd.models import ma, Files from CTFd.models import Files, ma
from CTFd.utils import string_types from CTFd.utils import string_types

View File

@ -1,4 +1,4 @@
from CTFd.models import ma, Flags from CTFd.models import Flags, ma
from CTFd.utils import string_types from CTFd.utils import string_types

View File

@ -1,4 +1,4 @@
from CTFd.models import ma, Hints from CTFd.models import Hints, ma
from CTFd.utils import string_types from CTFd.utils import string_types

View File

@ -1,4 +1,4 @@
from CTFd.models import ma, Notifications from CTFd.models import Notifications, ma
from CTFd.utils import string_types from CTFd.utils import string_types

View File

@ -1,5 +1,6 @@
from marshmallow import pre_load from marshmallow import pre_load
from CTFd.models import ma, Pages
from CTFd.models import Pages, ma
from CTFd.utils import string_types from CTFd.utils import string_types

View File

@ -1,6 +1,7 @@
from marshmallow import fields from marshmallow import fields
from CTFd.models import Submissions, ma
from CTFd.schemas.challenges import ChallengeSchema from CTFd.schemas.challenges import ChallengeSchema
from CTFd.models import ma, Submissions
from CTFd.utils import string_types from CTFd.utils import string_types

View File

@ -1,4 +1,4 @@
from CTFd.models import ma, Tags from CTFd.models import Tags, ma
from CTFd.utils import string_types from CTFd.utils import string_types

View File

@ -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 marshmallow_sqlalchemy import field_for
from CTFd.models import ma, Teams, Users
from CTFd.utils.validators import validate_country_code from CTFd.models import Teams, Users, ma
from CTFd.utils import get_config from CTFd.utils import get_config, string_types
from CTFd.utils.user import is_admin, get_current_team, get_current_user
from CTFd.utils.crypto import verify_password 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): class TeamSchema(ma.ModelSchema):

View File

@ -1,4 +1,4 @@
from CTFd.models import ma, Tokens from CTFd.models import Tokens, ma
from CTFd.utils import string_types from CTFd.utils import string_types

View File

@ -1,4 +1,4 @@
from CTFd.models import ma, Unlocks from CTFd.models import Unlocks, ma
from CTFd.utils import string_types from CTFd.utils import string_types

View File

@ -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 marshmallow_sqlalchemy import field_for
from CTFd.models import ma, Users
from CTFd.utils import get_config from CTFd.models import Users, ma
from CTFd.utils.validators import validate_country_code from CTFd.utils import get_config, string_types
from CTFd.utils.user import is_admin, get_current_user
from CTFd.utils.crypto import verify_password from CTFd.utils.crypto import verify_password
from CTFd.utils.email import check_email_is_whitelisted 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): class UserSchema(ma.ModelSchema):

View File

@ -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.cache import cache, make_cache_key
from CTFd.utils import config from CTFd.utils import config
from CTFd.utils.decorators.visibility import check_score_visibility from CTFd.utils.decorators.visibility import check_score_visibility
from CTFd.utils.scores import get_standings from CTFd.utils.scores import get_standings
scoreboard = Blueprint("scoreboard", __name__) scoreboard = Blueprint("scoreboard", __name__)

View File

@ -1,15 +1,16 @@
from flask import render_template, request, redirect, url_for, Blueprint from flask import Blueprint, redirect, render_template, request, url_for
from CTFd.models import db, Teams
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 import authed_only, ratelimit
from CTFd.utils.decorators.modes import require_team_mode 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 ( from CTFd.utils.decorators.visibility import (
check_account_visibility, check_account_visibility,
check_score_visibility, check_score_visibility,
) )
from CTFd.utils.helpers import get_errors, get_infos from CTFd.utils.helpers import get_errors, get_infos
from CTFd.utils.user import get_current_user
teams = Blueprint("teams", __name__) teams = Blueprint("teams", __name__)
@ -72,7 +73,9 @@ def join():
name=team.name, limit=team_size_limit 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 user.team_id = team.id
db.session.commit() db.session.commit()

View File

@ -118,8 +118,8 @@ export function editHint(event) {
const params = $(this).serializeJSON(true); const params = $(this).serializeJSON(true);
params["challenge"] = CHALLENGE_ID; params["challenge"] = CHALLENGE_ID;
const method = "POST"; let method = "POST";
const url = "/api/v1/hints"; let url = "/api/v1/hints";
if (params.id) { if (params.id) {
method = "PATCH"; method = "PATCH";
url = "/api/v1/hints/" + params.id; url = "/api/v1/hints/" + params.id;

View File

@ -32,7 +32,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n
/***/ (function(module, exports, __webpack_require__) { /***/ (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?");
/***/ }), /***/ }),

View File

@ -186,7 +186,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n
/***/ (function(module, exports, __webpack_require__) { /***/ (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?");
/***/ }), /***/ }),

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -106,12 +106,24 @@
<div class="form-group"> <div class="form-group">
<label> <label>
Stylesheet editor Theme Header
<small class="form-text text-muted"> <small class="form-text text-muted">
CSS is applied to all public facing pages if the theme used supports custom CSS. Theme headers are inserted just before the <code>&lt;/head&gt;</code> tag on all public facing pages.
Requires theme support.
</small> </small>
</label> </label>
<textarea class="form-control" id="css-editor" name="css" rows="7">{{ css }}</textarea> <textarea class="form-control" id="theme-header" name="theme_header" rows="7">{{ theme_header or "" }}</textarea>
</div>
<div class="form-group">
<label>
Theme Footer
<small class="form-text text-muted">
Theme footers are inserted just before the <code>&lt;/body&gt;</code> tag on all public facing pages.
Requires theme support.
</small>
</label>
<textarea class="form-control" id="theme-footer" name="theme_footer" rows="7">{{ theme_footer or "" }}</textarea>
</div> </div>
<button type="submit" class="btn btn-md btn-primary float-right">Update</button> <button type="submit" class="btn btn-md btn-primary float-right">Update</button>

View File

@ -1,6 +1,140 @@
<div role="tabpanel" class="tab-pane config-section" id="email"> <div role="tabpanel" class="tab-pane config-section" id="email">
<form method="POST" autocomplete="off" class="w-100"> <form method="POST" autocomplete="off" class="w-100">
<ul class="nav nav-tabs mb-3" role="tablist"> <h5>Email Content</h5>
<small class="form-text text-muted">
Customize CTFd emails with <a href="https://help.ctfd.io/configuration/emails/#email-content" target="_blank">predefined variables</a> and custom content
</small>
<ul class="nav nav-tabs mt-3" role="tablist">
<li class="nav-item active">
<a class="nav-link active" href="#confirmation-email-tab" role="tab" data-toggle="tab">
Confirmation
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#account-details-email-tab" role="tab" data-toggle="tab">Account Details</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#password-reset-email-tab" role="tab" data-toggle="tab">Password Reset</a>
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="confirmation-email-tab">
<div class="form-group">
<label class="pt-3">
Account Registration<br>
<small class="form-text text-muted">
Email sent to users after they've registered their account entirely
</small>
</label>
<div>
<label>
Subject
</label>
<input class="form-control" id='successful_registration_email_subject' name='successful_registration_email_subject' type='text'
value="{{ successful_registration_email_subject or ''}}">
<label>
Body
</label>
<textarea class="form-control" type="text" id="successful_registration_email_body" name="verification_email_body"
rows="5">{{ successful_registration_email_body or '' }}</textarea>
</div>
</div>
<div class="form-group">
<label class="pt-3">
Account Confirmation<br>
<small class="form-text text-muted">
Email sent to users to confirm their account
</small>
</label>
<div>
<label>
Subject
</label>
<input class="form-control" id='verification_email_subject' name='verification_email_subject' type='text' value="{{ verification_email_subject or ''}}">
<label>
Body
</label>
<textarea class="form-control" type="text" id="verification_email_body" name="verification_email_body"
rows="5">{{ verification_email_body or '' }}</textarea>
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="account-details-email-tab">
<div class="form-group">
<label class="pt-3">
New Account Details<br>
<small class="form-text text-muted">
Email sent to new users (created by an admin) with their initial account details
</small>
</label>
<div>
<label>
Subject
</label>
<input class="form-control" id='user_creation_email_subject' name='user_creation_email_subject' type='text'
value="{{ user_creation_email_subject or ''}}">
<label>
Body
</label>
<textarea class="form-control" type="text" id="user_creation_email_body" name="user_creation_email_body"
rows="5">{{ user_creation_email_body or '' }}</textarea>
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="password-reset-email-tab">
<div class="form-group">
<label class="pt-3">
Password Reset Request<br>
<small class="form-text text-muted">
Email sent whent a user requests a password reset
</small>
</label>
<div>
<label>
Subject
</label>
<input class="form-control" id='password_reset_subject' name='password_reset_subject' type='text'
value="{{ password_reset_subject or ''}}">
<label>
Body
</label>
<textarea class="form-control" type="text" id="password_reset_body" name="password_reset_body"
rows="5">{{ password_reset_body or '' }}</textarea>
</div>
</div>
<div class="form-group">
<label class="pt-3">
Password Reset Confirmation<br>
<small class="form-text text-muted">
Email sent whent a user successfully resets their password
</small>
</label>
<div>
<label>
Subject
</label>
<input class="form-control" id='password_change_alert_subject' name='password_change_alert_subject' type='text'
value="{{ password_change_alert_subject or ''}}">
<label>
Body
</label>
<textarea class="form-control" type="text" id="password_change_alert_body" name="password_change_alert_body"
rows="5">{{ password_change_alert_body or '' }}</textarea>
</div>
</div>
</div>
</div>
<hr class="my-5">
<h5>Email Server</h5>
<small class="form-text text-muted">
Change the email server used by CTFd to send email
</small>
<ul class="nav nav-tabs my-3" role="tablist">
<li class="nav-item active"> <li class="nav-item active">
<a class="nav-link active" href="#mailserver" role="tab" data-toggle="tab">Mail <a class="nav-link active" href="#mailserver" role="tab" data-toggle="tab">Mail
Server</a> Server</a>

View File

@ -4,10 +4,14 @@ import { API } from "./api";
import "./patch"; import "./patch";
import MarkdownIt from "markdown-it"; import MarkdownIt from "markdown-it";
import $ from "jquery"; import $ from "jquery";
import ezq from "./ezq";
const api = new API("/"); const api = new API("/");
const user = {}; const user = {};
const _internal = {}; const _internal = {};
const ui = {
ezq
};
const lib = { const lib = {
$, $,
markdown markdown
@ -47,6 +51,7 @@ const CTFd = {
config, config,
fetch, fetch,
user, user,
ui,
api, api,
lib, lib,
_internal, _internal,

View File

@ -109,4 +109,25 @@ $(() => {
} }
} }
}); });
$("#setup-form").submit(function(e) {
if ($("#newsletter-checkbox").prop("checked")) {
let email = $(e.target)
.find("input[name=email]")
.val();
$.ajax({
type: "POST",
url:
"https://ctfd.us15.list-manage.com/subscribe/post-json?u=6c7fa6feeced52775aec9d015&id=dd1484208e&c=?",
data: {
EMAIL: email,
subscribe: "Subscribe",
b_6c7fa6feeced52775aec9d015_dd1484208e: ""
},
dataType: "jsonp",
contentType: "application/json; charset=utf-8"
});
}
});
}); });

View File

@ -8,7 +8,7 @@
/***/ (function(module, exports, __webpack_require__) { /***/ (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?");
/***/ }), /***/ }),

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -162,7 +162,7 @@
/***/ (function(module, exports, __webpack_require__) { /***/ (function(module, exports, __webpack_require__) {
; ;
eval("\n\n__webpack_require__(/*! ./main */ \"./CTFd/themes/core/assets/js/pages/main.js\");\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _momentTimezone = _interopRequireDefault(__webpack_require__(/*! moment-timezone */ \"./node_modules/moment-timezone/index.js\"));\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! ../CTFd */ \"./CTFd/themes/core/assets/js/CTFd.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction switchTab(event) {\n event.preventDefault(); // Handle tab validation\n\n var valid_tab = true;\n (0, _jquery.default)(event.target).closest(\"[role=tabpanel]\").find(\"input,textarea\").each(function (i, e) {\n $e = (0, _jquery.default)(e);\n var status = e.checkValidity();\n\n if (status === false) {\n $e.removeClass(\"input-filled-valid\");\n $e.addClass(\"input-filled-invalid\");\n valid_tab = false;\n }\n });\n\n if (valid_tab == false) {\n return;\n }\n\n var href = (0, _jquery.default)(event.target).data(\"href\");\n (0, _jquery.default)(\".nav a[href=\\\"\".concat(href, \"\\\"]\")).tab(\"show\");\n}\n\nfunction processDateTime(datetime) {\n var date_picker = (0, _jquery.default)(\"#\".concat(datetime, \"-date\"));\n var time_picker = (0, _jquery.default)(\"#\".concat(datetime, \"-time\"));\n return function (event) {\n var unix_time = (0, _momentTimezone.default)(\"\".concat(date_picker.val(), \" \").concat(time_picker.val()), \"YYYY-MM-DD HH:mm\").utc().format(\"X\");\n (0, _jquery.default)(\"#\".concat(datetime, \"-preview\")).val(unix_time);\n };\n}\n\nfunction mlcSetup(event) {\n var params = {\n name: (0, _jquery.default)(\"#ctf_name\").val(),\n type: \"jeopardy\",\n description: (0, _jquery.default)(\"#ctf_description\").val(),\n user_mode: (0, _jquery.default)(\"#user_mode\").val(),\n event_url: window.location.origin + _CTFd.default.config.urlRoot,\n redirect_url: window.location.origin + _CTFd.default.config.urlRoot + \"/redirect\",\n integration_setup_url: window.location.origin + _CTFd.default.config.urlRoot + \"/setup/integrations\",\n start: (0, _jquery.default)(\"#start-preview\").val(),\n end: (0, _jquery.default)(\"#end-preview\").val(),\n platform: \"CTFd\",\n state: STATE\n };\n var ret = [];\n\n for (var p in params) {\n ret.push(encodeURIComponent(p) + \"=\" + encodeURIComponent(params[p]));\n }\n\n window.open(\"https://www.majorleaguecyber.org/events/new?\" + ret.join(\"&\"), \"_blank\");\n}\n\n(0, _jquery.default)(function () {\n (0, _jquery.default)(\".tab-next\").click(switchTab);\n (0, _jquery.default)(\"input\").on(\"keypress\", function (e) {\n // Hook Enter button\n if (e.keyCode == 13) {\n e.preventDefault();\n (0, _jquery.default)(e.target).closest(\".tab-pane\").find(\"button[data-href]\").click();\n }\n });\n (0, _jquery.default)(\"#integration-mlc\").click(mlcSetup);\n (0, _jquery.default)(\"#start-date,#start-time\").change(processDateTime(\"start\"));\n (0, _jquery.default)(\"#end-date,#end-time\").change(processDateTime(\"end\"));\n (0, _jquery.default)(\"#config-color-picker\").on(\"input\", function (e) {\n (0, _jquery.default)(\"#config-color-input\").val((0, _jquery.default)(this).val());\n });\n (0, _jquery.default)(\"#config-color-reset\").click(function () {\n (0, _jquery.default)(\"#config-color-input\").val(\"\");\n (0, _jquery.default)(\"#config-color-picker\").val(\"\");\n });\n window.addEventListener(\"storage\", function (event) {\n if (event.key == \"integrations\" && event.newValue) {\n var integration = JSON.parse(event.newValue);\n\n if (integration[\"name\"] == \"mlc\") {\n (0, _jquery.default)(\"#integration-mlc\").text(\"Already Configured\").attr(\"disabled\", true);\n window.focus();\n localStorage.removeItem(\"integrations\");\n }\n }\n });\n});\n\n//# sourceURL=webpack:///./CTFd/themes/core/assets/js/pages/setup.js?"); eval("\n\n__webpack_require__(/*! ./main */ \"./CTFd/themes/core/assets/js/pages/main.js\");\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _momentTimezone = _interopRequireDefault(__webpack_require__(/*! moment-timezone */ \"./node_modules/moment-timezone/index.js\"));\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! ../CTFd */ \"./CTFd/themes/core/assets/js/CTFd.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction switchTab(event) {\n event.preventDefault(); // Handle tab validation\n\n var valid_tab = true;\n (0, _jquery.default)(event.target).closest(\"[role=tabpanel]\").find(\"input,textarea\").each(function (i, e) {\n $e = (0, _jquery.default)(e);\n var status = e.checkValidity();\n\n if (status === false) {\n $e.removeClass(\"input-filled-valid\");\n $e.addClass(\"input-filled-invalid\");\n valid_tab = false;\n }\n });\n\n if (valid_tab == false) {\n return;\n }\n\n var href = (0, _jquery.default)(event.target).data(\"href\");\n (0, _jquery.default)(\".nav a[href=\\\"\".concat(href, \"\\\"]\")).tab(\"show\");\n}\n\nfunction processDateTime(datetime) {\n var date_picker = (0, _jquery.default)(\"#\".concat(datetime, \"-date\"));\n var time_picker = (0, _jquery.default)(\"#\".concat(datetime, \"-time\"));\n return function (event) {\n var unix_time = (0, _momentTimezone.default)(\"\".concat(date_picker.val(), \" \").concat(time_picker.val()), \"YYYY-MM-DD HH:mm\").utc().format(\"X\");\n (0, _jquery.default)(\"#\".concat(datetime, \"-preview\")).val(unix_time);\n };\n}\n\nfunction mlcSetup(event) {\n var params = {\n name: (0, _jquery.default)(\"#ctf_name\").val(),\n type: \"jeopardy\",\n description: (0, _jquery.default)(\"#ctf_description\").val(),\n user_mode: (0, _jquery.default)(\"#user_mode\").val(),\n event_url: window.location.origin + _CTFd.default.config.urlRoot,\n redirect_url: window.location.origin + _CTFd.default.config.urlRoot + \"/redirect\",\n integration_setup_url: window.location.origin + _CTFd.default.config.urlRoot + \"/setup/integrations\",\n start: (0, _jquery.default)(\"#start-preview\").val(),\n end: (0, _jquery.default)(\"#end-preview\").val(),\n platform: \"CTFd\",\n state: STATE\n };\n var ret = [];\n\n for (var p in params) {\n ret.push(encodeURIComponent(p) + \"=\" + encodeURIComponent(params[p]));\n }\n\n window.open(\"https://www.majorleaguecyber.org/events/new?\" + ret.join(\"&\"), \"_blank\");\n}\n\n(0, _jquery.default)(function () {\n (0, _jquery.default)(\".tab-next\").click(switchTab);\n (0, _jquery.default)(\"input\").on(\"keypress\", function (e) {\n // Hook Enter button\n if (e.keyCode == 13) {\n e.preventDefault();\n (0, _jquery.default)(e.target).closest(\".tab-pane\").find(\"button[data-href]\").click();\n }\n });\n (0, _jquery.default)(\"#integration-mlc\").click(mlcSetup);\n (0, _jquery.default)(\"#start-date,#start-time\").change(processDateTime(\"start\"));\n (0, _jquery.default)(\"#end-date,#end-time\").change(processDateTime(\"end\"));\n (0, _jquery.default)(\"#config-color-picker\").on(\"input\", function (e) {\n (0, _jquery.default)(\"#config-color-input\").val((0, _jquery.default)(this).val());\n });\n (0, _jquery.default)(\"#config-color-reset\").click(function () {\n (0, _jquery.default)(\"#config-color-input\").val(\"\");\n (0, _jquery.default)(\"#config-color-picker\").val(\"\");\n });\n window.addEventListener(\"storage\", function (event) {\n if (event.key == \"integrations\" && event.newValue) {\n var integration = JSON.parse(event.newValue);\n\n if (integration[\"name\"] == \"mlc\") {\n (0, _jquery.default)(\"#integration-mlc\").text(\"Already Configured\").attr(\"disabled\", true);\n window.focus();\n localStorage.removeItem(\"integrations\");\n }\n }\n });\n (0, _jquery.default)(\"#setup-form\").submit(function (e) {\n if ((0, _jquery.default)(\"#newsletter-checkbox\").prop(\"checked\")) {\n var email = (0, _jquery.default)(e.target).find(\"input[name=email]\").val();\n\n _jquery.default.ajax({\n type: \"POST\",\n url: \"https://ctfd.us15.list-manage.com/subscribe/post-json?u=6c7fa6feeced52775aec9d015&id=dd1484208e&c=?\",\n data: {\n EMAIL: email,\n subscribe: \"Subscribe\",\n b_6c7fa6feeced52775aec9d015_dd1484208e: \"\"\n },\n dataType: \"jsonp\",\n contentType: \"application/json; charset=utf-8\"\n });\n }\n });\n});\n\n//# sourceURL=webpack:///./CTFd/themes/core/assets/js/pages/setup.js?");
/***/ }) /***/ })

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -18,7 +18,6 @@
<link rel="stylesheet" type="text/css" href="{{ stylesheet }}"> <link rel="stylesheet" type="text/css" href="{{ stylesheet }}">
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<link rel="stylesheet" type="text/css" href="{{ url_for('views.custom_css') }}">
<script type="text/javascript"> <script type="text/javascript">
var init = { var init = {
'urlRoot': "{{ request.script_root }}", 'urlRoot': "{{ request.script_root }}",
@ -29,6 +28,7 @@
'end': {{ get_config("end") | tojson }}, 'end': {{ get_config("end") | tojson }},
} }
</script> </script>
{{ get_config('theme_header', '') | safe }}
</head> </head>
<body> <body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top"> <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
@ -210,5 +210,7 @@
<script defer src="{{ script }}"></script> <script defer src="{{ script }}"></script>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{{ get_config('theme_footer', '') | safe }}
</body> </body>
</html> </html>

View File

@ -21,7 +21,7 @@
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
<form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal"> <form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal" id="setup-form">
<ul class="nav nav-pills nav-fill mb-4"> <ul class="nav nav-pills nav-fill mb-4">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#general">General</a> <a class="nav-link active" data-toggle="pill" href="#general">General</a>
@ -111,6 +111,14 @@
</label> </label>
<input class="form-control" type="password" name="password" required/> <input class="form-control" type="password" name="password" required/>
</div> </div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" value="" id="newsletter-checkbox" checked>
Subscribe email address to the CTFd LLC Newsletter for news and updates
</label>
</div>
<div class="float-right"> <div class="float-right">
<button type="button" class="btn btn-primary btn-outlined tab-next" data-href="#style"> <button type="button" class="btn btn-primary btn-outlined tab-next" data-href="#style">
Next Next

View File

@ -1,13 +1,13 @@
from flask import request, render_template, Blueprint from flask import Blueprint, render_template, request
from CTFd.models import Users from CTFd.models import Users
from CTFd.utils.decorators import authed_only
from CTFd.utils import config from CTFd.utils import config
from CTFd.utils.user import get_current_user from CTFd.utils.decorators import authed_only
from CTFd.utils.decorators.visibility import ( from CTFd.utils.decorators.visibility import (
check_account_visibility, check_account_visibility,
check_score_visibility, check_score_visibility,
) )
from CTFd.utils.user import get_current_user
users = Blueprint("users", __name__) users = Blueprint("users", __name__)

View File

@ -1,6 +1,7 @@
import mistune import mistune
import six import six
from flask import current_app as app from flask import current_app as app
from CTFd.cache import cache from CTFd.cache import cache
if six.PY2: if six.PY2:
@ -59,4 +60,4 @@ def set_config(key, value):
return config return config
from CTFd.models import db, Configs # noqa: E402 from CTFd.models import Configs, db # noqa: E402

View File

@ -1,8 +1,10 @@
from flask import current_app as app
from CTFd.utils import get_config
from CTFd.utils.modes import USERS_MODE, TEAMS_MODE
import time
import os import os
import time
from flask import current_app as app
from CTFd.utils import get_config
from CTFd.utils.modes import TEAMS_MODE, USERS_MODE
def ctf_name(): def ctf_name():

View File

@ -1,7 +1,9 @@
from CTFd.utils import string_types
from passlib.hash import bcrypt_sha256
import hashlib import hashlib
from passlib.hash import bcrypt_sha256
from CTFd.utils import string_types
def hash_password(plaintext): def hash_password(plaintext):
return bcrypt_sha256.hash(str(plaintext)) return bcrypt_sha256.hash(str(plaintext))

View File

@ -1,8 +1,8 @@
from CTFd.utils import get_config
import datetime import datetime
import time import time
from CTFd.utils import get_config
def ctftime(): def ctftime():
""" Checks whether it's CTF time or not. """ """ Checks whether it's CTF time or not. """

View File

@ -1,12 +1,14 @@
from flask import request, redirect, url_for, abort, jsonify
from CTFd.utils import config, get_config
from CTFd.cache import cache
from CTFd.utils.dates import ctf_ended, ctf_started, ctftime, view_after_ctf
from CTFd.utils import user as current_user
from CTFd.utils.user import get_current_team, is_admin, authed
from CTFd.utils.modes import TEAMS_MODE
import functools import functools
from flask import abort, jsonify, redirect, request, url_for
from CTFd.cache import cache
from CTFd.utils import config, get_config
from CTFd.utils import user as current_user
from CTFd.utils.dates import ctf_ended, ctf_started, ctftime, view_after_ctf
from CTFd.utils.modes import TEAMS_MODE
from CTFd.utils.user import authed, get_current_team, is_admin
def during_ctf_time_only(f): def during_ctf_time_only(f):
""" """
@ -84,7 +86,10 @@ def authed_only(f):
if authed(): if authed():
return f(*args, **kwargs) return f(*args, **kwargs)
else: else:
if request.content_type == "application/json" or request.accept_mimetypes.best == "text/event-stream": if (
request.content_type == "application/json"
or request.accept_mimetypes.best == "text/event-stream"
):
abort(403) abort(403)
else: else:
return redirect(url_for("auth.login", next=request.full_path)) return redirect(url_for("auth.login", next=request.full_path))

View File

@ -1,8 +1,10 @@
from CTFd.utils.modes import TEAMS_MODE, USERS_MODE
from CTFd.utils import get_config
from flask import abort
import functools import functools
from flask import abort
from CTFd.utils import get_config
from CTFd.utils.modes import TEAMS_MODE, USERS_MODE
def require_team_mode(f): def require_team_mode(f):
@functools.wraps(f) @functools.wraps(f)

View File

@ -1,8 +1,10 @@
from flask import request, abort, redirect, url_for, render_template
from CTFd.utils import get_config
from CTFd.utils.user import is_admin, authed
import functools import functools
from flask import abort, redirect, render_template, request, url_for
from CTFd.utils import get_config
from CTFd.utils.user import authed, is_admin
def check_score_visibility(f): def check_score_visibility(f):
@functools.wraps(f) @functools.wraps(f)

View File

@ -1,10 +1,38 @@
from flask import url_for from flask import url_for
from CTFd.utils import get_config from CTFd.utils import get_config
from CTFd.utils.formatters import safe_format
from CTFd.utils.config import get_mail_provider from CTFd.utils.config import get_mail_provider
from CTFd.utils.email import mailgun, smtp from CTFd.utils.email import mailgun, smtp
from CTFd.utils.formatters import safe_format
from CTFd.utils.security.signing import serialize from CTFd.utils.security.signing import serialize
DEFAULT_VERIFICATION_EMAIL_SUBJECT = "Confirm your account for {ctf_name}"
DEFAULT_VERIFICATION_EMAIL_BODY = (
"Please click the following link to confirm your email "
"address for {ctf_name}: {url}"
)
DEFAULT_SUCCESSFUL_REGISTRATION_EMAIL_SUBJECT = "Successfully registered for {ctf_name}"
DEFAULT_SUCCESSFUL_REGISTRATION_EMAIL_BODY = (
"You've successfully registered for {ctf_name}!"
)
DEFAULT_USER_CREATION_EMAIL_SUBJECT = "Message from {ctf_name}"
DEFAULT_USER_CREATION_EMAIL_BODY = (
"An account has been created for you for {ctf_name} at {url}. \n\n"
"Username: {name}\n"
"Password: {password}"
)
DEFAULT_PASSWORD_RESET_SUBJECT = "Password Reset Request from {ctf_name}"
DEFAULT_PASSWORD_RESET_BODY = (
"Did you initiate a password reset? "
"If you didn't initiate this request you can ignore this email. \n\n"
"Click the following link to reset your password:\n{url}"
)
DEFAULT_PASSWORD_CHANGE_ALERT_SUBJECT = "Password Change Confirmation for {ctf_name}"
DEFAULT_PASSWORD_CHANGE_ALERT_BODY = (
"Your password for {ctf_name} has been changed.\n\n"
"If you didn't request a password change you can reset your password here: {url}"
)
def sendmail(addr, text, subject="Message from {ctf_name}"): def sendmail(addr, text, subject="Message from {ctf_name}"):
subject = safe_format(subject, ctf_name=get_config("ctf_name")) subject = safe_format(subject, ctf_name=get_config("ctf_name"))
@ -17,48 +45,84 @@ def sendmail(addr, text, subject="Message from {ctf_name}"):
def password_change_alert(email): def password_change_alert(email):
ctf_name = get_config("ctf_name") text = safe_format(
text = ( get_config("password_change_alert_body") or DEFAULT_PASSWORD_CHANGE_ALERT_BODY,
"Your password for {ctf_name} has been changed.\n\n" ctf_name=get_config("ctf_name"),
"If you didn't request a password change you can reset your password here: {url}" ctf_description=get_config("ctf_description"),
).format(ctf_name=ctf_name, url=url_for("auth.reset_password", _external=True)) url=url_for("auth.reset_password", _external=True),
)
subject = "Password Change Confirmation for {ctf_name}".format(ctf_name=ctf_name) subject = safe_format(
get_config("password_change_alert_subject")
or DEFAULT_PASSWORD_CHANGE_ALERT_SUBJECT,
ctf_name=get_config("ctf_name"),
)
return sendmail(addr=email, text=text, subject=subject) return sendmail(addr=email, text=text, subject=subject)
def forgot_password(email): def forgot_password(email):
token = serialize(email) text = safe_format(
text = """Did you initiate a password reset? If you didn't initiate this request you can ignore this email. get_config("password_reset_body") or DEFAULT_PASSWORD_RESET_BODY,
ctf_name=get_config("ctf_name"),
Click the following link to reset your password: ctf_description=get_config("ctf_description"),
{0}/{1} url=url_for("auth.reset_password", data=serialize(email), _external=True),
""".format( )
url_for("auth.reset_password", _external=True), token
subject = safe_format(
get_config("password_reset_subject") or DEFAULT_PASSWORD_RESET_SUBJECT,
ctf_name=get_config("ctf_name"),
) )
subject = "Password Reset Request from {ctf_name}"
return sendmail(addr=email, text=text, subject=subject) return sendmail(addr=email, text=text, subject=subject)
def verify_email_address(addr): def verify_email_address(addr):
token = serialize(addr) text = safe_format(
text = """Please click the following link to confirm your email address for {ctf_name}: {url}/{token}""".format( get_config("verification_email_body") or DEFAULT_VERIFICATION_EMAIL_BODY,
ctf_name=get_config("ctf_name"),
ctf_description=get_config("ctf_description"),
url=url_for("auth.confirm", data=serialize(addr), _external=True),
)
subject = safe_format(
get_config("verification_email_subject") or DEFAULT_VERIFICATION_EMAIL_SUBJECT,
ctf_name=get_config("ctf_name"),
)
return sendmail(addr=addr, text=text, subject=subject)
def successful_registration_notification(addr):
text = safe_format(
get_config("successful_registration_email_body")
or DEFAULT_SUCCESSFUL_REGISTRATION_EMAIL_BODY,
ctf_name=get_config("ctf_name"),
ctf_description=get_config("ctf_description"),
url=url_for("views.static_html", _external=True),
)
subject = safe_format(
get_config("successful_registration_email_subject")
or DEFAULT_SUCCESSFUL_REGISTRATION_EMAIL_SUBJECT,
ctf_name=get_config("ctf_name"), ctf_name=get_config("ctf_name"),
url=url_for("auth.confirm", _external=True),
token=token,
) )
subject = "Confirm your account for {ctf_name}"
return sendmail(addr=addr, text=text, subject=subject) return sendmail(addr=addr, text=text, subject=subject)
def user_created_notification(addr, name, password): def user_created_notification(addr, name, password):
text = """An account has been created for you for {ctf_name} at {url}. \n\nUsername: {name}\nPassword: {password}""".format( text = safe_format(
get_config("user_creation_email_body") or DEFAULT_USER_CREATION_EMAIL_BODY,
ctf_name=get_config("ctf_name"), ctf_name=get_config("ctf_name"),
ctf_description=get_config("ctf_description"),
url=url_for("views.static_html", _external=True), url=url_for("views.static_html", _external=True),
name=name, name=name,
password=password, password=password,
) )
return sendmail(addr, text)
subject = safe_format(
get_config("user_creation_email_subject")
or DEFAULT_USER_CREATION_EMAIL_SUBJECT,
ctf_name=get_config("ctf_name"),
)
return sendmail(addr=addr, text=text, subject=subject)
def check_email_is_whitelisted(email_address): def check_email_is_whitelisted(email_address):

View File

@ -1,6 +1,7 @@
from CTFd.utils import get_config, get_app_config
import requests import requests
from CTFd.utils import get_app_config, get_config
def sendmail(addr, text, subject): def sendmail(addr, text, subject):
ctf_name = get_config("ctf_name") ctf_name = get_config("ctf_name")

View File

@ -1,7 +1,8 @@
from CTFd.utils import get_config, get_app_config import smtplib
from email.mime.text import MIMEText from email.mime.text import MIMEText
from socket import timeout from socket import timeout
import smtplib
from CTFd.utils import get_app_config, get_config
def get_smtp(host, port, username=None, password=None, TLS=None, SSL=None, auth=None): def get_smtp(host, port, username=None, password=None, TLS=None, SSL=None, auth=None):

View File

@ -1,8 +1,9 @@
from CTFd.utils import string_types import base64
import codecs
import six import six
import codecs
import base64 from CTFd.utils import string_types
def hexencode(s): def hexencode(s):

Some files were not shown because too many files have changed in this diff Show More