diff --git a/CTFd/api/v1/challenges.py b/CTFd/api/v1/challenges.py index d9eb6f2..d58ce6b 100644 --- a/CTFd/api/v1/challenges.py +++ b/CTFd/api/v1/challenges.py @@ -66,6 +66,10 @@ class ChallengeList(Resource): .order_by(Solves.challenge_id.asc())\ .all() solve_ids = set([value for value, in solve_ids]) + + # TODO: Convert this into a re-useable decorator + if config.is_teams_mode() and get_current_team() is None: + abort(403) else: solve_ids = set() @@ -210,6 +214,10 @@ class Challenge(Resource): unlocked_hints = set([u.target for u in HintUnlocks.query.filter_by( type='hints', account_id=user.account_id)]) + # TODO: Convert this into a re-useable decorator + if config.is_teams_mode() and get_current_team() is None: + abort(403) + for hint in Hints.query.filter_by(challenge_id=chal.id).all(): if hint.id in unlocked_hints or ctf_ended(): hints.append({'id': hint.id, 'cost': hint.cost, @@ -309,6 +317,10 @@ class ChallengeAttempt(Resource): user = get_current_user() team = get_current_team() + # TODO: Convert this into a re-useable decorator + if config.is_teams_mode() and team is None: + abort(403) + fails = Fails.query.filter_by( account_id=user.account_id, challenge_id=challenge_id diff --git a/CTFd/schemas/awards.py b/CTFd/schemas/awards.py index ff3962d..9b21335 100644 --- a/CTFd/schemas/awards.py +++ b/CTFd/schemas/awards.py @@ -3,6 +3,7 @@ from marshmallow import fields, post_load from marshmallow import validate, ValidationError from marshmallow_sqlalchemy import field_for from CTFd.models import ma, Awards +from CTFd.utils import string_types class AwardSchema(ma.ModelSchema): @@ -43,9 +44,9 @@ class AwardSchema(ma.ModelSchema): def __init__(self, view=None, *args, **kwargs): if view: - if type(view) == str: + if isinstance(view, string_types): kwargs['only'] = self.views[view] - elif type(view) == list: + elif isinstance(view, list): kwargs['only'] = view super(AwardSchema, self).__init__(*args, **kwargs) diff --git a/CTFd/schemas/config.py b/CTFd/schemas/config.py index b227e1f..84fe416 100644 --- a/CTFd/schemas/config.py +++ b/CTFd/schemas/config.py @@ -3,6 +3,7 @@ from marshmallow import fields, post_load from marshmallow import validate, ValidationError from marshmallow_sqlalchemy import field_for from CTFd.models import ma, Configs +from CTFd.utils import string_types class ConfigSchema(ma.ModelSchema): @@ -21,9 +22,9 @@ class ConfigSchema(ma.ModelSchema): def __init__(self, view=None, *args, **kwargs): if view: - if type(view) == str: + if isinstance(view, string_types): kwargs['only'] = self.views[view] - elif type(view) == list: + elif isinstance(view, list): kwargs['only'] = view super(ConfigSchema, self).__init__(*args, **kwargs) diff --git a/CTFd/schemas/files.py b/CTFd/schemas/files.py index fa50e14..191eb53 100644 --- a/CTFd/schemas/files.py +++ b/CTFd/schemas/files.py @@ -3,6 +3,7 @@ from marshmallow import fields, post_load from marshmallow import validate, ValidationError from marshmallow_sqlalchemy import field_for from CTFd.models import ma, Files, ChallengeFiles, PageFiles +from CTFd.utils import string_types class FileSchema(ma.ModelSchema): @@ -13,9 +14,9 @@ class FileSchema(ma.ModelSchema): def __init__(self, view=None, *args, **kwargs): if view: - if type(view) == str: + if isinstance(view, string_types): kwargs['only'] = self.views[view] - elif type(view) == list: + elif isinstance(view, list): kwargs['only'] = view super(FileSchema, self).__init__(*args, **kwargs) diff --git a/CTFd/schemas/flags.py b/CTFd/schemas/flags.py index e3c44a1..1e66104 100644 --- a/CTFd/schemas/flags.py +++ b/CTFd/schemas/flags.py @@ -3,6 +3,7 @@ from marshmallow import fields, post_load from marshmallow import validate, ValidationError from marshmallow_sqlalchemy import field_for from CTFd.models import ma, Flags +from CTFd.utils import string_types class FlagSchema(ma.ModelSchema): @@ -13,9 +14,9 @@ class FlagSchema(ma.ModelSchema): def __init__(self, view=None, *args, **kwargs): if view: - if type(view) == str: + if isinstance(view, string_types): kwargs['only'] = self.views[view] - elif type(view) == list: + elif isinstance(view, list): kwargs['only'] = view super(FlagSchema, self).__init__(*args, **kwargs) diff --git a/CTFd/schemas/hints.py b/CTFd/schemas/hints.py index 4ab8a09..0874846 100644 --- a/CTFd/schemas/hints.py +++ b/CTFd/schemas/hints.py @@ -3,6 +3,7 @@ from marshmallow import fields, post_load from marshmallow import validate, ValidationError from marshmallow_sqlalchemy import field_for from CTFd.models import ma, Hints +from CTFd.utils import string_types class HintSchema(ma.ModelSchema): @@ -37,9 +38,9 @@ class HintSchema(ma.ModelSchema): def __init__(self, view=None, *args, **kwargs): if view: - if type(view) == str: + if isinstance(view, string_types): kwargs['only'] = self.views[view] - elif type(view) == list: + elif isinstance(view, list): kwargs['only'] = view super(HintSchema, self).__init__(*args, **kwargs) diff --git a/CTFd/schemas/notifications.py b/CTFd/schemas/notifications.py index 6240027..bcb4803 100644 --- a/CTFd/schemas/notifications.py +++ b/CTFd/schemas/notifications.py @@ -3,6 +3,7 @@ from marshmallow import fields, post_load from marshmallow import validate, ValidationError from marshmallow_sqlalchemy import field_for from CTFd.models import ma, Notifications +from CTFd.utils import string_types class NotificationSchema(ma.ModelSchema): @@ -13,9 +14,9 @@ class NotificationSchema(ma.ModelSchema): def __init__(self, view=None, *args, **kwargs): if view: - if type(view) == str: + if isinstance(view, string_types): kwargs['only'] = self.views[view] - elif type(view) == list: + elif isinstance(view, list): kwargs['only'] = view super(NotificationSchema, self).__init__(*args, **kwargs) diff --git a/CTFd/schemas/pages.py b/CTFd/schemas/pages.py index 21a7077..6b5ff3a 100644 --- a/CTFd/schemas/pages.py +++ b/CTFd/schemas/pages.py @@ -3,6 +3,7 @@ from marshmallow import fields, post_load from marshmallow import validate, ValidationError, pre_load from marshmallow_sqlalchemy import field_for from CTFd.models import ma, Pages +from CTFd.utils import string_types class PageSchema(ma.ModelSchema): @@ -19,9 +20,9 @@ class PageSchema(ma.ModelSchema): def __init__(self, view=None, *args, **kwargs): if view: - if type(view) == str: + if isinstance(view, string_types): kwargs['only'] = self.views[view] - elif type(view) == list: + elif isinstance(view, list): kwargs['only'] = view super(PageSchema, self).__init__(*args, **kwargs) diff --git a/CTFd/schemas/submissions.py b/CTFd/schemas/submissions.py index 7e0a391..5a48205 100644 --- a/CTFd/schemas/submissions.py +++ b/CTFd/schemas/submissions.py @@ -3,6 +3,7 @@ from marshmallow import fields, post_load, validate, ValidationError from marshmallow_sqlalchemy import field_for from CTFd.schemas.challenges import ChallengeSchema from CTFd.models import ma, Submissions +from CTFd.utils import string_types class SubmissionSchema(ma.ModelSchema): @@ -38,9 +39,9 @@ class SubmissionSchema(ma.ModelSchema): def __init__(self, view=None, *args, **kwargs): if view: - if type(view) == str: + if isinstance(view, string_types): kwargs['only'] = self.views[view] - elif type(view) == list: + elif isinstance(view, list): kwargs['only'] = view super(SubmissionSchema, self).__init__(*args, **kwargs) diff --git a/CTFd/schemas/tags.py b/CTFd/schemas/tags.py index 8ce1770..94fac88 100644 --- a/CTFd/schemas/tags.py +++ b/CTFd/schemas/tags.py @@ -3,6 +3,7 @@ from marshmallow import fields, post_load from marshmallow import validate, ValidationError from marshmallow_sqlalchemy import field_for from CTFd.models import ma, Tags +from CTFd.utils import string_types class TagSchema(ma.ModelSchema): @@ -24,9 +25,9 @@ class TagSchema(ma.ModelSchema): def __init__(self, view=None, *args, **kwargs): if view: - if type(view) == str: + if isinstance(view, string_types): kwargs['only'] = self.views[view] - elif type(view) == list: + elif isinstance(view, list): kwargs['only'] = view super(TagSchema, self).__init__(*args, **kwargs) diff --git a/CTFd/schemas/teams.py b/CTFd/schemas/teams.py index 5aaed8e..c4cb0e7 100644 --- a/CTFd/schemas/teams.py +++ b/CTFd/schemas/teams.py @@ -9,6 +9,7 @@ from CTFd.utils.user import is_admin, get_current_team from CTFd.utils.countries import lookup_country_code from CTFd.utils.user import is_admin, get_current_team from CTFd.utils.crypto import verify_password, hash_password +from CTFd.utils import string_types class TeamSchema(ma.ModelSchema): @@ -162,9 +163,9 @@ class TeamSchema(ma.ModelSchema): def __init__(self, view=None, *args, **kwargs): if view: - if type(view) == str: + if isinstance(view, string_types): kwargs['only'] = self.views[view] - elif type(view) == list: + elif isinstance(view, list): kwargs['only'] = view super(TeamSchema, self).__init__(*args, **kwargs) diff --git a/CTFd/schemas/unlocks.py b/CTFd/schemas/unlocks.py index 4faeba7..9b54050 100644 --- a/CTFd/schemas/unlocks.py +++ b/CTFd/schemas/unlocks.py @@ -3,6 +3,7 @@ from marshmallow import fields, post_load from marshmallow import validate, ValidationError from marshmallow_sqlalchemy import field_for from CTFd.models import ma, Unlocks +from CTFd.utils import string_types class UnlockSchema(ma.ModelSchema): @@ -30,9 +31,9 @@ class UnlockSchema(ma.ModelSchema): def __init__(self, view=None, *args, **kwargs): if view: - if type(view) == str: + if isinstance(view, string_types): kwargs['only'] = self.views[view] - elif type(view) == list: + elif isinstance(view, list): kwargs['only'] = view super(UnlockSchema, self).__init__(*args, **kwargs) diff --git a/CTFd/schemas/users.py b/CTFd/schemas/users.py index 9b71ef2..48ec97b 100644 --- a/CTFd/schemas/users.py +++ b/CTFd/schemas/users.py @@ -11,6 +11,7 @@ from CTFd.utils.user import is_admin, get_current_user from CTFd.utils.countries import lookup_country_code from CTFd.utils.crypto import verify_password, hash_password from CTFd.utils.email import check_email_is_whitelisted +from CTFd.utils import string_types class UserSchema(ma.ModelSchema): @@ -182,9 +183,9 @@ class UserSchema(ma.ModelSchema): def __init__(self, view=None, *args, **kwargs): if view: - if type(view) == str: + if isinstance(view, string_types): kwargs['only'] = self.views[view] - elif type(view) == list: + elif isinstance(view, list): kwargs['only'] = view super(UserSchema, self).__init__(*args, **kwargs) diff --git a/CTFd/utils/config/__init__.py b/CTFd/utils/config/__init__.py index c98cd68..3bf594c 100644 --- a/CTFd/utils/config/__init__.py +++ b/CTFd/utils/config/__init__.py @@ -3,6 +3,7 @@ from CTFd.models import Configs, Users, Teams from CTFd.cache import cache from CTFd.utils import get_config from CTFd.utils.user import authed +from CTFd.utils.modes import USERS_MODE, TEAMS_MODE import time import os @@ -16,6 +17,14 @@ def user_mode(): return get_config('user_mode') +def is_users_mode(): + return user_mode() == USERS_MODE + + +def is_teams_mode(): + return user_mode() == TEAMS_MODE + + def ctf_logo(): return get_config('ctf_logo') diff --git a/tests/api/v1/test_teams.py b/tests/api/v1/test_teams.py index 7de02cd..41c8dc1 100644 --- a/tests/api/v1/test_teams.py +++ b/tests/api/v1/test_teams.py @@ -480,3 +480,37 @@ def test_api_accessing_hidden_banned_users(): assert client.get('/api/v1/teams/2/fails').status_code == 200 assert client.get('/api/v1/teams/2/awards').status_code == 200 destroy_ctfd(app) + + +def test_api_user_without_team_challenge_interaction(): + """Can a user interact with challenges without having joined a team?""" + app = create_ctfd(user_mode="teams") + with app.app_context(): + register_user(app) + gen_challenge(app.db) + gen_flag(app.db, 1) + + with login_as_user(app) as client: + assert client.get('/api/v1/challenges').status_code == 403 + assert client.get('/api/v1/challenges/1').status_code == 403 + assert client.post('/api/v1/challenges/attempt', json={ + "challenge_id": 1, + "submission": "wrong_flag" + }).status_code == 403 + + # Create a user with a team + user = gen_user(app.db, email='user_name@ctfd.io') + team = gen_team(app.db) + team.members.append(user) + user.team_id = team.id + app.db.session.commit() + + # Test if user with team can interact with challenges + with login_as_user(app, name="user_name") as client: + assert client.get('/api/v1/challenges').status_code == 200 + assert client.get('/api/v1/challenges/1').status_code == 200 + assert client.post('/api/v1/challenges/attempt', json={ + "challenge_id": 1, + "submission": "flag" + }).status_code == 200 + destroy_ctfd(app) diff --git a/tests/api/v1/test_users.py b/tests/api/v1/test_users.py index 3d7a867..16e4427 100644 --- a/tests/api/v1/test_users.py +++ b/tests/api/v1/test_users.py @@ -3,6 +3,7 @@ from CTFd.utils import set_config from CTFd.utils.crypto import verify_password +from CTFd.schemas.users import UserSchema from tests.helpers import * @@ -674,3 +675,22 @@ def test_api_user_send_email(): assert r.status_code == 200 destroy_ctfd(app) + + +def test_api_user_get_schema(): + """Can a user get /api/v1/users/ doesn't return unnecessary data""" + app = create_ctfd() + with app.app_context(): + register_user(app, name="user1", email="user1@ctfd.io") # ID 2 + register_user(app, name="user2", email="user2@ctfd.io") # ID 3 + + with app.test_client() as client: + r = client.get('/api/v1/users/3') + data = r.get_json()['data'] + assert sorted(data.keys()) == sorted(UserSchema.views['user'] + ['score', 'place']) + + with login_as_user(app, name="user1") as client: + r = client.get('/api/v1/users/3') + data = r.get_json()['data'] + assert sorted(data.keys()) == sorted(UserSchema.views['user'] + ['score', 'place']) + destroy_ctfd(app)