Bump Dependencies (#2332)

* Bump dependencies
* Closes #2300 
* Closes #2331
This commit is contained in:
Kevin Chung
2023-07-02 17:33:58 -04:00
committed by GitHub
parent 7b68babee6
commit deae9e1941
43 changed files with 205 additions and 166 deletions

View File

@@ -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

View File

@@ -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 + "/"

View File

@@ -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
)

View File

@@ -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

View File

@@ -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")

View File

@@ -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()
)

View File

@@ -21,7 +21,7 @@ def timed_lru_cache(timeout: int = 300, maxsize: int = 64, typed: bool = False):
def wrapper_cache(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
@wraps(func)

View File

@@ -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,
)

View File

@@ -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")

View File

@@ -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

View File

@@ -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
"create": "/plugins/dynamic_challenges/assets/create.html",
"update": "/plugins/dynamic_challenges/assets/update.html",
"view": "/plugins/dynamic_challenges/assets/view.html",
}
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",

View File

@@ -57,8 +57,8 @@ def logarithmic(challenge):
# It is important that this calculation takes into account floats.
# Hence this file uses from __future__ import division
value = (
((challenge.minimum - challenge.initial) / (challenge.decay ** 2))
* (solve_count ** 2)
((challenge.minimum - challenge.initial) / (challenge.decay**2))
* (solve_count**2)
) + challenge.initial
value = math.ceil(value)

View File

@@ -68,5 +68,7 @@ class ChallengeSchema(ma.ModelSchema):
)
requirements = field_for(
Challenges, "requirements", validate=[ChallengeRequirementsValidator()],
Challenges,
"requirements",
validate=[ChallengeRequirementsValidator()],
)

View File

@@ -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.",
)
],
)

View File

@@ -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)

View File

@@ -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:

View File

@@ -5,7 +5,7 @@ from CTFd.utils import get_config
def ctftime():
""" Checks whether it's CTF time or not. """
"""Checks whether it's CTF time or not."""
start = get_config("start")
end = get_config("end")

View File

@@ -84,6 +84,7 @@ def export_ctf():
backup_zip.close()
backup.seek(0)
db.close()
return backup

View File

@@ -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)

View File

@@ -152,15 +152,15 @@ def is_verified():
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
(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.
The trusted_proxies regexes is taken from Ruby on Rails.
"""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.
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.
This has issues if the clients are also on the local network so you can remove proxies from config.py.
This has issues if the clients are also on the local network so you can remove proxies from config.py.
CTFd does not use IP address for anything besides cursory tracking of teams and it is ill-advised to do much
more than that if you do not know what you're doing.
CTFd does not use IP address for anything besides cursory tracking of teams and it is ill-advised to do much
more than that if you do not know what you're doing.
"""
if req is None:
req = request

View File

@@ -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

View File

@@ -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
View File

@@ -0,0 +1,3 @@
black==22.3.0
isort==4.3.21
ruff==0.0.260

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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)

View File

@@ -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"

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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:
req = client.get("/api/v1/challenges/1")
data = req.get_json()
file_url = data["data"]["files"][0]
# 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]
# 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:

View File

@@ -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)

View File

@@ -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}

View File

@@ -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")

View File

@@ -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)

View File

@@ -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)

View File

@@ -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"],

View File

@@ -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)

View File

@@ -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()