def test_api_challenge_get_solves_ctftime_public(): """Can a public user get /api/v1/challenges/<challenge_id>/solves if ctftime is over""" app = create_ctfd() with app.app_context(), freeze_time("2017-10-7"): set_config('challenge_visibility', 'public') gen_challenge(app.db) with app.test_client() as client: r = client.get('/api/v1/challenges/1/solves') assert r.status_code == 200 set_config( 'start', '1507089600' ) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST set_config('end', '1507262400' ) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST r = client.get('/api/v1/challenges/1/solves', json="") assert r.status_code == 403 destroy_ctfd(app)
def test_admins_can_preview_hints(): """Test that admins are able to bypass restrictions and preview hints with ?preview=true""" app = create_ctfd() with app.app_context(): gen_challenge(app.db) gen_hint(app.db, challenge_id=1, cost=100) client = login_as_user(app, name="admin", password="******") r = client.get('/api/v1/hints/1') assert r.status_code == 200 hint = r.get_json() assert hint.get('content') is None r = client.get('/api/v1/hints/1?preview=true') assert r.status_code == 200 hint = r.get_json() assert hint['data']['content'] == "This is a hint" destroy_ctfd(app)
def test_api_challenge_get_ctftime_public(): """Can a public user get /api/v1/challenges/<challenge_id> if ctftime is over""" app = create_ctfd() with app.app_context(), freeze_time("2017-10-7"): set_config("challenge_visibility", "public") gen_challenge(app.db) with app.test_client() as client: r = client.get("/api/v1/challenges/1") assert r.status_code == 200 set_config( "start", "1507089600" ) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST set_config("end", "1507262400" ) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST r = client.get("/api/v1/challenges/1") assert r.status_code == 403 destroy_ctfd(app)
def test_api_challenge_get_solves_ctf_frozen(): """Test users can only see challenge solves that happened before freeze time""" app = create_ctfd() with app.app_context(): register_user(app, name="user1", email="*****@*****.**") register_user(app, name="user2", email="*****@*****.**") # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST set_config("freeze", "1507262400") with freeze_time("2017-10-4"): chal = gen_challenge(app.db) chal_id = chal.id gen_solve(app.db, user_id=2, challenge_id=chal_id) chal2 = gen_challenge(app.db) chal2_id = chal2.id with freeze_time("2017-10-8"): chal2 = gen_solve(app.db, user_id=2, challenge_id=chal2_id) # There should now be two solves assigned to the same user. assert Solves.query.count() == 2 client = login_as_user(app, name="user2") # Challenge 1 should have one solve r = client.get("/api/v1/challenges/1/solves") data = r.get_json()["data"] assert len(data) == 1 # Challenge 2 should have a solve shouldn't be shown to the user r = client.get("/api/v1/challenges/2/solves") data = r.get_json()["data"] assert len(data) == 0 # Admins should see data as an admin with no modifications admin = login_as_user(app, name="admin") r = admin.get("/api/v1/challenges/2/solves") data = r.get_json()["data"] assert len(data) == 1 # But should see as a user if the preview param is passed r = admin.get("/api/v1/challenges/2/solves?preview=true") data = r.get_json()["data"] assert len(data) == 0 destroy_ctfd(app)
def test_api_challenge_get_verified_emails(): """Can a verified email load /api/v1/challenges/<challenge_id>""" app = create_ctfd() with app.app_context(), freeze_time("2017-10-5"): set_config('start', '1507089600') # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST set_config('end', '1507262400') # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST set_config('verify_emails', True) gen_challenge(app.db) gen_user(app.db, name='user_name', email='*****@*****.**', password='******', verified=True) register_user(app) client = login_as_user(app) registered_client = login_as_user(app, 'user_name', 'password') r = client.get('/api/v1/challenges/1', json="") assert r.status_code == 403 r = registered_client.get('/api/v1/challenges/1') assert r.status_code == 200 destroy_ctfd(app)
def test_api_challenge_attempt_post_admin(): """Can an admin user post /api/v1/challenges/attempt""" app = create_ctfd() with app.app_context(): gen_challenge(app.db) gen_flag(app.db, 1) with login_as_user(app, 'admin') as client: r = client.post('/api/v1/challenges/attempt', json={"challenge_id": 1, "submission": "wrong_flag"}) assert r.status_code == 200 assert r.get_json()['data']['status'] == 'incorrect' r = client.post('/api/v1/challenges/attempt', json={"challenge_id": 1, "submission": "flag"}) assert r.status_code == 200 assert r.get_json()['data']['status'] == 'correct' r = client.post('/api/v1/challenges/attempt', json={"challenge_id": 1, "submission": "flag"}) assert r.status_code == 200 assert r.get_json()['data']['status'] == 'already_solved' destroy_ctfd(app)
def test_api_challenge_get_ctftime_private(): """Can a private user get /api/v1/challenges/<challenge_id> if ctftime is over""" app = create_ctfd() with app.app_context(), freeze_time("2017-10-7"): gen_challenge(app.db) register_user(app) client = login_as_user(app) r = client.get("/api/v1/challenges/1") assert r.status_code == 200 set_config("start", "1507089600" ) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST set_config( "end", "1507262400") # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST r = client.get("/api/v1/challenges/1") assert r.status_code == 403 destroy_ctfd(app)
def test_hidden_users_should_not_influence_scores(): app = create_ctfd() with app.app_context(): register_user(app, name="user1", email="*****@*****.**", password="******") register_user(app, name="user2", email="*****@*****.**", password="******") register_user(app, name="user3", email="*****@*****.**", password="******") user = Users.query.filter_by(name="user3").first() user.hidden = True app.db.session.commit() client1 = login_as_user(app, name="user1", password="******") # User 1 solves 1st challenge chal1 = gen_challenge(app.db) gen_solve(app.db, user_id=2, challenge_id=chal1.id) # User 2 solves 2nd challenge chal2 = gen_challenge(app.db) gen_solve(app.db, user_id=3, challenge_id=chal2.id) # User 3 solves both gen_solve(app.db, user_id=4, challenge_id=chal1.id) gen_solve(app.db, user_id=4, challenge_id=chal2.id) scores = get_scores(client1) for entry in scores: assert entry["name"] != "user3" user1 = Users.query.filter_by(name="user1").first() assert user1.place == "1st" user2 = Users.query.filter_by(name="user2").first() assert user2.place == "2nd" destroy_ctfd(app)
def test_users_cannot_preview_hints(): """Test that users aren't able to preview hints""" app = create_ctfd() with app.app_context(): gen_challenge(app.db) gen_hint(app.db, challenge_id=1, cost=100) register_user(app) client = login_as_user(app) r = client.get('/api/v1/hints/1') assert r.status_code == 200 hint = r.get_json() assert hint.get('content') is None r = client.get('/api/v1/hints/1?preview=true') assert r.status_code == 200 hint = r.get_json() assert hint['data'].get('content') is None destroy_ctfd(app)
def test_api_challenge_solves_visibility(): """Can the api load /api/v1/challenges/<challenge_id>/solves if challenge_visibility is private/public""" app = create_ctfd() with app.app_context(), freeze_time("2017-10-5"): set_config('start', '1507089600' ) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST set_config( 'end', '1507262400') # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST set_config('challenge_visibility', 'public') gen_challenge(app.db) with app.test_client() as client: r = client.get('/api/v1/challenges/1/solves') assert r.status_code == 200 set_config('challenge_visibility', 'private') r = client.get('/api/v1/challenges/1/solves') assert r.status_code == 302 destroy_ctfd(app)
def test_api_challenge_solves_user_visibility(): """Can the user load /api/v1/challenges/<challenge_id>/solves if challenge_visibility is private/public""" app = create_ctfd() with app.app_context(), freeze_time("2017-10-5"): set_config("start", "1507089600" ) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST set_config( "end", "1507262400") # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST gen_challenge(app.db) register_user(app) client = login_as_user(app) r = client.get("/api/v1/challenges/1/solves") assert r.status_code == 200 set_config("challenge_visibility", "public") r = client.get("/api/v1/challenges/1/solves") assert r.status_code == 200 destroy_ctfd(app)
def test_api_challenge_visibility(): """Can the api load /api/v1/challenges/<challenge_id> if challenge_visibility is private/public""" app = create_ctfd() with app.app_context(), freeze_time("2017-10-5"): set_config("start", "1507089600" ) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST set_config( "end", "1507262400") # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST set_config("challenge_visibility", "public") with app.test_client() as client: gen_challenge(app.db) r = client.get("/api/v1/challenges/1") assert r.status_code == 200 set_config("challenge_visibility", "private") r = client.get("/api/v1/challenges/1") assert r.status_code == 302 destroy_ctfd(app)
def test_api_submissions_post_admin(): """Can a user post /api/v1/submissions if admin""" app = create_ctfd() with app.app_context(): gen_challenge(app.db) with login_as_user(app, name="admin") as client: r = client.post( "/api/v1/submissions", json={ "provided": "MARKED AS SOLVED BY ADMIN", "user_id": 1, "team_id": None, "challenge_id": "1", "type": "correct", }, ) assert r.status_code == 200 destroy_ctfd(app)
def test_scoreboard_team_score(): """Is a user's submitted flag reflected on the team's score on /scoreboard""" app = create_kmactf(user_mode="teams") with app.app_context(): user = gen_user(app.db, name="user") team = gen_team(app.db) user.team_id = team.id team.members.append(user) gen_challenge(app.db) gen_flag(app.db, 1) app.db.session.commit() with login_as_user(app) as client: flag = {"challenge_id": 1, "submission": "flag"} client.post("/api/v1/challenges/attempt", json=flag) standings = get_standings() assert standings[0][2] == "team_name" assert standings[0][3] == 100 destroy_kmactf(app)
def test_challenge_board_under_view_after_ctf(): """Test that the challenge board does not show an error under view_after_ctf""" app = create_ctfd() with app.app_context(): set_config("view_after_ctf", True) set_config("start", "1507089600" ) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST set_config( "end", "1507262400") # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST register_user(app) client = login_as_user(app) gen_challenge(app.db) gen_flag(app.db, challenge_id=1, content="flag") gen_challenge(app.db) gen_flag(app.db, challenge_id=2, content="flag") # CTF is ongoing. Normal operation. with freeze_time("2017-10-5"): r = client.get("/challenges") assert r.status_code == 200 assert "has ended" not in r.get_data(as_text=True) data = {"submission": "flag", "challenge_id": 1} r = client.post("/api/v1/challenges/attempt", json=data) assert r.status_code == 200 assert r.get_json()["data"]["status"] == "correct" assert Solves.query.count() == 1 # CTF is now over. There should be a message and challenges should show submission status but not store solves with freeze_time("2017-10-7"): r = client.get("/challenges") assert r.status_code == 200 assert "has ended" in r.get_data(as_text=True) data = {"submission": "flag", "challenge_id": 2} r = client.post("/api/v1/challenges/attempt", json=data) assert r.status_code == 200 assert r.get_json()["data"]["status"] == "correct" assert Solves.query.count() == 1 destroy_ctfd(app)
def test_import_ctf(): """Test that CTFd can import a CTF""" app = create_ctfd() if not app.config.get("SQLALCHEMY_DATABASE_URI").startswith("sqlite"): with app.app_context(): base_user = "******" for x in range(10): user = base_user + str(x) user_email = user + "@ctfd.io" gen_user(app.db, name=user, email=user_email) for x in range(9): chal = gen_challenge(app.db, name="chal_name{}".format(x)) gen_flag(app.db, challenge_id=chal.id, content="flag") chal = gen_challenge(app.db, name="chal_name10", requirements={"prerequisites": [1]}) gen_flag(app.db, challenge_id=chal.id, content="flag") app.db.session.commit() backup = export_ctf() with open("export.test_import_ctf.zip", "wb") as f: f.write(backup.read()) destroy_ctfd(app) app = create_ctfd() # TODO: These databases should work but they don't... if not app.config.get("SQLALCHEMY_DATABASE_URI").startswith("sqlite"): with app.app_context(): import_ctf("export.test_import_ctf.zip") if not app.config.get("SQLALCHEMY_DATABASE_URI").startswith( "postgres"): # TODO: Dig deeper into why Postgres fails here assert Users.query.count() == 11 assert Challenges.query.count() == 10 assert Flags.query.count() == 10 chal = Challenges.query.filter_by(name="chal_name10").first() assert chal.requirements == {"prerequisites": [1]} destroy_ctfd(app)
def test_hint_team_unlock(): """Is a user's unlocked hint reflected on other team members""" app = create_ctfd(user_mode="teams") with app.app_context(): user = gen_user(app.db) second_user = gen_user(app.db, name="user", email="*****@*****.**") team = gen_team(app.db) user.team_id = team.id second_user.team_id = team.id team.members.append(user) team.members.append(second_user) chal = gen_challenge(app.db) gen_hint(app.db, chal.id, content="hint", cost=1, type="standard") # Give the points to the user that doesn't unlock # Users that unlock hints should be able to unlock but cost their team points gen_award(app.db, user_id=3, team_id=team.id) app.db.session.commit() with login_as_user(app, name="user_name") as client: # Assert that we don't see a hint r = client.get("/api/v1/hints/1") assert r.get_json()["data"].get("content") is None # Unlock the hint client.post("/api/v1/unlocks", json={"target": 1, "type": "hints"}) # Assert that we see a hint r = client.get("/api/v1/hints/1") assert r.get_json()["data"].get("content") with login_as_user(app) as second_client: # Assert that we see a hint r = second_client.get("/api/v1/hints/1") assert r.get_json()["data"].get("content") # Assert that we can't double unlock r = second_client.post("/api/v1/unlocks", json={ "target": 1, "type": "hints" }) assert r.status_code == 400 assert (r.get_json()["errors"]["target"] == "You've already unlocked this this target") # Assert that we see a hint r = second_client.get("/api/v1/hints/1") assert r.json["data"]["content"] == "hint" # Verify standings # We start with 100 points from the award. # We lose a point because we unlock successfully once standings = get_standings() assert standings[0][2] == "team_name" assert standings[0][3] == 99 destroy_ctfd(app)
def test_api_challenge_get_solves_verified_emails(): """Can a verified email get /api/v1/challenges/<challenge_id>/solves""" app = create_ctfd() with app.app_context(): set_config('verify_emails', True) gen_challenge(app.db) gen_user(app.db, name='user_name', email='*****@*****.**', password='******', verified=True) register_user(app) client = login_as_user(app) registered_client = login_as_user(app, 'user_name', 'password') r = client.get('/api/v1/challenges/1/solves', json="") assert r.status_code == 403 r = registered_client.get('/api/v1/challenges/1/solves') assert r.status_code == 200 destroy_ctfd(app)
def test_api_flag_patch_admin(): """Can a user patch /api/v1/flags/<flag_id> if admin""" app = create_ctfd() with app.app_context(): gen_challenge(app.db) gen_flag(app.db, 1) with login_as_user(app, "admin") as client: r = client.patch( "/api/v1/flags/1", json={ "content": "flag_edit", "data": "", "type": "static", "id": "1" }, ) assert r.status_code == 200 assert r.get_json()["data"]["content"] == "flag_edit" destroy_ctfd(app)
def test_api_team_captain_disbanding_only_inactive_teams(): """Test that only teams that haven't conducted any actions can be disbanded""" app = create_ctfd(user_mode="teams") with app.app_context(): user = gen_user(app.db, name="user") team = gen_team(app.db) team.members.append(user) user.team_id = team.id team.captain_id = 2 user2 = gen_user(app.db, name="user2", email="*****@*****.**") team.members.append(user2) app.db.session.commit() gen_challenge(app.db) gen_flag(app.db, 1) gen_solve(app.db, user_id=3, team_id=1, challenge_id=1) with login_as_user(app) as client: r = client.delete("/api/v1/teams/me", json="") assert r.status_code == 403 assert r.get_json() == { "success": False, "errors": { "": [ "You cannot disband your team as it has participated in the event. " "Please contact an admin to disband your team or remove a member." ] }, } user = gen_user(app.db, name="user3", email="*****@*****.**") team = gen_team(app.db, name="team2", email="*****@*****.**") print(user.id) team.members.append(user) user.team_id = team.id team.captain_id = user.id app.db.session.commit() with login_as_user(app, name="user3") as client: r = client.delete("/api/v1/teams/me", json="") print(r.get_json()) assert r.status_code == 200 assert r.get_json() == {"success": True} destroy_ctfd(app)
def test_api_post_comments_with_invalid_author_id(): app = create_ctfd() with app.app_context(): gen_challenge(app.db) register_user(app) with login_as_user(app, "admin") as admin: r = admin.post( "/api/v1/comments", json={ "content": "this is a challenge comment", "type": "challenge", "challenge_id": 1, "author_id": 2, }, ) # Check that POST response has comment data assert r.status_code == 200 resp = r.get_json() assert resp["data"]["author_id"] == 1 destroy_ctfd(app)
def test_api_delete_comments(): app = create_ctfd() with app.app_context(): gen_challenge(app.db) with login_as_user(app, "admin") as admin: gen_comment( app.db, content="this is a challenge comment", author_id=1, challenge_id=1, ) assert Comments.query.count() == 1 # Check that the comment can be deleted r = admin.delete("/api/v1/comments/1", json="") assert r.status_code == 200 resp = r.get_json() assert Comments.query.count() == 0 assert resp["success"] is True destroy_ctfd(app)
def test_api_get_comments(): app = create_ctfd() with app.app_context(): gen_challenge(app.db) with login_as_user(app, "admin") as admin: gen_comment( app.db, content="this is a challenge comment", author_id=1, challenge_id=1, ) r = admin.get("/api/v1/comments", json="") # Check that the comment shows up in the list of all comments assert r.status_code == 200 resp = r.get_json() assert resp["data"][0]["content"] == "this is a challenge comment" assert "this is a challenge comment" in resp["data"][0]["html"] assert resp["data"][0]["type"] == "challenge" destroy_ctfd(app)
def test_api_user_get_solves_after_freze_time(): """Can a user get /api/v1/users/<user_id>/solves after freeze time""" app = create_ctfd(user_mode="users") with app.app_context(): register_user(app, name="user1", email="*****@*****.**") register_user(app, name="user2", email="*****@*****.**") # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST set_config("freeze", "1507262400") with freeze_time("2017-10-4"): chal = gen_challenge(app.db) chal_id = chal.id gen_solve(app.db, user_id=2, challenge_id=chal_id) chal2 = gen_challenge(app.db) chal2_id = chal2.id with freeze_time("2017-10-8"): chal2 = gen_solve(app.db, user_id=2, challenge_id=chal2_id) # There should now be two solves assigned to the same user. assert Solves.query.count() == 2 # User 2 should have 2 solves when seen by themselves client = login_as_user(app, name="user1") r = client.get("/api/v1/users/me/solves") data = r.get_json()["data"] assert len(data) == 2 # User 2 should have 1 solve when seen by another user client = login_as_user(app, name="user2") r = client.get("/api/v1/users/2/solves") data = r.get_json()["data"] assert len(data) == 1 # Admins should see all solves for the user admin = login_as_user(app, name="admin") r = admin.get("/api/v1/users/2/solves") data = r.get_json()["data"] assert len(data) == 2 destroy_ctfd(app)
def test_api_team_get_solves_after_freze_time(): """Can a user get /api/v1/teams/<team_id>/solves after freeze time""" app = create_ctfd(user_mode="teams") with app.app_context(): register_user(app) team = gen_team(app.db, name="team1", email="*****@*****.**", member_count=1) team_member = team.members[0] tm_name = team_member.name set_config("freeze", "1507262400") with freeze_time("2017-10-4"): chal = gen_challenge(app.db) chal_id = chal.id gen_solve(app.db, user_id=3, team_id=1, challenge_id=chal_id) chal2 = gen_challenge(app.db) chal2_id = chal2.id with freeze_time("2017-10-8"): gen_solve(app.db, user_id=3, team_id=1, challenge_id=chal2_id) assert Solves.query.count() == 2 with login_as_user(app) as client: r = client.get("/api/v1/teams/1/solves") data = r.get_json()["data"] assert len(data) == 1 with login_as_user(app, name=tm_name) as client: r = client.get("/api/v1/teams/me/solves") data = r.get_json()["data"] assert len(data) == 2 with login_as_user(app, name="admin") as client: r = client.get("/api/v1/teams/1/solves") data = r.get_json()["data"] assert len(data) == 2 destroy_ctfd(app)
def test_challenges_model_access_plugin_class(): """ Test that the Challenges model can access its plugin class """ app = create_ctfd() with app.app_context(): from CTFd.plugins.challenges import get_chal_class chal = gen_challenge(app.db) assert chal.plugin_class == get_chal_class("standard") destroy_ctfd(app)
def test_api_challenges_get_solves_score_visibility(): """Can a user get /api/v1/challenges/<challenge_id>/solves if score_visibility is public/private/admin""" app = create_ctfd() with app.app_context(): set_config("challenge_visibility", "public") set_config("score_visibility", "public") gen_challenge(app.db) with app.test_client() as client: r = client.get("/api/v1/challenges/1/solves") assert r.status_code == 200 set_config("challenge_visibility", "private") set_config("score_visibility", "private") register_user(app) private_client = login_as_user(app) r = private_client.get("/api/v1/challenges/1/solves") assert r.status_code == 200 set_config("score_visibility", "admin") admin = login_as_user(app, "admin", "password") r = admin.get("/api/v1/challenges/1/solves") assert r.status_code == 200 destroy_ctfd(app)
def test_api_challenge_get_solves_verified_emails(): """Can a verified email get /api/v1/challenges/<challenge_id>/solves""" app = create_ctfd() with app.app_context(): set_config("verify_emails", True) gen_challenge(app.db) gen_user( app.db, name="user_name", email="*****@*****.**", password="******", verified=True, ) register_user(app) client = login_as_user(app) registered_client = login_as_user(app, "user_name", "password") r = client.get("/api/v1/challenges/1/solves", json="") assert r.status_code == 403 r = registered_client.get("/api/v1/challenges/1/solves") assert r.status_code == 200 destroy_ctfd(app)
def test_anonymous_users_view_public_challenges_without_team(): """Test that if challenges are public, users without team can still view them""" app = create_ctfd(user_mode="teams") with app.app_context(): register_user(app) gen_challenge(app.db) with app.test_client() as client: r = client.get("/challenges") assert r.status_code == 302 assert r.location.startswith("http://localhost/login") set_config("challenge_visibility", "public") with app.test_client() as client: r = client.get("/challenges") assert r.status_code == 200 with login_as_user(app) as client: r = client.get("/challenges") assert r.status_code == 302 assert r.location.startswith("http://localhost/team") destroy_ctfd(app)
def test_invalid_requirements_are_rejected(): """Test that invalid requirements JSON blobs are rejected by the API""" app = create_ctfd() with app.app_context(): gen_challenge(app.db) gen_challenge(app.db) with login_as_user(app, "admin") as client: # Test None/null values r = client.patch("/api/v1/challenges/1", json={"requirements": { "prerequisites": [None] }}) assert r.status_code == 400 assert r.get_json() == { "success": False, "errors": { "requirements": ["Challenge requirements cannot have a null prerequisite"] }, } # Test empty strings r = client.patch("/api/v1/challenges/1", json={"requirements": { "prerequisites": [""] }}) assert r.status_code == 400 assert r.get_json() == { "success": False, "errors": { "requirements": ["Challenge requirements cannot have a null prerequisite"] }, } # Test a valid integer r = client.patch("/api/v1/challenges/1", json={"requirements": { "prerequisites": [2] }}) assert r.status_code == 200 destroy_ctfd(app)