mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-17 05:54:19 +01:00
Bump Dependencies (#2332)
* Bump dependencies * Closes #2300 * Closes #2331
This commit is contained in:
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
python -m pip install -r development.txt
|
python -m pip install -r linting.txt
|
||||||
sudo yarn install --non-interactive
|
sudo yarn install --non-interactive
|
||||||
sudo yarn global add prettier@1.17.0
|
sudo yarn global add prettier@1.17.0
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from flask_migrate import upgrade
|
|||||||
from jinja2 import FileSystemLoader
|
from jinja2 import FileSystemLoader
|
||||||
from jinja2.sandbox import SandboxedEnvironment
|
from jinja2.sandbox import SandboxedEnvironment
|
||||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||||
from werkzeug.utils import cached_property
|
|
||||||
|
|
||||||
import CTFd.utils.config
|
import CTFd.utils.config
|
||||||
from CTFd import utils
|
from CTFd import utils
|
||||||
@@ -36,16 +35,14 @@ __channel__ = "oss"
|
|||||||
|
|
||||||
|
|
||||||
class CTFdRequest(Request):
|
class CTFdRequest(Request):
|
||||||
@cached_property
|
def __init__(self, *args, **kwargs):
|
||||||
def path(self):
|
super().__init__(*args, **kwargs)
|
||||||
"""
|
"""
|
||||||
Hijack the original Flask request path because it does not account for subdirectory deployments in an intuitive
|
Hijack the original Flask request path because it does not account for subdirectory deployments in an intuitive
|
||||||
manner. We append script_root so that the path always points to the full path as seen in the browser.
|
manner. We append script_root so that the path always points to the full path as seen in the browser.
|
||||||
e.g. /subdirectory/path/route vs /path/route
|
e.g. /subdirectory/path/route vs /path/route
|
||||||
|
|
||||||
:return: string
|
|
||||||
"""
|
"""
|
||||||
return self.script_root + super(CTFdRequest, self).path
|
self.path = self.script_root + self.path
|
||||||
|
|
||||||
|
|
||||||
class CTFdFlask(Flask):
|
class CTFdFlask(Flask):
|
||||||
@@ -86,24 +83,30 @@ class SandboxedBaseEnvironment(SandboxedEnvironment):
|
|||||||
theme = str(utils.get_config("ctf_theme"))
|
theme = str(utils.get_config("ctf_theme"))
|
||||||
cache_name = theme + "/" + name
|
cache_name = theme + "/" + name
|
||||||
|
|
||||||
# Rest of this code is copied from Jinja
|
# Rest of this code roughly copied from Jinja
|
||||||
# https://github.com/pallets/jinja/blob/master/src/jinja2/environment.py#L802-L815
|
# https://github.com/pallets/jinja/blob/b08cd4bc64bb980df86ed2876978ae5735572280/src/jinja2/environment.py#L956-L973
|
||||||
cache_key = (weakref.ref(self.loader), cache_name)
|
cache_key = (weakref.ref(self.loader), cache_name)
|
||||||
if self.cache is not None:
|
if self.cache is not None:
|
||||||
template = self.cache.get(cache_key)
|
template = self.cache.get(cache_key)
|
||||||
if template is not None and (
|
if template is not None and (
|
||||||
not self.auto_reload or template.is_up_to_date
|
not self.auto_reload or template.is_up_to_date
|
||||||
):
|
):
|
||||||
|
# template.globals is a ChainMap, modifying it will only
|
||||||
|
# affect the template, not the environment globals.
|
||||||
|
if globals:
|
||||||
|
template.globals.update(globals)
|
||||||
|
|
||||||
return template
|
return template
|
||||||
template = self.loader.load(self, name, globals)
|
|
||||||
|
template = self.loader.load(self, name, self.make_globals(globals))
|
||||||
|
|
||||||
if self.cache is not None:
|
if self.cache is not None:
|
||||||
self.cache[cache_key] = template
|
self.cache[cache_key] = template
|
||||||
return template
|
return template
|
||||||
|
|
||||||
|
|
||||||
class ThemeLoader(FileSystemLoader):
|
class ThemeLoader(FileSystemLoader):
|
||||||
"""Custom FileSystemLoader that is aware of theme structure and config.
|
"""Custom FileSystemLoader that is aware of theme structure and config."""
|
||||||
"""
|
|
||||||
|
|
||||||
DEFAULT_THEMES_PATH = os.path.join(os.path.dirname(__file__), "themes")
|
DEFAULT_THEMES_PATH = os.path.join(os.path.dirname(__file__), "themes")
|
||||||
_ADMIN_THEME_PREFIX = ADMIN_THEME + "/"
|
_ADMIN_THEME_PREFIX = ADMIN_THEME + "/"
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ def export_ctf():
|
|||||||
backup = export_ctf_util()
|
backup = export_ctf_util()
|
||||||
ctf_name = ctf_config.ctf_name()
|
ctf_name = ctf_config.ctf_name()
|
||||||
day = datetime.datetime.now().strftime("%Y-%m-%d_%T")
|
day = datetime.datetime.now().strftime("%Y-%m-%d_%T")
|
||||||
full_name = u"{}.{}.zip".format(ctf_name, day)
|
full_name = "{}.{}.zip".format(ctf_name, day)
|
||||||
return send_file(
|
return send_file(
|
||||||
backup, cache_timeout=-1, as_attachment=True, attachment_filename=full_name
|
backup, cache_timeout=-1, as_attachment=True, attachment_filename=full_name
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -31,7 +31,5 @@ def sqlalchemy_to_pydantic(
|
|||||||
for name, python_type in include.items():
|
for name, python_type in include.items():
|
||||||
default = None
|
default = None
|
||||||
fields[name] = (python_type, default)
|
fields[name] = (python_type, default)
|
||||||
pydantic_model = create_model(
|
pydantic_model = create_model(db_model.__name__, **fields) # type: ignore
|
||||||
db_model.__name__, **fields # type: ignore
|
|
||||||
)
|
|
||||||
return pydantic_model
|
return pydantic_model
|
||||||
|
|||||||
@@ -51,7 +51,10 @@ class TopicList(Resource):
|
|||||||
{
|
{
|
||||||
"value": (str, None),
|
"value": (str, None),
|
||||||
"q": (str, None),
|
"q": (str, None),
|
||||||
"field": (RawEnum("TopicFields", {"value": "value"}), None,),
|
"field": (
|
||||||
|
RawEnum("TopicFields", {"value": "value"}),
|
||||||
|
None,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
location="query",
|
location="query",
|
||||||
)
|
)
|
||||||
@@ -122,7 +125,8 @@ class TopicList(Resource):
|
|||||||
responses={200: ("Success", "APISimpleSuccessResponse")},
|
responses={200: ("Success", "APISimpleSuccessResponse")},
|
||||||
)
|
)
|
||||||
@validate_args(
|
@validate_args(
|
||||||
{"type": (str, None), "target_id": (int, 0)}, location="query",
|
{"type": (str, None), "target_id": (int, 0)},
|
||||||
|
location="query",
|
||||||
)
|
)
|
||||||
def delete(self, query_args):
|
def delete(self, query_args):
|
||||||
topic_type = query_args.get("type")
|
topic_type = query_args.get("type")
|
||||||
|
|||||||
@@ -208,9 +208,11 @@ def register():
|
|||||||
registration_code = str(request.form.get("registration_code", ""))
|
registration_code = str(request.form.get("registration_code", ""))
|
||||||
|
|
||||||
name_len = len(name) == 0
|
name_len = len(name) == 0
|
||||||
names = Users.query.add_columns("name", "id").filter_by(name=name).first()
|
names = (
|
||||||
|
Users.query.add_columns(Users.name, Users.id).filter_by(name=name).first()
|
||||||
|
)
|
||||||
emails = (
|
emails = (
|
||||||
Users.query.add_columns("email", "id")
|
Users.query.add_columns(Users.email, Users.id)
|
||||||
.filter_by(email=email_address)
|
.filter_by(email=email_address)
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
|||||||
2
CTFd/cache/__init__.py
vendored
2
CTFd/cache/__init__.py
vendored
@@ -21,7 +21,7 @@ def timed_lru_cache(timeout: int = 300, maxsize: int = 64, typed: bool = False):
|
|||||||
|
|
||||||
def wrapper_cache(func):
|
def wrapper_cache(func):
|
||||||
func = lru_cache(maxsize=maxsize, typed=typed)(func)
|
func = lru_cache(maxsize=maxsize, typed=typed)(func)
|
||||||
func.delta = timeout * 10 ** 9
|
func.delta = timeout * 10**9
|
||||||
func.expiration = monotonic_ns() + func.delta
|
func.expiration = monotonic_ns() + func.delta
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ def render_error(error):
|
|||||||
try:
|
try:
|
||||||
return (
|
return (
|
||||||
render_template(
|
render_template(
|
||||||
"errors/{}.html".format(error.code), error=error.description,
|
"errors/{}.html".format(error.code),
|
||||||
|
error=error.description,
|
||||||
),
|
),
|
||||||
error.code,
|
error.code,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ class AccountSettingsForm(BaseForm):
|
|||||||
description="Max number of teams (Teams mode only)",
|
description="Max number of teams (Teams mode only)",
|
||||||
)
|
)
|
||||||
num_users = IntegerField(
|
num_users = IntegerField(
|
||||||
widget=NumberInput(min=0), description="Max number of users",
|
widget=NumberInput(min=0),
|
||||||
|
description="Max number of users",
|
||||||
)
|
)
|
||||||
verify_emails = SelectField(
|
verify_emails = SelectField(
|
||||||
"Verify Emails",
|
"Verify Emails",
|
||||||
@@ -101,13 +102,15 @@ class LegalSettingsForm(BaseForm):
|
|||||||
description="External URL to a Terms of Service document hosted elsewhere",
|
description="External URL to a Terms of Service document hosted elsewhere",
|
||||||
)
|
)
|
||||||
tos_text = TextAreaField(
|
tos_text = TextAreaField(
|
||||||
"Terms of Service", description="Text shown on the Terms of Service page",
|
"Terms of Service",
|
||||||
|
description="Text shown on the Terms of Service page",
|
||||||
)
|
)
|
||||||
privacy_url = URLField(
|
privacy_url = URLField(
|
||||||
"Privacy Policy URL",
|
"Privacy Policy URL",
|
||||||
description="External URL to a Privacy Policy document hosted elsewhere",
|
description="External URL to a Privacy Policy document hosted elsewhere",
|
||||||
)
|
)
|
||||||
privacy_text = TextAreaField(
|
privacy_text = TextAreaField(
|
||||||
"Privacy Policy", description="Text shown on the Privacy Policy page",
|
"Privacy Policy",
|
||||||
|
description="Text shown on the Privacy Policy page",
|
||||||
)
|
)
|
||||||
submit = SubmitField("Update")
|
submit = SubmitField("Update")
|
||||||
|
|||||||
@@ -15,12 +15,13 @@ ma = Marshmallow()
|
|||||||
|
|
||||||
def get_class_by_tablename(tablename):
|
def get_class_by_tablename(tablename):
|
||||||
"""Return class reference mapped to table.
|
"""Return class reference mapped to table.
|
||||||
https://stackoverflow.com/a/23754464
|
https://stackoverflow.com/a/66666783
|
||||||
|
|
||||||
:param tablename: String with name of table.
|
:param tablename: String with name of table.
|
||||||
:return: Class reference or None.
|
:return: Class reference or None.
|
||||||
"""
|
"""
|
||||||
for c in db.Model._decl_class_registry.values():
|
for m in db.Model.registry.mappers:
|
||||||
|
c = m.class_
|
||||||
if hasattr(c, "__tablename__") and c.__tablename__ == tablename:
|
if hasattr(c, "__tablename__") and c.__tablename__ == tablename:
|
||||||
return c
|
return c
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -25,11 +25,13 @@ class DynamicChallenge(Challenges):
|
|||||||
class DynamicValueChallenge(BaseChallenge):
|
class DynamicValueChallenge(BaseChallenge):
|
||||||
id = "dynamic" # Unique identifier used to register challenges
|
id = "dynamic" # Unique identifier used to register challenges
|
||||||
name = "dynamic" # Name of a challenge type
|
name = "dynamic" # Name of a challenge type
|
||||||
templates = { # Handlebars templates used for each aspect of challenge editing & viewing
|
templates = (
|
||||||
|
{ # Handlebars templates used for each aspect of challenge editing & viewing
|
||||||
"create": "/plugins/dynamic_challenges/assets/create.html",
|
"create": "/plugins/dynamic_challenges/assets/create.html",
|
||||||
"update": "/plugins/dynamic_challenges/assets/update.html",
|
"update": "/plugins/dynamic_challenges/assets/update.html",
|
||||||
"view": "/plugins/dynamic_challenges/assets/view.html",
|
"view": "/plugins/dynamic_challenges/assets/view.html",
|
||||||
}
|
}
|
||||||
|
)
|
||||||
scripts = { # Scripts that are loaded when a template is loaded
|
scripts = { # Scripts that are loaded when a template is loaded
|
||||||
"create": "/plugins/dynamic_challenges/assets/create.js",
|
"create": "/plugins/dynamic_challenges/assets/create.js",
|
||||||
"update": "/plugins/dynamic_challenges/assets/update.js",
|
"update": "/plugins/dynamic_challenges/assets/update.js",
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ def logarithmic(challenge):
|
|||||||
# It is important that this calculation takes into account floats.
|
# It is important that this calculation takes into account floats.
|
||||||
# Hence this file uses from __future__ import division
|
# Hence this file uses from __future__ import division
|
||||||
value = (
|
value = (
|
||||||
((challenge.minimum - challenge.initial) / (challenge.decay ** 2))
|
((challenge.minimum - challenge.initial) / (challenge.decay**2))
|
||||||
* (solve_count ** 2)
|
* (solve_count**2)
|
||||||
) + challenge.initial
|
) + challenge.initial
|
||||||
|
|
||||||
value = math.ceil(value)
|
value = math.ceil(value)
|
||||||
|
|||||||
@@ -68,5 +68,7 @@ class ChallengeSchema(ma.ModelSchema):
|
|||||||
)
|
)
|
||||||
|
|
||||||
requirements = field_for(
|
requirements = field_for(
|
||||||
Challenges, "requirements", validate=[ChallengeRequirementsValidator()],
|
Challenges,
|
||||||
|
"requirements",
|
||||||
|
validate=[ChallengeRequirementsValidator()],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ class PageSchema(ma.ModelSchema):
|
|||||||
"title",
|
"title",
|
||||||
validate=[
|
validate=[
|
||||||
validate.Length(
|
validate.Length(
|
||||||
min=0, max=80, error="Page could not be saved. Your title is too long.",
|
min=0,
|
||||||
|
max=80,
|
||||||
|
error="Page could not be saved. Your title is too long.",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -111,10 +111,14 @@ def get_solve_counts_for_challenges(challenge_id=None, admin=False):
|
|||||||
else:
|
else:
|
||||||
freeze_cond = true()
|
freeze_cond = true()
|
||||||
exclude_solves_cond = and_(
|
exclude_solves_cond = and_(
|
||||||
AccountModel.banned == false(), AccountModel.hidden == false(),
|
AccountModel.banned == false(),
|
||||||
|
AccountModel.hidden == false(),
|
||||||
)
|
)
|
||||||
solves_q = (
|
solves_q = (
|
||||||
db.session.query(Solves.challenge_id, sa_func.count(Solves.challenge_id),)
|
db.session.query(
|
||||||
|
Solves.challenge_id,
|
||||||
|
sa_func.count(Solves.challenge_id),
|
||||||
|
)
|
||||||
.join(AccountModel)
|
.join(AccountModel)
|
||||||
.filter(*challenge_id_filter, freeze_cond, exclude_solves_cond)
|
.filter(*challenge_id_filter, freeze_cond, exclude_solves_cond)
|
||||||
.group_by(Solves.challenge_id)
|
.group_by(Solves.challenge_id)
|
||||||
|
|||||||
@@ -368,21 +368,31 @@ def load_challenges_csv(dict_reader):
|
|||||||
if flags:
|
if flags:
|
||||||
flags = [flag.strip() for flag in flags.split(",")]
|
flags = [flag.strip() for flag in flags.split(",")]
|
||||||
for flag in flags:
|
for flag in flags:
|
||||||
f = Flags(type="static", challenge_id=challenge.id, content=flag,)
|
f = Flags(
|
||||||
|
type="static",
|
||||||
|
challenge_id=challenge.id,
|
||||||
|
content=flag,
|
||||||
|
)
|
||||||
db.session.add(f)
|
db.session.add(f)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
if tags:
|
if tags:
|
||||||
tags = [tag.strip() for tag in tags.split(",")]
|
tags = [tag.strip() for tag in tags.split(",")]
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
t = Tags(challenge_id=challenge.id, value=tag,)
|
t = Tags(
|
||||||
|
challenge_id=challenge.id,
|
||||||
|
value=tag,
|
||||||
|
)
|
||||||
db.session.add(t)
|
db.session.add(t)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
if hints:
|
if hints:
|
||||||
hints = [hint.strip() for hint in hints.split(",")]
|
hints = [hint.strip() for hint in hints.split(",")]
|
||||||
for hint in hints:
|
for hint in hints:
|
||||||
h = Hints(challenge_id=challenge.id, content=hint,)
|
h = Hints(
|
||||||
|
challenge_id=challenge.id,
|
||||||
|
content=hint,
|
||||||
|
)
|
||||||
db.session.add(h)
|
db.session.add(h)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
if errors:
|
if errors:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ 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."""
|
||||||
|
|
||||||
start = get_config("start")
|
start = get_config("start")
|
||||||
end = get_config("end")
|
end = get_config("end")
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ def export_ctf():
|
|||||||
|
|
||||||
backup_zip.close()
|
backup_zip.close()
|
||||||
backup.seek(0)
|
backup.seek(0)
|
||||||
|
db.close()
|
||||||
return backup
|
return backup
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ migrations = Migrate()
|
|||||||
def create_database():
|
def create_database():
|
||||||
url = make_url(app.config["SQLALCHEMY_DATABASE_URI"])
|
url = make_url(app.config["SQLALCHEMY_DATABASE_URI"])
|
||||||
if url.drivername == "postgres":
|
if url.drivername == "postgres":
|
||||||
url.drivername = "postgresql"
|
url = url.set(drivername="postgresql")
|
||||||
|
|
||||||
if url.drivername.startswith("mysql"):
|
if url.drivername.startswith("mysql"):
|
||||||
url.query["charset"] = "utf8mb4"
|
url = url.update_query_dict({"charset": "utf8mb4"})
|
||||||
|
|
||||||
# Creates database if the database database does not exist
|
# Creates database if the database database does not exist
|
||||||
if not database_exists_util(url):
|
if not database_exists_util(url):
|
||||||
@@ -34,7 +34,7 @@ def create_database():
|
|||||||
def drop_database():
|
def drop_database():
|
||||||
url = make_url(app.config["SQLALCHEMY_DATABASE_URI"])
|
url = make_url(app.config["SQLALCHEMY_DATABASE_URI"])
|
||||||
if url.drivername == "postgres":
|
if url.drivername == "postgres":
|
||||||
url.drivername = "postgresql"
|
url = url.set(drivername="postgresql")
|
||||||
drop_database_util(url)
|
drop_database_util(url)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ def is_verified():
|
|||||||
|
|
||||||
|
|
||||||
def get_ip(req=None):
|
def get_ip(req=None):
|
||||||
""" Returns the IP address of the currently in scope request. The approach is to define a list of trusted proxies
|
"""Returns the IP address of the currently in scope request. The approach is to define a list of trusted proxies
|
||||||
(in this case the local network), and only trust the most recently defined untrusted IP address.
|
(in this case the local network), and only trust the most recently defined untrusted IP address.
|
||||||
Taken from http://stackoverflow.com/a/22936947/4285524 but the generator there makes no sense.
|
Taken from http://stackoverflow.com/a/22936947/4285524 but the generator there makes no sense.
|
||||||
The trusted_proxies regexes is taken from Ruby on Rails.
|
The trusted_proxies regexes is taken from Ruby on Rails.
|
||||||
|
|||||||
@@ -124,9 +124,15 @@ def setup():
|
|||||||
password = request.form["password"]
|
password = request.form["password"]
|
||||||
|
|
||||||
name_len = len(name) == 0
|
name_len = len(name) == 0
|
||||||
names = Users.query.add_columns("name", "id").filter_by(name=name).first()
|
names = (
|
||||||
|
Users.query.add_columns(Users.name, Users.id)
|
||||||
|
.filter_by(name=name)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
emails = (
|
emails = (
|
||||||
Users.query.add_columns("email", "id").filter_by(email=email).first()
|
Users.query.add_columns(Users.email, Users.id)
|
||||||
|
.filter_by(email=email)
|
||||||
|
.first()
|
||||||
)
|
)
|
||||||
pass_short = len(password) == 0
|
pass_short = len(password) == 0
|
||||||
pass_long = len(password) > 128
|
pass_long = len(password) > 128
|
||||||
|
|||||||
@@ -3,17 +3,14 @@ pip-tools==5.4.0
|
|||||||
pytest==7.3.1
|
pytest==7.3.1
|
||||||
pytest-randomly==3.12.0
|
pytest-randomly==3.12.0
|
||||||
coverage==7.2.3
|
coverage==7.2.3
|
||||||
ruff==0.0.260
|
|
||||||
psycopg2-binary==2.9.6
|
psycopg2-binary==2.9.6
|
||||||
moto==1.3.16
|
moto==4.1.11
|
||||||
bandit==1.6.2
|
bandit==1.6.2
|
||||||
flask_profiler==1.8.1
|
flask_profiler==1.8.1
|
||||||
pytest-xdist==3.2.1
|
pytest-xdist==3.2.1
|
||||||
pytest-cov==4.0.0
|
pytest-cov==4.0.0
|
||||||
sphinx_rtd_theme==0.4.3
|
sphinx_rtd_theme==0.4.3
|
||||||
flask-debugtoolbar==0.11.0
|
flask-debugtoolbar==0.11.0
|
||||||
isort==4.3.21
|
|
||||||
Faker==4.1.0
|
Faker==4.1.0
|
||||||
pipdeptree==2.2.0
|
pipdeptree==2.2.0
|
||||||
black==19.10b0
|
|
||||||
pytest-sugar==0.9.7
|
pytest-sugar==0.9.7
|
||||||
|
|||||||
3
linting.txt
Normal file
3
linting.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
black==22.3.0
|
||||||
|
isort==4.3.21
|
||||||
|
ruff==0.0.260
|
||||||
2
ping.py
2
ping.py
@@ -17,7 +17,7 @@ if url.drivername.startswith("sqlite"):
|
|||||||
|
|
||||||
# Null out the database so raw_connection doesnt error if it doesnt exist
|
# Null out the database so raw_connection doesnt error if it doesnt exist
|
||||||
# CTFd will create the database if it doesnt exist
|
# CTFd will create the database if it doesnt exist
|
||||||
url.database = None
|
url = url.set(database=None)
|
||||||
|
|
||||||
# Wait for the database server to be available
|
# Wait for the database server to be available
|
||||||
engine = create_engine(url)
|
engine = create_engine(url)
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
Flask==1.1.2
|
Flask==2.0.3
|
||||||
Werkzeug==1.0.1
|
Werkzeug==2.1.2
|
||||||
Jinja2==2.11.3
|
Flask-SQLAlchemy==2.5.1
|
||||||
Flask-SQLAlchemy==2.4.3
|
Flask-Caching==2.0.2
|
||||||
Flask-Caching==1.8.0
|
|
||||||
Flask-Migrate==2.5.3
|
Flask-Migrate==2.5.3
|
||||||
Flask-Script==2.0.6
|
Flask-Script==2.0.6
|
||||||
SQLAlchemy==1.3.17
|
SQLAlchemy==1.4.48
|
||||||
SQLAlchemy-Utils==0.41.0
|
SQLAlchemy-Utils==0.41.1
|
||||||
passlib==1.7.4
|
passlib==1.7.4
|
||||||
bcrypt==4.0.1
|
bcrypt==4.0.1
|
||||||
itsdangerous==1.1.0
|
|
||||||
requests==2.28.1
|
requests==2.28.1
|
||||||
PyMySQL[rsa]==0.9.3
|
PyMySQL[rsa]==0.9.3
|
||||||
gunicorn==20.1.0
|
gunicorn==20.1.0
|
||||||
dataset==1.3.1
|
dataset==1.5.2
|
||||||
cmarkgfm==2022.10.27
|
cmarkgfm==2022.10.27
|
||||||
redis==4.4.4
|
redis==4.5.5
|
||||||
gevent==22.10.2
|
gevent==22.10.2
|
||||||
python-dotenv==0.13.0
|
python-dotenv==0.13.0
|
||||||
flask-restx==0.5.1
|
flask-restx==1.1.0
|
||||||
flask-marshmallow==0.10.1
|
flask-marshmallow==0.10.1
|
||||||
marshmallow-sqlalchemy==0.17.0
|
marshmallow-sqlalchemy==0.17.0
|
||||||
boto3==1.13.9
|
boto3==1.13.9
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#
|
#
|
||||||
# This file is autogenerated by pip-compile with python 3.9
|
# This file is autogenerated by pip-compile with Python 3.9
|
||||||
# To update, run:
|
# by the following command:
|
||||||
#
|
#
|
||||||
# ./scripts/pip-compile.sh
|
# ./scripts/pip-compile.sh
|
||||||
#
|
#
|
||||||
@@ -16,6 +16,8 @@ attrs==20.3.0
|
|||||||
# via jsonschema
|
# via jsonschema
|
||||||
babel==2.12.1
|
babel==2.12.1
|
||||||
# via flask-babel
|
# via flask-babel
|
||||||
|
banal==1.0.6
|
||||||
|
# via dataset
|
||||||
bcrypt==4.0.1
|
bcrypt==4.0.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
boto3==1.13.9
|
boto3==1.13.9
|
||||||
@@ -24,6 +26,8 @@ botocore==1.16.26
|
|||||||
# via
|
# via
|
||||||
# boto3
|
# boto3
|
||||||
# s3transfer
|
# s3transfer
|
||||||
|
cachelib==0.9.0
|
||||||
|
# via flask-caching
|
||||||
certifi==2022.12.7
|
certifi==2022.12.7
|
||||||
# via requests
|
# via requests
|
||||||
cffi==1.15.0
|
cffi==1.15.0
|
||||||
@@ -39,11 +43,11 @@ cmarkgfm==2022.10.27
|
|||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
cryptography==40.0.2
|
cryptography==40.0.2
|
||||||
# via pymysql
|
# via pymysql
|
||||||
dataset==1.3.1
|
dataset==1.5.2
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
docutils==0.15.2
|
docutils==0.15.2
|
||||||
# via botocore
|
# via botocore
|
||||||
flask==1.1.2
|
flask==2.0.3
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# flask-babel
|
# flask-babel
|
||||||
@@ -55,17 +59,17 @@ flask==1.1.2
|
|||||||
# flask-sqlalchemy
|
# flask-sqlalchemy
|
||||||
flask-babel==2.0.0
|
flask-babel==2.0.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
flask-caching==1.8.0
|
flask-caching==2.0.2
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
flask-marshmallow==0.10.1
|
flask-marshmallow==0.10.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
flask-migrate==2.5.3
|
flask-migrate==2.5.3
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
flask-restx==0.5.1
|
flask-restx==1.1.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
flask-script==2.0.6
|
flask-script==2.0.6
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
flask-sqlalchemy==2.4.3
|
flask-sqlalchemy==2.5.1
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# flask-migrate
|
# flask-migrate
|
||||||
@@ -74,18 +78,17 @@ freezegun==1.2.2
|
|||||||
gevent==22.10.2
|
gevent==22.10.2
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
greenlet==2.0.1
|
greenlet==2.0.1
|
||||||
# via gevent
|
# via
|
||||||
|
# gevent
|
||||||
|
# sqlalchemy
|
||||||
gunicorn==20.1.0
|
gunicorn==20.1.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
idna==2.10
|
idna==2.10
|
||||||
# via requests
|
# via requests
|
||||||
itsdangerous==1.1.0
|
itsdangerous==2.1.2
|
||||||
|
# via flask
|
||||||
|
jinja2==3.1.2
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
|
||||||
# flask
|
|
||||||
jinja2==2.11.3
|
|
||||||
# via
|
|
||||||
# -r requirements.in
|
|
||||||
# flask
|
# flask
|
||||||
# flask-babel
|
# flask-babel
|
||||||
jmespath==0.10.0
|
jmespath==0.10.0
|
||||||
@@ -96,7 +99,7 @@ jsonschema==3.2.0
|
|||||||
# via flask-restx
|
# via flask-restx
|
||||||
mako==1.1.3
|
mako==1.1.3
|
||||||
# via alembic
|
# via alembic
|
||||||
markupsafe==1.1.1
|
markupsafe==2.1.3
|
||||||
# via
|
# via
|
||||||
# jinja2
|
# jinja2
|
||||||
# mako
|
# mako
|
||||||
@@ -139,7 +142,7 @@ pytz==2020.4
|
|||||||
# via
|
# via
|
||||||
# flask-babel
|
# flask-babel
|
||||||
# flask-restx
|
# flask-restx
|
||||||
redis==3.5.2
|
redis==4.5.5
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
requests==2.28.1
|
requests==2.28.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
@@ -148,11 +151,10 @@ s3transfer==0.3.3
|
|||||||
six==1.15.0
|
six==1.15.0
|
||||||
# via
|
# via
|
||||||
# flask-marshmallow
|
# flask-marshmallow
|
||||||
# flask-restx
|
|
||||||
# jsonschema
|
# jsonschema
|
||||||
# python-dateutil
|
# python-dateutil
|
||||||
# tenacity
|
# tenacity
|
||||||
sqlalchemy==1.3.17
|
sqlalchemy==1.4.48
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# alembic
|
# alembic
|
||||||
@@ -160,7 +162,7 @@ sqlalchemy==1.3.17
|
|||||||
# flask-sqlalchemy
|
# flask-sqlalchemy
|
||||||
# marshmallow-sqlalchemy
|
# marshmallow-sqlalchemy
|
||||||
# sqlalchemy-utils
|
# sqlalchemy-utils
|
||||||
sqlalchemy-utils==0.41.0
|
sqlalchemy-utils==0.41.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
tenacity==6.2.0
|
tenacity==6.2.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
@@ -168,7 +170,7 @@ urllib3==1.25.11
|
|||||||
# via
|
# via
|
||||||
# botocore
|
# botocore
|
||||||
# requests
|
# requests
|
||||||
werkzeug==1.0.1
|
werkzeug==2.1.2
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# flask
|
# flask
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ docker run \
|
|||||||
-v $ROOTDIR:/mnt/CTFd \
|
-v $ROOTDIR:/mnt/CTFd \
|
||||||
-e CUSTOM_COMPILE_COMMAND='./scripts/pip-compile.sh' \
|
-e CUSTOM_COMPILE_COMMAND='./scripts/pip-compile.sh' \
|
||||||
-it python:3.9-slim-buster \
|
-it python:3.9-slim-buster \
|
||||||
-c 'cd /mnt/CTFd && pip install pip-tools==6.6.0 && pip-compile'
|
-c 'cd /mnt/CTFd && pip install pip-tools==6.13.0 && pip-compile'
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ def test_admin_access():
|
|||||||
for route in routes:
|
for route in routes:
|
||||||
r = client.get(route)
|
r = client.get(route)
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.location.startswith("http://localhost/login")
|
assert r.location.startswith("/login")
|
||||||
|
|
||||||
admin = login_as_user(app, name="admin")
|
admin = login_as_user(app, name="admin")
|
||||||
routes.remove("/admin")
|
routes.remove("/admin")
|
||||||
@@ -78,5 +78,5 @@ def test_get_admin_as_user():
|
|||||||
client = login_as_user(app)
|
client = login_as_user(app)
|
||||||
r = client.get("/admin")
|
r = client.get("/admin")
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.location.startswith("http://localhost/login")
|
assert r.location.startswith("/login")
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|||||||
@@ -132,7 +132,8 @@ def test_config_value_types():
|
|||||||
|
|
||||||
# Test regular length strings
|
# Test regular length strings
|
||||||
r = admin.patch(
|
r = admin.patch(
|
||||||
"/api/v1/configs", json={"ctf_footer": "// regular length string"},
|
"/api/v1/configs",
|
||||||
|
json={"ctf_footer": "// regular length string"},
|
||||||
)
|
)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert get_config("ctf_footer") == "// regular length string"
|
assert get_config("ctf_footer") == "// regular length string"
|
||||||
|
|||||||
@@ -44,5 +44,5 @@ def test_api_hint_404():
|
|||||||
for endpoint in endpoints:
|
for endpoint in endpoints:
|
||||||
r = client.get(endpoint.format(1))
|
r = client.get(endpoint.format(1))
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.location.startswith("http://localhost/login")
|
assert r.location.startswith("/login")
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ def create_ctfd(
|
|||||||
config.APPLICATION_ROOT = application_root
|
config.APPLICATION_ROOT = application_root
|
||||||
url = make_url(config.SQLALCHEMY_DATABASE_URI)
|
url = make_url(config.SQLALCHEMY_DATABASE_URI)
|
||||||
if url.database:
|
if url.database:
|
||||||
url.database = str(uuid.uuid4())
|
url = url.set(database=str(uuid.uuid4()))
|
||||||
config.SQLALCHEMY_DATABASE_URI = str(url)
|
config.SQLALCHEMY_DATABASE_URI = str(url)
|
||||||
|
|
||||||
app = create_app(config)
|
app = create_app(config)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ def test_oauth_not_configured():
|
|||||||
with app.app_context():
|
with app.app_context():
|
||||||
with app.test_client() as client:
|
with app.test_client() as client:
|
||||||
r = client.get("/oauth", follow_redirects=False)
|
r = client.get("/oauth", follow_redirects=False)
|
||||||
assert r.location == "http://localhost/login"
|
assert r.location == "/login"
|
||||||
r = client.get(r.location)
|
r = client.get(r.location)
|
||||||
resp = r.get_data(as_text=True)
|
resp = r.get_data(as_text=True)
|
||||||
assert "OAuth Settings not configured" in resp
|
assert "OAuth Settings not configured" in resp
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ def test_anonymous_users_view_public_challenges_without_team():
|
|||||||
with app.test_client() as client:
|
with app.test_client() as client:
|
||||||
r = client.get("/challenges")
|
r = client.get("/challenges")
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.location.startswith("http://localhost/login")
|
assert r.location.startswith("/login")
|
||||||
|
|
||||||
set_config("challenge_visibility", "public")
|
set_config("challenge_visibility", "public")
|
||||||
with app.test_client() as client:
|
with app.test_client() as client:
|
||||||
@@ -61,5 +61,5 @@ def test_anonymous_users_view_public_challenges_without_team():
|
|||||||
with login_as_user(app) as client:
|
with login_as_user(app) as client:
|
||||||
r = client.get("/challenges")
|
r = client.get("/challenges")
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.location.startswith("http://localhost/team")
|
assert r.location.startswith("/team")
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ def test_that_ctfd_can_be_deployed_in_subdir():
|
|||||||
with app.test_client() as client:
|
with app.test_client() as client:
|
||||||
r = client.get("/")
|
r = client.get("/")
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.location == "http://localhost/ctf/setup"
|
assert r.location == "/ctf/setup"
|
||||||
|
|
||||||
r = client.get("/setup")
|
r = client.get("/setup")
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
@@ -105,13 +105,13 @@ def test_that_ctfd_can_be_deployed_in_subdir():
|
|||||||
}
|
}
|
||||||
r = client.post("/setup", data=data)
|
r = client.post("/setup", data=data)
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.location == "http://localhost/ctf/"
|
assert r.location == "/ctf/"
|
||||||
|
|
||||||
c = Client(app)
|
c = Client(app)
|
||||||
app_iter, status, headers = c.get("/")
|
response = c.get("/")
|
||||||
headers = dict(headers)
|
headers = dict(response.headers)
|
||||||
assert status == "302 FOUND"
|
assert response.status == "302 FOUND"
|
||||||
assert headers["Location"] == "http://localhost/ctf/"
|
assert headers["Location"] == "/ctf/?"
|
||||||
|
|
||||||
r = client.get("/challenges")
|
r = client.get("/challenges")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ def test_page_requiring_auth():
|
|||||||
with app.test_client() as client:
|
with app.test_client() as client:
|
||||||
r = client.get("/this-is-a-route")
|
r = client.get("/this-is-a-route")
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.location == "http://localhost/login?next=%2Fthis-is-a-route%3F"
|
assert r.location == "/login?next=%2Fthis-is-a-route%3F"
|
||||||
|
|
||||||
register_user(app)
|
register_user(app)
|
||||||
client = login_as_user(app)
|
client = login_as_user(app)
|
||||||
@@ -388,6 +388,13 @@ def test_user_can_access_files_with_auth_token():
|
|||||||
# Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
|
# Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
|
||||||
set_config("end", "1507262400")
|
set_config("end", "1507262400")
|
||||||
set_config("view_after_ctf", True)
|
set_config("view_after_ctf", True)
|
||||||
|
|
||||||
|
# Get file_url under current time
|
||||||
|
with login_as_user(app) as user:
|
||||||
|
req = user.get("/api/v1/challenges/1")
|
||||||
|
data = req.get_json()
|
||||||
|
file_url = data["data"]["files"][0]
|
||||||
|
|
||||||
for v in ("public", "private"):
|
for v in ("public", "private"):
|
||||||
set_config("challenge_visibility", v)
|
set_config("challenge_visibility", v)
|
||||||
|
|
||||||
@@ -424,12 +431,13 @@ def test_user_can_access_files_if_view_after_ctf():
|
|||||||
|
|
||||||
register_user(app)
|
register_user(app)
|
||||||
with login_as_user(app) as client:
|
with login_as_user(app) as client:
|
||||||
|
# After ctf end
|
||||||
|
# Get file_url during freeze time
|
||||||
|
with freeze_time("2017-10-7"):
|
||||||
req = client.get("/api/v1/challenges/1")
|
req = client.get("/api/v1/challenges/1")
|
||||||
data = req.get_json()
|
data = req.get_json()
|
||||||
file_url = data["data"]["files"][0]
|
file_url = data["data"]["files"][0]
|
||||||
|
|
||||||
# After ctf end
|
|
||||||
with freeze_time("2017-10-7"):
|
|
||||||
# Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
|
# Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
|
||||||
set_config("end", "1507262400")
|
set_config("end", "1507262400")
|
||||||
|
|
||||||
@@ -443,8 +451,8 @@ def test_user_can_access_files_if_view_after_ctf():
|
|||||||
assert r.get_data(as_text=True) == "testing file load"
|
assert r.get_data(as_text=True) == "testing file load"
|
||||||
|
|
||||||
# Unauthed users should be able to download if view_after_ctf
|
# Unauthed users should be able to download if view_after_ctf
|
||||||
client = app.test_client()
|
unauth_client = app.test_client()
|
||||||
r = client.get(file_url)
|
r = unauth_client.get(file_url)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.get_data(as_text=True) == "testing file load"
|
assert r.get_data(as_text=True) == "testing file load"
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -119,9 +119,7 @@ def test_user_bad_login():
|
|||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
assert sess.get("id") is None
|
assert sess.get("id") is None
|
||||||
r = client.get("/profile")
|
r = client.get("/profile")
|
||||||
assert r.location.startswith(
|
assert r.location.startswith("/login") # We got redirected to login
|
||||||
"http://localhost/login"
|
|
||||||
) # We got redirected to login
|
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|
||||||
|
|
||||||
@@ -132,9 +130,7 @@ def test_user_login():
|
|||||||
register_user(app)
|
register_user(app)
|
||||||
client = login_as_user(app)
|
client = login_as_user(app)
|
||||||
r = client.get("/profile")
|
r = client.get("/profile")
|
||||||
assert (
|
assert r.location is None # We didn't get redirected to login
|
||||||
r.location != "http://localhost/login"
|
|
||||||
) # We didn't get redirected to login
|
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|
||||||
@@ -146,9 +142,7 @@ def test_user_login_with_email():
|
|||||||
register_user(app)
|
register_user(app)
|
||||||
client = login_as_user(app, name="user@examplectf.com", password="password")
|
client = login_as_user(app, name="user@examplectf.com", password="password")
|
||||||
r = client.get("/profile")
|
r = client.get("/profile")
|
||||||
assert (
|
assert r.location is None # We didn't get redirected to login
|
||||||
r.location != "http://localhost/login"
|
|
||||||
) # We didn't get redirected to login
|
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|
||||||
@@ -161,7 +155,7 @@ def test_user_get_logout():
|
|||||||
client = login_as_user(app)
|
client = login_as_user(app)
|
||||||
client.get("/logout", follow_redirects=True)
|
client.get("/logout", follow_redirects=True)
|
||||||
r = client.get("/challenges")
|
r = client.get("/challenges")
|
||||||
assert r.location == "http://localhost/login?next=%2Fchallenges%3F"
|
assert r.location == "/login?next=%2Fchallenges%3F"
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|
||||||
@@ -182,7 +176,7 @@ def test_user_isnt_admin():
|
|||||||
"config",
|
"config",
|
||||||
]:
|
]:
|
||||||
r = client.get("/admin/{}".format(page))
|
r = client.get("/admin/{}".format(page))
|
||||||
assert r.location.startswith("http://localhost/login?next=")
|
assert r.location.startswith("/login?next=")
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|
||||||
@@ -302,7 +296,7 @@ def test_user_can_confirm_email(mock_smtp):
|
|||||||
|
|
||||||
client = login_as_user(app, name="user1", password="password")
|
client = login_as_user(app, name="user1", password="password")
|
||||||
|
|
||||||
r = client.get("http://localhost/confirm")
|
r = client.get("/confirm")
|
||||||
assert "We've sent a confirmation email" in r.get_data(as_text=True)
|
assert "We've sent a confirmation email" in r.get_data(as_text=True)
|
||||||
|
|
||||||
# smtp send message function was called
|
# smtp send message function was called
|
||||||
@@ -310,23 +304,21 @@ def test_user_can_confirm_email(mock_smtp):
|
|||||||
|
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
data = {"nonce": sess.get("nonce")}
|
data = {"nonce": sess.get("nonce")}
|
||||||
r = client.post("http://localhost/confirm", data=data)
|
r = client.post("/confirm", data=data)
|
||||||
assert "Confirmation email sent to" in r.get_data(as_text=True)
|
assert "Confirmation email sent to" in r.get_data(as_text=True)
|
||||||
|
|
||||||
r = client.get("/challenges")
|
r = client.get("/challenges")
|
||||||
assert (
|
assert r.location == "/confirm" # We got redirected to /confirm
|
||||||
r.location == "http://localhost/confirm"
|
|
||||||
) # We got redirected to /confirm
|
|
||||||
|
|
||||||
r = client.get("http://localhost/confirm/" + serialize("user@user.com"))
|
r = client.get("/confirm/" + serialize("user@user.com"))
|
||||||
assert r.location == "http://localhost/challenges"
|
assert r.location == "/challenges"
|
||||||
|
|
||||||
# The team is now verified
|
# The team is now verified
|
||||||
user = Users.query.filter_by(email="user@user.com").first()
|
user = Users.query.filter_by(email="user@user.com").first()
|
||||||
assert user.verified is True
|
assert user.verified is True
|
||||||
|
|
||||||
r = client.get("http://localhost/confirm")
|
r = client.get("/confirm")
|
||||||
assert r.location == "http://localhost/settings"
|
assert r.location == "/settings"
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|
||||||
|
|
||||||
@@ -462,7 +454,7 @@ def test_registration_code_required():
|
|||||||
data["registration_code"] = "secret-sauce"
|
data["registration_code"] = "secret-sauce"
|
||||||
r = client.post("/register", data=data)
|
r = client.post("/register", data=data)
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.location.startswith("http://localhost/challenges")
|
assert r.location.startswith("/challenges")
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|
||||||
|
|
||||||
@@ -492,5 +484,5 @@ def test_registration_code_allows_numeric():
|
|||||||
data["registration_code"] = "1234567890"
|
data["registration_code"] = "1234567890"
|
||||||
r = client.post("/register", data=data)
|
r = client.post("/register", data=data)
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.location.startswith("http://localhost/challenges")
|
assert r.location.startswith("/challenges")
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ def test_submitting_unicode_flag():
|
|||||||
register_user(app)
|
register_user(app)
|
||||||
client = login_as_user(app)
|
client = login_as_user(app)
|
||||||
chal = gen_challenge(app.db)
|
chal = gen_challenge(app.db)
|
||||||
gen_flag(app.db, challenge_id=chal.id, content=u"你好")
|
gen_flag(app.db, challenge_id=chal.id, content="你好")
|
||||||
with client.session_transaction():
|
with client.session_transaction():
|
||||||
data = {"submission": "你好", "challenge_id": chal.id}
|
data = {"submission": "你好", "challenge_id": chal.id}
|
||||||
r = client.post("/api/v1/challenges/attempt", json=data)
|
r = client.post("/api/v1/challenges/attempt", json=data)
|
||||||
@@ -251,7 +251,7 @@ def test_challenges_with_max_attempts():
|
|||||||
chal.max_attempts = 3
|
chal.max_attempts = 3
|
||||||
app.db.session.commit()
|
app.db.session.commit()
|
||||||
|
|
||||||
gen_flag(app.db, challenge_id=chal.id, content=u"flag")
|
gen_flag(app.db, challenge_id=chal.id, content="flag")
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
data = {"submission": "notflag", "challenge_id": chal_id}
|
data = {"submission": "notflag", "challenge_id": chal_id}
|
||||||
r = client.post("/api/v1/challenges/attempt", json=data)
|
r = client.post("/api/v1/challenges/attempt", json=data)
|
||||||
@@ -281,7 +281,7 @@ def test_challenge_kpm_limit():
|
|||||||
chal = gen_challenge(app.db)
|
chal = gen_challenge(app.db)
|
||||||
chal_id = chal.id
|
chal_id = chal.id
|
||||||
|
|
||||||
gen_flag(app.db, challenge_id=chal.id, content=u"flag")
|
gen_flag(app.db, challenge_id=chal.id, content="flag")
|
||||||
for _ in range(11):
|
for _ in range(11):
|
||||||
with client.session_transaction():
|
with client.session_transaction():
|
||||||
data = {"submission": "notflag", "challenge_id": chal_id}
|
data = {"submission": "notflag", "challenge_id": chal_id}
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ def test_user_needs_all_required_fields():
|
|||||||
|
|
||||||
r = client.get("/challenges")
|
r = client.get("/challenges")
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.location.startswith("http://localhost/settings")
|
assert r.location.startswith("/settings")
|
||||||
|
|
||||||
# Populate the non-required fields
|
# Populate the non-required fields
|
||||||
r = client.patch(
|
r = client.patch(
|
||||||
@@ -277,7 +277,7 @@ def test_user_needs_all_required_fields():
|
|||||||
# I should still be restricted from seeing challenges
|
# I should still be restricted from seeing challenges
|
||||||
r = client.get("/challenges")
|
r = client.get("/challenges")
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.location.startswith("http://localhost/settings")
|
assert r.location.startswith("/settings")
|
||||||
|
|
||||||
# I should still see all fields b/c I don't have a complete profile
|
# I should still see all fields b/c I don't have a complete profile
|
||||||
r = client.get("/settings")
|
r = client.get("/settings")
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ def test_ctfd_setup_redirect():
|
|||||||
with app.test_client() as client:
|
with app.test_client() as client:
|
||||||
r = client.get("/users")
|
r = client.get("/users")
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.location == "http://localhost/setup"
|
assert r.location == "/setup"
|
||||||
|
|
||||||
# Files in /themes load properly
|
# Files in /themes load properly
|
||||||
r = client.get("/themes/core/static/css/main.dev.css")
|
r = client.get("/themes/core/static/css/main.dev.css")
|
||||||
@@ -52,5 +52,5 @@ def test_ctfd_setup_verification():
|
|||||||
data["email"] = "admin@examplectf.com"
|
data["email"] = "admin@examplectf.com"
|
||||||
r = client.post("/setup", data=data)
|
r = client.post("/setup", data=data)
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.location == "http://localhost/"
|
assert r.location == "/"
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ def test_ctftime_prevents_accessing_challenges_before_ctf():
|
|||||||
register_user(app)
|
register_user(app)
|
||||||
chal = gen_challenge(app.db)
|
chal = gen_challenge(app.db)
|
||||||
chal_id = chal.id
|
chal_id = chal.id
|
||||||
gen_flag(app.db, challenge_id=chal.id, content=u"flag")
|
gen_flag(app.db, challenge_id=chal.id, content="flag")
|
||||||
|
|
||||||
with ctftime.not_started():
|
with ctftime.not_started():
|
||||||
client = login_as_user(app)
|
client = login_as_user(app)
|
||||||
@@ -48,7 +48,7 @@ def test_ctftime_redirects_to_teams_page_in_teams_mode_before_ctf():
|
|||||||
with ctftime.init():
|
with ctftime.init():
|
||||||
register_user(app)
|
register_user(app)
|
||||||
chal = gen_challenge(app.db)
|
chal = gen_challenge(app.db)
|
||||||
gen_flag(app.db, challenge_id=chal.id, content=u"flag")
|
gen_flag(app.db, challenge_id=chal.id, content="flag")
|
||||||
|
|
||||||
with ctftime.not_started():
|
with ctftime.not_started():
|
||||||
client = login_as_user(app)
|
client = login_as_user(app)
|
||||||
@@ -83,7 +83,7 @@ def test_ctftime_allows_accessing_challenges_during_ctf():
|
|||||||
register_user(app)
|
register_user(app)
|
||||||
chal = gen_challenge(app.db)
|
chal = gen_challenge(app.db)
|
||||||
chal_id = chal.id
|
chal_id = chal.id
|
||||||
gen_flag(app.db, challenge_id=chal.id, content=u"flag")
|
gen_flag(app.db, challenge_id=chal.id, content="flag")
|
||||||
|
|
||||||
with ctftime.started():
|
with ctftime.started():
|
||||||
client = login_as_user(app)
|
client = login_as_user(app)
|
||||||
@@ -112,7 +112,7 @@ def test_ctftime_prevents_accessing_challenges_after_ctf():
|
|||||||
register_user(app)
|
register_user(app)
|
||||||
chal = gen_challenge(app.db)
|
chal = gen_challenge(app.db)
|
||||||
chal_id = chal.id
|
chal_id = chal.id
|
||||||
gen_flag(app.db, challenge_id=chal.id, content=u"flag")
|
gen_flag(app.db, challenge_id=chal.id, content="flag")
|
||||||
|
|
||||||
with ctftime.ended():
|
with ctftime.ended():
|
||||||
client = login_as_user(app)
|
client = login_as_user(app)
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ def test_sendmail_with_mailgun_from_config_file(fake_post_request):
|
|||||||
|
|
||||||
args, kwargs = fake_post_request.call_args
|
args, kwargs = fake_post_request.call_args
|
||||||
assert args[0] == "https://api.mailgun.net/v3/file.faked.com/messages"
|
assert args[0] == "https://api.mailgun.net/v3/file.faked.com/messages"
|
||||||
assert kwargs["auth"] == ("api", u"key-1234567890-file-config")
|
assert kwargs["auth"] == ("api", "key-1234567890-file-config")
|
||||||
assert kwargs["timeout"] == 1.0
|
assert kwargs["timeout"] == 1.0
|
||||||
assert kwargs["data"] == {
|
assert kwargs["data"] == {
|
||||||
"to": ["user@user.com"],
|
"to": ["user@user.com"],
|
||||||
@@ -144,7 +144,7 @@ def test_sendmail_with_mailgun_from_db_config(fake_post_request):
|
|||||||
|
|
||||||
args, kwargs = fake_post_request.call_args
|
args, kwargs = fake_post_request.call_args
|
||||||
assert args[0] == "https://api.mailgun.net/v3/db.faked.com/messages"
|
assert args[0] == "https://api.mailgun.net/v3/db.faked.com/messages"
|
||||||
assert kwargs["auth"] == ("api", u"key-1234567890-db-config")
|
assert kwargs["auth"] == ("api", "key-1234567890-db-config")
|
||||||
assert kwargs["timeout"] == 1.0
|
assert kwargs["timeout"] == 1.0
|
||||||
assert kwargs["data"] == {
|
assert kwargs["data"] == {
|
||||||
"to": ["user@user.com"],
|
"to": ["user@user.com"],
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
from unittest.mock import Mock, patch
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from mock import Mock, patch
|
|
||||||
|
|
||||||
from tests.helpers import create_ctfd, destroy_ctfd, login_as_user, register_user
|
from tests.helpers import create_ctfd, destroy_ctfd, login_as_user, register_user
|
||||||
|
|
||||||
|
|
||||||
@@ -40,7 +39,7 @@ def test_session_invalidation_on_admin_password_change():
|
|||||||
r = user.get("/settings")
|
r = user.get("/settings")
|
||||||
# User's password was changed
|
# User's password was changed
|
||||||
# They should be logged out
|
# They should be logged out
|
||||||
assert r.location.startswith("http://localhost/login")
|
assert r.location.startswith("/login")
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|
||||||
|
|||||||
@@ -69,14 +69,14 @@ def test_update_check_ignores_downgrades(fake_post_request):
|
|||||||
fake_response = Mock()
|
fake_response = Mock()
|
||||||
fake_post_request.return_value = fake_response
|
fake_post_request.return_value = fake_response
|
||||||
fake_response.json = lambda: {
|
fake_response.json = lambda: {
|
||||||
u"resource": {
|
"resource": {
|
||||||
u"html_url": u"https://github.com/CTFd/CTFd/releases/tag/0.0.1",
|
"html_url": "https://github.com/CTFd/CTFd/releases/tag/0.0.1",
|
||||||
u"download_url": u"https://api.github.com/repos/CTFd/CTFd/zipball/0.0.1",
|
"download_url": "https://api.github.com/repos/CTFd/CTFd/zipball/0.0.1",
|
||||||
u"published_at": u"Wed, 25 Oct 2017 19:39:42 -0000",
|
"published_at": "Wed, 25 Oct 2017 19:39:42 -0000",
|
||||||
u"tag": u"0.0.1",
|
"tag": "0.0.1",
|
||||||
u"prerelease": False,
|
"prerelease": False,
|
||||||
u"id": 6,
|
"id": 6,
|
||||||
u"latest": True,
|
"latest": True,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
update_check()
|
update_check()
|
||||||
@@ -85,18 +85,18 @@ def test_update_check_ignores_downgrades(fake_post_request):
|
|||||||
fake_response = Mock()
|
fake_response = Mock()
|
||||||
fake_post_request.return_value = fake_response
|
fake_post_request.return_value = fake_response
|
||||||
fake_response.json = lambda: {
|
fake_response.json = lambda: {
|
||||||
u"resource": {
|
"resource": {
|
||||||
u"html_url": u"https://github.com/CTFd/CTFd/releases/tag/{}".format(
|
"html_url": "https://github.com/CTFd/CTFd/releases/tag/{}".format(
|
||||||
app.VERSION
|
app.VERSION
|
||||||
),
|
),
|
||||||
u"download_url": u"https://api.github.com/repos/CTFd/CTFd/zipball/{}".format(
|
"download_url": "https://api.github.com/repos/CTFd/CTFd/zipball/{}".format(
|
||||||
app.VERSION
|
app.VERSION
|
||||||
),
|
),
|
||||||
u"published_at": u"Wed, 25 Oct 2017 19:39:42 -0000",
|
"published_at": "Wed, 25 Oct 2017 19:39:42 -0000",
|
||||||
u"tag": u"{}".format(app.VERSION),
|
"tag": "{}".format(app.VERSION),
|
||||||
u"prerelease": False,
|
"prerelease": False,
|
||||||
u"id": 6,
|
"id": 6,
|
||||||
u"latest": True,
|
"latest": True,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
update_check()
|
update_check()
|
||||||
|
|||||||
Reference in New Issue
Block a user