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
|
||||
run: |
|
||||
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 global add prettier@1.17.0
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ from flask_migrate import upgrade
|
||||
from jinja2 import FileSystemLoader
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
from werkzeug.utils import cached_property
|
||||
|
||||
import CTFd.utils.config
|
||||
from CTFd import utils
|
||||
@@ -36,16 +35,14 @@ __channel__ = "oss"
|
||||
|
||||
|
||||
class CTFdRequest(Request):
|
||||
@cached_property
|
||||
def path(self):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
"""
|
||||
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.
|
||||
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):
|
||||
@@ -86,24 +83,30 @@ class SandboxedBaseEnvironment(SandboxedEnvironment):
|
||||
theme = str(utils.get_config("ctf_theme"))
|
||||
cache_name = theme + "/" + name
|
||||
|
||||
# Rest of this code is copied from Jinja
|
||||
# https://github.com/pallets/jinja/blob/master/src/jinja2/environment.py#L802-L815
|
||||
# Rest of this code roughly copied from Jinja
|
||||
# https://github.com/pallets/jinja/blob/b08cd4bc64bb980df86ed2876978ae5735572280/src/jinja2/environment.py#L956-L973
|
||||
cache_key = (weakref.ref(self.loader), cache_name)
|
||||
if self.cache is not None:
|
||||
template = self.cache.get(cache_key)
|
||||
if template is not None and (
|
||||
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
|
||||
template = self.loader.load(self, name, globals)
|
||||
|
||||
template = self.loader.load(self, name, self.make_globals(globals))
|
||||
|
||||
if self.cache is not None:
|
||||
self.cache[cache_key] = template
|
||||
return template
|
||||
|
||||
|
||||
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")
|
||||
_ADMIN_THEME_PREFIX = ADMIN_THEME + "/"
|
||||
|
||||
@@ -120,7 +120,7 @@ def export_ctf():
|
||||
backup = export_ctf_util()
|
||||
ctf_name = ctf_config.ctf_name()
|
||||
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(
|
||||
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():
|
||||
default = None
|
||||
fields[name] = (python_type, default)
|
||||
pydantic_model = create_model(
|
||||
db_model.__name__, **fields # type: ignore
|
||||
)
|
||||
pydantic_model = create_model(db_model.__name__, **fields) # type: ignore
|
||||
return pydantic_model
|
||||
|
||||
@@ -51,7 +51,10 @@ class TopicList(Resource):
|
||||
{
|
||||
"value": (str, None),
|
||||
"q": (str, None),
|
||||
"field": (RawEnum("TopicFields", {"value": "value"}), None,),
|
||||
"field": (
|
||||
RawEnum("TopicFields", {"value": "value"}),
|
||||
None,
|
||||
),
|
||||
},
|
||||
location="query",
|
||||
)
|
||||
@@ -122,7 +125,8 @@ class TopicList(Resource):
|
||||
responses={200: ("Success", "APISimpleSuccessResponse")},
|
||||
)
|
||||
@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):
|
||||
topic_type = query_args.get("type")
|
||||
|
||||
@@ -208,9 +208,11 @@ def register():
|
||||
registration_code = str(request.form.get("registration_code", ""))
|
||||
|
||||
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 = (
|
||||
Users.query.add_columns("email", "id")
|
||||
Users.query.add_columns(Users.email, Users.id)
|
||||
.filter_by(email=email_address)
|
||||
.first()
|
||||
)
|
||||
|
||||
@@ -12,7 +12,8 @@ def render_error(error):
|
||||
try:
|
||||
return (
|
||||
render_template(
|
||||
"errors/{}.html".format(error.code), error=error.description,
|
||||
"errors/{}.html".format(error.code),
|
||||
error=error.description,
|
||||
),
|
||||
error.code,
|
||||
)
|
||||
|
||||
@@ -49,7 +49,8 @@ class AccountSettingsForm(BaseForm):
|
||||
description="Max number of teams (Teams mode only)",
|
||||
)
|
||||
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",
|
||||
@@ -101,13 +102,15 @@ class LegalSettingsForm(BaseForm):
|
||||
description="External URL to a Terms of Service document hosted elsewhere",
|
||||
)
|
||||
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 Policy URL",
|
||||
description="External URL to a Privacy Policy document hosted elsewhere",
|
||||
)
|
||||
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")
|
||||
|
||||
@@ -15,12 +15,13 @@ ma = Marshmallow()
|
||||
|
||||
def get_class_by_tablename(tablename):
|
||||
"""Return class reference mapped to table.
|
||||
https://stackoverflow.com/a/23754464
|
||||
https://stackoverflow.com/a/66666783
|
||||
|
||||
:param tablename: String with name of table.
|
||||
: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:
|
||||
return c
|
||||
return None
|
||||
|
||||
@@ -25,11 +25,13 @@ class DynamicChallenge(Challenges):
|
||||
class DynamicValueChallenge(BaseChallenge):
|
||||
id = "dynamic" # Unique identifier used to register challenges
|
||||
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",
|
||||
"update": "/plugins/dynamic_challenges/assets/update.html",
|
||||
"view": "/plugins/dynamic_challenges/assets/view.html",
|
||||
}
|
||||
)
|
||||
scripts = { # Scripts that are loaded when a template is loaded
|
||||
"create": "/plugins/dynamic_challenges/assets/create.js",
|
||||
"update": "/plugins/dynamic_challenges/assets/update.js",
|
||||
|
||||
@@ -68,5 +68,7 @@ class ChallengeSchema(ma.ModelSchema):
|
||||
)
|
||||
|
||||
requirements = field_for(
|
||||
Challenges, "requirements", validate=[ChallengeRequirementsValidator()],
|
||||
Challenges,
|
||||
"requirements",
|
||||
validate=[ChallengeRequirementsValidator()],
|
||||
)
|
||||
|
||||
@@ -16,7 +16,9 @@ class PageSchema(ma.ModelSchema):
|
||||
"title",
|
||||
validate=[
|
||||
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:
|
||||
freeze_cond = true()
|
||||
exclude_solves_cond = and_(
|
||||
AccountModel.banned == false(), AccountModel.hidden == false(),
|
||||
AccountModel.banned == false(),
|
||||
AccountModel.hidden == false(),
|
||||
)
|
||||
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)
|
||||
.filter(*challenge_id_filter, freeze_cond, exclude_solves_cond)
|
||||
.group_by(Solves.challenge_id)
|
||||
|
||||
@@ -368,21 +368,31 @@ def load_challenges_csv(dict_reader):
|
||||
if flags:
|
||||
flags = [flag.strip() for flag in flags.split(",")]
|
||||
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.commit()
|
||||
|
||||
if tags:
|
||||
tags = [tag.strip() for tag in tags.split(",")]
|
||||
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.commit()
|
||||
|
||||
if hints:
|
||||
hints = [hint.strip() for hint in hints.split(",")]
|
||||
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.commit()
|
||||
if errors:
|
||||
|
||||
@@ -84,6 +84,7 @@ def export_ctf():
|
||||
|
||||
backup_zip.close()
|
||||
backup.seek(0)
|
||||
db.close()
|
||||
return backup
|
||||
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@ migrations = Migrate()
|
||||
def create_database():
|
||||
url = make_url(app.config["SQLALCHEMY_DATABASE_URI"])
|
||||
if url.drivername == "postgres":
|
||||
url.drivername = "postgresql"
|
||||
url = url.set(drivername="postgresql")
|
||||
|
||||
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
|
||||
if not database_exists_util(url):
|
||||
@@ -34,7 +34,7 @@ def create_database():
|
||||
def drop_database():
|
||||
url = make_url(app.config["SQLALCHEMY_DATABASE_URI"])
|
||||
if url.drivername == "postgres":
|
||||
url.drivername = "postgresql"
|
||||
url = url.set(drivername="postgresql")
|
||||
drop_database_util(url)
|
||||
|
||||
|
||||
|
||||
@@ -124,9 +124,15 @@ def setup():
|
||||
password = request.form["password"]
|
||||
|
||||
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 = (
|
||||
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_long = len(password) > 128
|
||||
|
||||
@@ -3,17 +3,14 @@ pip-tools==5.4.0
|
||||
pytest==7.3.1
|
||||
pytest-randomly==3.12.0
|
||||
coverage==7.2.3
|
||||
ruff==0.0.260
|
||||
psycopg2-binary==2.9.6
|
||||
moto==1.3.16
|
||||
moto==4.1.11
|
||||
bandit==1.6.2
|
||||
flask_profiler==1.8.1
|
||||
pytest-xdist==3.2.1
|
||||
pytest-cov==4.0.0
|
||||
sphinx_rtd_theme==0.4.3
|
||||
flask-debugtoolbar==0.11.0
|
||||
isort==4.3.21
|
||||
Faker==4.1.0
|
||||
pipdeptree==2.2.0
|
||||
black==19.10b0
|
||||
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
|
||||
# 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
|
||||
engine = create_engine(url)
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
Flask==1.1.2
|
||||
Werkzeug==1.0.1
|
||||
Jinja2==2.11.3
|
||||
Flask-SQLAlchemy==2.4.3
|
||||
Flask-Caching==1.8.0
|
||||
Flask==2.0.3
|
||||
Werkzeug==2.1.2
|
||||
Flask-SQLAlchemy==2.5.1
|
||||
Flask-Caching==2.0.2
|
||||
Flask-Migrate==2.5.3
|
||||
Flask-Script==2.0.6
|
||||
SQLAlchemy==1.3.17
|
||||
SQLAlchemy-Utils==0.41.0
|
||||
SQLAlchemy==1.4.48
|
||||
SQLAlchemy-Utils==0.41.1
|
||||
passlib==1.7.4
|
||||
bcrypt==4.0.1
|
||||
itsdangerous==1.1.0
|
||||
requests==2.28.1
|
||||
PyMySQL[rsa]==0.9.3
|
||||
gunicorn==20.1.0
|
||||
dataset==1.3.1
|
||||
dataset==1.5.2
|
||||
cmarkgfm==2022.10.27
|
||||
redis==4.4.4
|
||||
redis==4.5.5
|
||||
gevent==22.10.2
|
||||
python-dotenv==0.13.0
|
||||
flask-restx==0.5.1
|
||||
flask-restx==1.1.0
|
||||
flask-marshmallow==0.10.1
|
||||
marshmallow-sqlalchemy==0.17.0
|
||||
boto3==1.13.9
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with python 3.9
|
||||
# To update, run:
|
||||
# This file is autogenerated by pip-compile with Python 3.9
|
||||
# by the following command:
|
||||
#
|
||||
# ./scripts/pip-compile.sh
|
||||
#
|
||||
@@ -16,6 +16,8 @@ attrs==20.3.0
|
||||
# via jsonschema
|
||||
babel==2.12.1
|
||||
# via flask-babel
|
||||
banal==1.0.6
|
||||
# via dataset
|
||||
bcrypt==4.0.1
|
||||
# via -r requirements.in
|
||||
boto3==1.13.9
|
||||
@@ -24,6 +26,8 @@ botocore==1.16.26
|
||||
# via
|
||||
# boto3
|
||||
# s3transfer
|
||||
cachelib==0.9.0
|
||||
# via flask-caching
|
||||
certifi==2022.12.7
|
||||
# via requests
|
||||
cffi==1.15.0
|
||||
@@ -39,11 +43,11 @@ cmarkgfm==2022.10.27
|
||||
# via -r requirements.in
|
||||
cryptography==40.0.2
|
||||
# via pymysql
|
||||
dataset==1.3.1
|
||||
dataset==1.5.2
|
||||
# via -r requirements.in
|
||||
docutils==0.15.2
|
||||
# via botocore
|
||||
flask==1.1.2
|
||||
flask==2.0.3
|
||||
# via
|
||||
# -r requirements.in
|
||||
# flask-babel
|
||||
@@ -55,17 +59,17 @@ flask==1.1.2
|
||||
# flask-sqlalchemy
|
||||
flask-babel==2.0.0
|
||||
# via -r requirements.in
|
||||
flask-caching==1.8.0
|
||||
flask-caching==2.0.2
|
||||
# via -r requirements.in
|
||||
flask-marshmallow==0.10.1
|
||||
# via -r requirements.in
|
||||
flask-migrate==2.5.3
|
||||
# via -r requirements.in
|
||||
flask-restx==0.5.1
|
||||
flask-restx==1.1.0
|
||||
# via -r requirements.in
|
||||
flask-script==2.0.6
|
||||
# via -r requirements.in
|
||||
flask-sqlalchemy==2.4.3
|
||||
flask-sqlalchemy==2.5.1
|
||||
# via
|
||||
# -r requirements.in
|
||||
# flask-migrate
|
||||
@@ -74,18 +78,17 @@ freezegun==1.2.2
|
||||
gevent==22.10.2
|
||||
# via -r requirements.in
|
||||
greenlet==2.0.1
|
||||
# via gevent
|
||||
# via
|
||||
# gevent
|
||||
# sqlalchemy
|
||||
gunicorn==20.1.0
|
||||
# via -r requirements.in
|
||||
idna==2.10
|
||||
# via requests
|
||||
itsdangerous==1.1.0
|
||||
itsdangerous==2.1.2
|
||||
# via flask
|
||||
jinja2==3.1.2
|
||||
# via
|
||||
# -r requirements.in
|
||||
# flask
|
||||
jinja2==2.11.3
|
||||
# via
|
||||
# -r requirements.in
|
||||
# flask
|
||||
# flask-babel
|
||||
jmespath==0.10.0
|
||||
@@ -96,7 +99,7 @@ jsonschema==3.2.0
|
||||
# via flask-restx
|
||||
mako==1.1.3
|
||||
# via alembic
|
||||
markupsafe==1.1.1
|
||||
markupsafe==2.1.3
|
||||
# via
|
||||
# jinja2
|
||||
# mako
|
||||
@@ -139,7 +142,7 @@ pytz==2020.4
|
||||
# via
|
||||
# flask-babel
|
||||
# flask-restx
|
||||
redis==3.5.2
|
||||
redis==4.5.5
|
||||
# via -r requirements.in
|
||||
requests==2.28.1
|
||||
# via -r requirements.in
|
||||
@@ -148,11 +151,10 @@ s3transfer==0.3.3
|
||||
six==1.15.0
|
||||
# via
|
||||
# flask-marshmallow
|
||||
# flask-restx
|
||||
# jsonschema
|
||||
# python-dateutil
|
||||
# tenacity
|
||||
sqlalchemy==1.3.17
|
||||
sqlalchemy==1.4.48
|
||||
# via
|
||||
# -r requirements.in
|
||||
# alembic
|
||||
@@ -160,7 +162,7 @@ sqlalchemy==1.3.17
|
||||
# flask-sqlalchemy
|
||||
# marshmallow-sqlalchemy
|
||||
# sqlalchemy-utils
|
||||
sqlalchemy-utils==0.41.0
|
||||
sqlalchemy-utils==0.41.1
|
||||
# via -r requirements.in
|
||||
tenacity==6.2.0
|
||||
# via -r requirements.in
|
||||
@@ -168,7 +170,7 @@ urllib3==1.25.11
|
||||
# via
|
||||
# botocore
|
||||
# requests
|
||||
werkzeug==1.0.1
|
||||
werkzeug==2.1.2
|
||||
# via
|
||||
# -r requirements.in
|
||||
# flask
|
||||
|
||||
@@ -7,4 +7,4 @@ docker run \
|
||||
-v $ROOTDIR:/mnt/CTFd \
|
||||
-e CUSTOM_COMPILE_COMMAND='./scripts/pip-compile.sh' \
|
||||
-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:
|
||||
r = client.get(route)
|
||||
assert r.status_code == 302
|
||||
assert r.location.startswith("http://localhost/login")
|
||||
assert r.location.startswith("/login")
|
||||
|
||||
admin = login_as_user(app, name="admin")
|
||||
routes.remove("/admin")
|
||||
@@ -78,5 +78,5 @@ def test_get_admin_as_user():
|
||||
client = login_as_user(app)
|
||||
r = client.get("/admin")
|
||||
assert r.status_code == 302
|
||||
assert r.location.startswith("http://localhost/login")
|
||||
assert r.location.startswith("/login")
|
||||
destroy_ctfd(app)
|
||||
|
||||
@@ -132,7 +132,8 @@ def test_config_value_types():
|
||||
|
||||
# Test regular length strings
|
||||
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 get_config("ctf_footer") == "// regular length string"
|
||||
|
||||
@@ -44,5 +44,5 @@ def test_api_hint_404():
|
||||
for endpoint in endpoints:
|
||||
r = client.get(endpoint.format(1))
|
||||
assert r.status_code == 302
|
||||
assert r.location.startswith("http://localhost/login")
|
||||
assert r.location.startswith("/login")
|
||||
destroy_ctfd(app)
|
||||
|
||||
@@ -138,7 +138,7 @@ def create_ctfd(
|
||||
config.APPLICATION_ROOT = application_root
|
||||
url = make_url(config.SQLALCHEMY_DATABASE_URI)
|
||||
if url.database:
|
||||
url.database = str(uuid.uuid4())
|
||||
url = url.set(database=str(uuid.uuid4()))
|
||||
config.SQLALCHEMY_DATABASE_URI = str(url)
|
||||
|
||||
app = create_app(config)
|
||||
|
||||
@@ -18,7 +18,7 @@ def test_oauth_not_configured():
|
||||
with app.app_context():
|
||||
with app.test_client() as client:
|
||||
r = client.get("/oauth", follow_redirects=False)
|
||||
assert r.location == "http://localhost/login"
|
||||
assert r.location == "/login"
|
||||
r = client.get(r.location)
|
||||
resp = r.get_data(as_text=True)
|
||||
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:
|
||||
r = client.get("/challenges")
|
||||
assert r.status_code == 302
|
||||
assert r.location.startswith("http://localhost/login")
|
||||
assert r.location.startswith("/login")
|
||||
|
||||
set_config("challenge_visibility", "public")
|
||||
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:
|
||||
r = client.get("/challenges")
|
||||
assert r.status_code == 302
|
||||
assert r.location.startswith("http://localhost/team")
|
||||
assert r.location.startswith("/team")
|
||||
destroy_ctfd(app)
|
||||
|
||||
@@ -90,7 +90,7 @@ def test_that_ctfd_can_be_deployed_in_subdir():
|
||||
with app.test_client() as client:
|
||||
r = client.get("/")
|
||||
assert r.status_code == 302
|
||||
assert r.location == "http://localhost/ctf/setup"
|
||||
assert r.location == "/ctf/setup"
|
||||
|
||||
r = client.get("/setup")
|
||||
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)
|
||||
assert r.status_code == 302
|
||||
assert r.location == "http://localhost/ctf/"
|
||||
assert r.location == "/ctf/"
|
||||
|
||||
c = Client(app)
|
||||
app_iter, status, headers = c.get("/")
|
||||
headers = dict(headers)
|
||||
assert status == "302 FOUND"
|
||||
assert headers["Location"] == "http://localhost/ctf/"
|
||||
response = c.get("/")
|
||||
headers = dict(response.headers)
|
||||
assert response.status == "302 FOUND"
|
||||
assert headers["Location"] == "/ctf/?"
|
||||
|
||||
r = client.get("/challenges")
|
||||
assert r.status_code == 200
|
||||
|
||||
@@ -83,7 +83,7 @@ def test_page_requiring_auth():
|
||||
with app.test_client() as client:
|
||||
r = client.get("/this-is-a-route")
|
||||
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)
|
||||
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
|
||||
set_config("end", "1507262400")
|
||||
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"):
|
||||
set_config("challenge_visibility", v)
|
||||
|
||||
@@ -424,12 +431,13 @@ def test_user_can_access_files_if_view_after_ctf():
|
||||
|
||||
register_user(app)
|
||||
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")
|
||||
data = req.get_json()
|
||||
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
|
||||
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"
|
||||
|
||||
# Unauthed users should be able to download if view_after_ctf
|
||||
client = app.test_client()
|
||||
r = client.get(file_url)
|
||||
unauth_client = app.test_client()
|
||||
r = unauth_client.get(file_url)
|
||||
assert r.status_code == 200
|
||||
assert r.get_data(as_text=True) == "testing file load"
|
||||
finally:
|
||||
|
||||
@@ -119,9 +119,7 @@ def test_user_bad_login():
|
||||
with client.session_transaction() as sess:
|
||||
assert sess.get("id") is None
|
||||
r = client.get("/profile")
|
||||
assert r.location.startswith(
|
||||
"http://localhost/login"
|
||||
) # We got redirected to login
|
||||
assert r.location.startswith("/login") # We got redirected to login
|
||||
destroy_ctfd(app)
|
||||
|
||||
|
||||
@@ -132,9 +130,7 @@ def test_user_login():
|
||||
register_user(app)
|
||||
client = login_as_user(app)
|
||||
r = client.get("/profile")
|
||||
assert (
|
||||
r.location != "http://localhost/login"
|
||||
) # We didn't get redirected to login
|
||||
assert r.location is None # We didn't get redirected to login
|
||||
assert r.status_code == 200
|
||||
destroy_ctfd(app)
|
||||
|
||||
@@ -146,9 +142,7 @@ def test_user_login_with_email():
|
||||
register_user(app)
|
||||
client = login_as_user(app, name="user@examplectf.com", password="password")
|
||||
r = client.get("/profile")
|
||||
assert (
|
||||
r.location != "http://localhost/login"
|
||||
) # We didn't get redirected to login
|
||||
assert r.location is None # We didn't get redirected to login
|
||||
assert r.status_code == 200
|
||||
destroy_ctfd(app)
|
||||
|
||||
@@ -161,7 +155,7 @@ def test_user_get_logout():
|
||||
client = login_as_user(app)
|
||||
client.get("/logout", follow_redirects=True)
|
||||
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
|
||||
destroy_ctfd(app)
|
||||
|
||||
@@ -182,7 +176,7 @@ def test_user_isnt_admin():
|
||||
"config",
|
||||
]:
|
||||
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
|
||||
destroy_ctfd(app)
|
||||
|
||||
@@ -302,7 +296,7 @@ def test_user_can_confirm_email(mock_smtp):
|
||||
|
||||
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)
|
||||
|
||||
# smtp send message function was called
|
||||
@@ -310,23 +304,21 @@ def test_user_can_confirm_email(mock_smtp):
|
||||
|
||||
with client.session_transaction() as sess:
|
||||
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)
|
||||
|
||||
r = client.get("/challenges")
|
||||
assert (
|
||||
r.location == "http://localhost/confirm"
|
||||
) # We got redirected to /confirm
|
||||
assert r.location == "/confirm" # We got redirected to /confirm
|
||||
|
||||
r = client.get("http://localhost/confirm/" + serialize("user@user.com"))
|
||||
assert r.location == "http://localhost/challenges"
|
||||
r = client.get("/confirm/" + serialize("user@user.com"))
|
||||
assert r.location == "/challenges"
|
||||
|
||||
# The team is now verified
|
||||
user = Users.query.filter_by(email="user@user.com").first()
|
||||
assert user.verified is True
|
||||
|
||||
r = client.get("http://localhost/confirm")
|
||||
assert r.location == "http://localhost/settings"
|
||||
r = client.get("/confirm")
|
||||
assert r.location == "/settings"
|
||||
destroy_ctfd(app)
|
||||
|
||||
|
||||
@@ -462,7 +454,7 @@ def test_registration_code_required():
|
||||
data["registration_code"] = "secret-sauce"
|
||||
r = client.post("/register", data=data)
|
||||
assert r.status_code == 302
|
||||
assert r.location.startswith("http://localhost/challenges")
|
||||
assert r.location.startswith("/challenges")
|
||||
destroy_ctfd(app)
|
||||
|
||||
|
||||
@@ -492,5 +484,5 @@ def test_registration_code_allows_numeric():
|
||||
data["registration_code"] = "1234567890"
|
||||
r = client.post("/register", data=data)
|
||||
assert r.status_code == 302
|
||||
assert r.location.startswith("http://localhost/challenges")
|
||||
assert r.location.startswith("/challenges")
|
||||
destroy_ctfd(app)
|
||||
|
||||
@@ -228,7 +228,7 @@ def test_submitting_unicode_flag():
|
||||
register_user(app)
|
||||
client = login_as_user(app)
|
||||
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():
|
||||
data = {"submission": "你好", "challenge_id": chal.id}
|
||||
r = client.post("/api/v1/challenges/attempt", json=data)
|
||||
@@ -251,7 +251,7 @@ def test_challenges_with_max_attempts():
|
||||
chal.max_attempts = 3
|
||||
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):
|
||||
data = {"submission": "notflag", "challenge_id": chal_id}
|
||||
r = client.post("/api/v1/challenges/attempt", json=data)
|
||||
@@ -281,7 +281,7 @@ def test_challenge_kpm_limit():
|
||||
chal = gen_challenge(app.db)
|
||||
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):
|
||||
with client.session_transaction():
|
||||
data = {"submission": "notflag", "challenge_id": chal_id}
|
||||
|
||||
@@ -259,7 +259,7 @@ def test_user_needs_all_required_fields():
|
||||
|
||||
r = client.get("/challenges")
|
||||
assert r.status_code == 302
|
||||
assert r.location.startswith("http://localhost/settings")
|
||||
assert r.location.startswith("/settings")
|
||||
|
||||
# Populate the non-required fields
|
||||
r = client.patch(
|
||||
@@ -277,7 +277,7 @@ def test_user_needs_all_required_fields():
|
||||
# I should still be restricted from seeing challenges
|
||||
r = client.get("/challenges")
|
||||
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
|
||||
r = client.get("/settings")
|
||||
|
||||
@@ -11,7 +11,7 @@ def test_ctfd_setup_redirect():
|
||||
with app.test_client() as client:
|
||||
r = client.get("/users")
|
||||
assert r.status_code == 302
|
||||
assert r.location == "http://localhost/setup"
|
||||
assert r.location == "/setup"
|
||||
|
||||
# Files in /themes load properly
|
||||
r = client.get("/themes/core/static/css/main.dev.css")
|
||||
@@ -52,5 +52,5 @@ def test_ctfd_setup_verification():
|
||||
data["email"] = "admin@examplectf.com"
|
||||
r = client.post("/setup", data=data)
|
||||
assert r.status_code == 302
|
||||
assert r.location == "http://localhost/"
|
||||
assert r.location == "/"
|
||||
destroy_ctfd(app)
|
||||
|
||||
@@ -21,7 +21,7 @@ def test_ctftime_prevents_accessing_challenges_before_ctf():
|
||||
register_user(app)
|
||||
chal = gen_challenge(app.db)
|
||||
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():
|
||||
client = login_as_user(app)
|
||||
@@ -48,7 +48,7 @@ def test_ctftime_redirects_to_teams_page_in_teams_mode_before_ctf():
|
||||
with ctftime.init():
|
||||
register_user(app)
|
||||
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():
|
||||
client = login_as_user(app)
|
||||
@@ -83,7 +83,7 @@ def test_ctftime_allows_accessing_challenges_during_ctf():
|
||||
register_user(app)
|
||||
chal = gen_challenge(app.db)
|
||||
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():
|
||||
client = login_as_user(app)
|
||||
@@ -112,7 +112,7 @@ def test_ctftime_prevents_accessing_challenges_after_ctf():
|
||||
register_user(app)
|
||||
chal = gen_challenge(app.db)
|
||||
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():
|
||||
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
|
||||
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["data"] == {
|
||||
"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
|
||||
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["data"] == {
|
||||
"to": ["user@user.com"],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from unittest.mock import Mock, patch
|
||||
from uuid import UUID
|
||||
|
||||
from mock import Mock, patch
|
||||
|
||||
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")
|
||||
# User's password was changed
|
||||
# They should be logged out
|
||||
assert r.location.startswith("http://localhost/login")
|
||||
assert r.location.startswith("/login")
|
||||
assert r.status_code == 302
|
||||
destroy_ctfd(app)
|
||||
|
||||
|
||||
@@ -69,14 +69,14 @@ def test_update_check_ignores_downgrades(fake_post_request):
|
||||
fake_response = Mock()
|
||||
fake_post_request.return_value = fake_response
|
||||
fake_response.json = lambda: {
|
||||
u"resource": {
|
||||
u"html_url": u"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",
|
||||
u"published_at": u"Wed, 25 Oct 2017 19:39:42 -0000",
|
||||
u"tag": u"0.0.1",
|
||||
u"prerelease": False,
|
||||
u"id": 6,
|
||||
u"latest": True,
|
||||
"resource": {
|
||||
"html_url": "https://github.com/CTFd/CTFd/releases/tag/0.0.1",
|
||||
"download_url": "https://api.github.com/repos/CTFd/CTFd/zipball/0.0.1",
|
||||
"published_at": "Wed, 25 Oct 2017 19:39:42 -0000",
|
||||
"tag": "0.0.1",
|
||||
"prerelease": False,
|
||||
"id": 6,
|
||||
"latest": True,
|
||||
}
|
||||
}
|
||||
update_check()
|
||||
@@ -85,18 +85,18 @@ def test_update_check_ignores_downgrades(fake_post_request):
|
||||
fake_response = Mock()
|
||||
fake_post_request.return_value = fake_response
|
||||
fake_response.json = lambda: {
|
||||
u"resource": {
|
||||
u"html_url": u"https://github.com/CTFd/CTFd/releases/tag/{}".format(
|
||||
"resource": {
|
||||
"html_url": "https://github.com/CTFd/CTFd/releases/tag/{}".format(
|
||||
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
|
||||
),
|
||||
u"published_at": u"Wed, 25 Oct 2017 19:39:42 -0000",
|
||||
u"tag": u"{}".format(app.VERSION),
|
||||
u"prerelease": False,
|
||||
u"id": 6,
|
||||
u"latest": True,
|
||||
"published_at": "Wed, 25 Oct 2017 19:39:42 -0000",
|
||||
"tag": "{}".format(app.VERSION),
|
||||
"prerelease": False,
|
||||
"id": 6,
|
||||
"latest": True,
|
||||
}
|
||||
}
|
||||
update_check()
|
||||
|
||||
Reference in New Issue
Block a user