Properly load schemas specified as strings (#943)

* Properly load schemas specified by their key string
* Add test for UserSchema 
* Prevent users without teams from interacting with challenges if the CTF is in Team Mode
selenium-screenshot-testing
Kevin Chung 2019-04-08 01:47:26 -04:00 committed by GitHub
parent 7c60c697ee
commit c0a32a836b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 111 additions and 24 deletions

View File

@ -66,6 +66,10 @@ class ChallengeList(Resource):
.order_by(Solves.challenge_id.asc())\ .order_by(Solves.challenge_id.asc())\
.all() .all()
solve_ids = set([value for value, in solve_ids]) 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: else:
solve_ids = set() solve_ids = set()
@ -210,6 +214,10 @@ class Challenge(Resource):
unlocked_hints = set([u.target for u in HintUnlocks.query.filter_by( unlocked_hints = set([u.target for u in HintUnlocks.query.filter_by(
type='hints', account_id=user.account_id)]) 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(): for hint in Hints.query.filter_by(challenge_id=chal.id).all():
if hint.id in unlocked_hints or ctf_ended(): if hint.id in unlocked_hints or ctf_ended():
hints.append({'id': hint.id, 'cost': hint.cost, hints.append({'id': hint.id, 'cost': hint.cost,
@ -309,6 +317,10 @@ class ChallengeAttempt(Resource):
user = get_current_user() user = get_current_user()
team = get_current_team() 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( fails = Fails.query.filter_by(
account_id=user.account_id, account_id=user.account_id,
challenge_id=challenge_id challenge_id=challenge_id

View File

@ -3,6 +3,7 @@ from marshmallow import fields, post_load
from marshmallow import validate, ValidationError from marshmallow import validate, ValidationError
from marshmallow_sqlalchemy import field_for from marshmallow_sqlalchemy import field_for
from CTFd.models import ma, Awards from CTFd.models import ma, Awards
from CTFd.utils import string_types
class AwardSchema(ma.ModelSchema): class AwardSchema(ma.ModelSchema):
@ -43,9 +44,9 @@ class AwardSchema(ma.ModelSchema):
def __init__(self, view=None, *args, **kwargs): def __init__(self, view=None, *args, **kwargs):
if view: if view:
if type(view) == str: if isinstance(view, string_types):
kwargs['only'] = self.views[view] kwargs['only'] = self.views[view]
elif type(view) == list: elif isinstance(view, list):
kwargs['only'] = view kwargs['only'] = view
super(AwardSchema, self).__init__(*args, **kwargs) super(AwardSchema, self).__init__(*args, **kwargs)

View File

@ -3,6 +3,7 @@ from marshmallow import fields, post_load
from marshmallow import validate, ValidationError from marshmallow import validate, ValidationError
from marshmallow_sqlalchemy import field_for from marshmallow_sqlalchemy import field_for
from CTFd.models import ma, Configs from CTFd.models import ma, Configs
from CTFd.utils import string_types
class ConfigSchema(ma.ModelSchema): class ConfigSchema(ma.ModelSchema):
@ -21,9 +22,9 @@ class ConfigSchema(ma.ModelSchema):
def __init__(self, view=None, *args, **kwargs): def __init__(self, view=None, *args, **kwargs):
if view: if view:
if type(view) == str: if isinstance(view, string_types):
kwargs['only'] = self.views[view] kwargs['only'] = self.views[view]
elif type(view) == list: elif isinstance(view, list):
kwargs['only'] = view kwargs['only'] = view
super(ConfigSchema, self).__init__(*args, **kwargs) super(ConfigSchema, self).__init__(*args, **kwargs)

View File

@ -3,6 +3,7 @@ from marshmallow import fields, post_load
from marshmallow import validate, ValidationError from marshmallow import validate, ValidationError
from marshmallow_sqlalchemy import field_for from marshmallow_sqlalchemy import field_for
from CTFd.models import ma, Files, ChallengeFiles, PageFiles from CTFd.models import ma, Files, ChallengeFiles, PageFiles
from CTFd.utils import string_types
class FileSchema(ma.ModelSchema): class FileSchema(ma.ModelSchema):
@ -13,9 +14,9 @@ class FileSchema(ma.ModelSchema):
def __init__(self, view=None, *args, **kwargs): def __init__(self, view=None, *args, **kwargs):
if view: if view:
if type(view) == str: if isinstance(view, string_types):
kwargs['only'] = self.views[view] kwargs['only'] = self.views[view]
elif type(view) == list: elif isinstance(view, list):
kwargs['only'] = view kwargs['only'] = view
super(FileSchema, self).__init__(*args, **kwargs) super(FileSchema, self).__init__(*args, **kwargs)

View File

@ -3,6 +3,7 @@ from marshmallow import fields, post_load
from marshmallow import validate, ValidationError from marshmallow import validate, ValidationError
from marshmallow_sqlalchemy import field_for from marshmallow_sqlalchemy import field_for
from CTFd.models import ma, Flags from CTFd.models import ma, Flags
from CTFd.utils import string_types
class FlagSchema(ma.ModelSchema): class FlagSchema(ma.ModelSchema):
@ -13,9 +14,9 @@ class FlagSchema(ma.ModelSchema):
def __init__(self, view=None, *args, **kwargs): def __init__(self, view=None, *args, **kwargs):
if view: if view:
if type(view) == str: if isinstance(view, string_types):
kwargs['only'] = self.views[view] kwargs['only'] = self.views[view]
elif type(view) == list: elif isinstance(view, list):
kwargs['only'] = view kwargs['only'] = view
super(FlagSchema, self).__init__(*args, **kwargs) super(FlagSchema, self).__init__(*args, **kwargs)

View File

@ -3,6 +3,7 @@ from marshmallow import fields, post_load
from marshmallow import validate, ValidationError from marshmallow import validate, ValidationError
from marshmallow_sqlalchemy import field_for from marshmallow_sqlalchemy import field_for
from CTFd.models import ma, Hints from CTFd.models import ma, Hints
from CTFd.utils import string_types
class HintSchema(ma.ModelSchema): class HintSchema(ma.ModelSchema):
@ -37,9 +38,9 @@ class HintSchema(ma.ModelSchema):
def __init__(self, view=None, *args, **kwargs): def __init__(self, view=None, *args, **kwargs):
if view: if view:
if type(view) == str: if isinstance(view, string_types):
kwargs['only'] = self.views[view] kwargs['only'] = self.views[view]
elif type(view) == list: elif isinstance(view, list):
kwargs['only'] = view kwargs['only'] = view
super(HintSchema, self).__init__(*args, **kwargs) super(HintSchema, self).__init__(*args, **kwargs)

View File

@ -3,6 +3,7 @@ from marshmallow import fields, post_load
from marshmallow import validate, ValidationError from marshmallow import validate, ValidationError
from marshmallow_sqlalchemy import field_for from marshmallow_sqlalchemy import field_for
from CTFd.models import ma, Notifications from CTFd.models import ma, Notifications
from CTFd.utils import string_types
class NotificationSchema(ma.ModelSchema): class NotificationSchema(ma.ModelSchema):
@ -13,9 +14,9 @@ class NotificationSchema(ma.ModelSchema):
def __init__(self, view=None, *args, **kwargs): def __init__(self, view=None, *args, **kwargs):
if view: if view:
if type(view) == str: if isinstance(view, string_types):
kwargs['only'] = self.views[view] kwargs['only'] = self.views[view]
elif type(view) == list: elif isinstance(view, list):
kwargs['only'] = view kwargs['only'] = view
super(NotificationSchema, self).__init__(*args, **kwargs) super(NotificationSchema, self).__init__(*args, **kwargs)

View File

@ -3,6 +3,7 @@ from marshmallow import fields, post_load
from marshmallow import validate, ValidationError, pre_load from marshmallow import validate, ValidationError, pre_load
from marshmallow_sqlalchemy import field_for from marshmallow_sqlalchemy import field_for
from CTFd.models import ma, Pages from CTFd.models import ma, Pages
from CTFd.utils import string_types
class PageSchema(ma.ModelSchema): class PageSchema(ma.ModelSchema):
@ -19,9 +20,9 @@ class PageSchema(ma.ModelSchema):
def __init__(self, view=None, *args, **kwargs): def __init__(self, view=None, *args, **kwargs):
if view: if view:
if type(view) == str: if isinstance(view, string_types):
kwargs['only'] = self.views[view] kwargs['only'] = self.views[view]
elif type(view) == list: elif isinstance(view, list):
kwargs['only'] = view kwargs['only'] = view
super(PageSchema, self).__init__(*args, **kwargs) super(PageSchema, self).__init__(*args, **kwargs)

View File

@ -3,6 +3,7 @@ from marshmallow import fields, post_load, validate, ValidationError
from marshmallow_sqlalchemy import field_for from marshmallow_sqlalchemy import field_for
from CTFd.schemas.challenges import ChallengeSchema from CTFd.schemas.challenges import ChallengeSchema
from CTFd.models import ma, Submissions from CTFd.models import ma, Submissions
from CTFd.utils import string_types
class SubmissionSchema(ma.ModelSchema): class SubmissionSchema(ma.ModelSchema):
@ -38,9 +39,9 @@ class SubmissionSchema(ma.ModelSchema):
def __init__(self, view=None, *args, **kwargs): def __init__(self, view=None, *args, **kwargs):
if view: if view:
if type(view) == str: if isinstance(view, string_types):
kwargs['only'] = self.views[view] kwargs['only'] = self.views[view]
elif type(view) == list: elif isinstance(view, list):
kwargs['only'] = view kwargs['only'] = view
super(SubmissionSchema, self).__init__(*args, **kwargs) super(SubmissionSchema, self).__init__(*args, **kwargs)

View File

@ -3,6 +3,7 @@ from marshmallow import fields, post_load
from marshmallow import validate, ValidationError from marshmallow import validate, ValidationError
from marshmallow_sqlalchemy import field_for from marshmallow_sqlalchemy import field_for
from CTFd.models import ma, Tags from CTFd.models import ma, Tags
from CTFd.utils import string_types
class TagSchema(ma.ModelSchema): class TagSchema(ma.ModelSchema):
@ -24,9 +25,9 @@ class TagSchema(ma.ModelSchema):
def __init__(self, view=None, *args, **kwargs): def __init__(self, view=None, *args, **kwargs):
if view: if view:
if type(view) == str: if isinstance(view, string_types):
kwargs['only'] = self.views[view] kwargs['only'] = self.views[view]
elif type(view) == list: elif isinstance(view, list):
kwargs['only'] = view kwargs['only'] = view
super(TagSchema, self).__init__(*args, **kwargs) super(TagSchema, self).__init__(*args, **kwargs)

View File

@ -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.countries import lookup_country_code
from CTFd.utils.user import is_admin, get_current_team from CTFd.utils.user import is_admin, get_current_team
from CTFd.utils.crypto import verify_password, hash_password from CTFd.utils.crypto import verify_password, hash_password
from CTFd.utils import string_types
class TeamSchema(ma.ModelSchema): class TeamSchema(ma.ModelSchema):
@ -162,9 +163,9 @@ class TeamSchema(ma.ModelSchema):
def __init__(self, view=None, *args, **kwargs): def __init__(self, view=None, *args, **kwargs):
if view: if view:
if type(view) == str: if isinstance(view, string_types):
kwargs['only'] = self.views[view] kwargs['only'] = self.views[view]
elif type(view) == list: elif isinstance(view, list):
kwargs['only'] = view kwargs['only'] = view
super(TeamSchema, self).__init__(*args, **kwargs) super(TeamSchema, self).__init__(*args, **kwargs)

View File

@ -3,6 +3,7 @@ from marshmallow import fields, post_load
from marshmallow import validate, ValidationError from marshmallow import validate, ValidationError
from marshmallow_sqlalchemy import field_for from marshmallow_sqlalchemy import field_for
from CTFd.models import ma, Unlocks from CTFd.models import ma, Unlocks
from CTFd.utils import string_types
class UnlockSchema(ma.ModelSchema): class UnlockSchema(ma.ModelSchema):
@ -30,9 +31,9 @@ class UnlockSchema(ma.ModelSchema):
def __init__(self, view=None, *args, **kwargs): def __init__(self, view=None, *args, **kwargs):
if view: if view:
if type(view) == str: if isinstance(view, string_types):
kwargs['only'] = self.views[view] kwargs['only'] = self.views[view]
elif type(view) == list: elif isinstance(view, list):
kwargs['only'] = view kwargs['only'] = view
super(UnlockSchema, self).__init__(*args, **kwargs) super(UnlockSchema, self).__init__(*args, **kwargs)

View File

@ -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.countries import lookup_country_code
from CTFd.utils.crypto import verify_password, hash_password from CTFd.utils.crypto import verify_password, hash_password
from CTFd.utils.email import check_email_is_whitelisted from CTFd.utils.email import check_email_is_whitelisted
from CTFd.utils import string_types
class UserSchema(ma.ModelSchema): class UserSchema(ma.ModelSchema):
@ -182,9 +183,9 @@ class UserSchema(ma.ModelSchema):
def __init__(self, view=None, *args, **kwargs): def __init__(self, view=None, *args, **kwargs):
if view: if view:
if type(view) == str: if isinstance(view, string_types):
kwargs['only'] = self.views[view] kwargs['only'] = self.views[view]
elif type(view) == list: elif isinstance(view, list):
kwargs['only'] = view kwargs['only'] = view
super(UserSchema, self).__init__(*args, **kwargs) super(UserSchema, self).__init__(*args, **kwargs)

View File

@ -3,6 +3,7 @@ from CTFd.models import Configs, Users, Teams
from CTFd.cache import cache from CTFd.cache import cache
from CTFd.utils import get_config from CTFd.utils import get_config
from CTFd.utils.user import authed from CTFd.utils.user import authed
from CTFd.utils.modes import USERS_MODE, TEAMS_MODE
import time import time
import os import os
@ -16,6 +17,14 @@ def user_mode():
return get_config('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(): def ctf_logo():
return get_config('ctf_logo') return get_config('ctf_logo')

View File

@ -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/fails').status_code == 200
assert client.get('/api/v1/teams/2/awards').status_code == 200 assert client.get('/api/v1/teams/2/awards').status_code == 200
destroy_ctfd(app) 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)

View File

@ -3,6 +3,7 @@
from CTFd.utils import set_config from CTFd.utils import set_config
from CTFd.utils.crypto import verify_password from CTFd.utils.crypto import verify_password
from CTFd.schemas.users import UserSchema
from tests.helpers import * from tests.helpers import *
@ -674,3 +675,22 @@ def test_api_user_send_email():
assert r.status_code == 200 assert r.status_code == 200
destroy_ctfd(app) destroy_ctfd(app)
def test_api_user_get_schema():
"""Can a user get /api/v1/users/<user_id> 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)