CoolFace
Apppublic

vishalbhat07/ctfd

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
test_challenges.py951 linesDownload Raw Back to users
1#!/usr/bin/env python2# -*- coding: utf-8 -*-3from datetime import datetime, timedelta4 5from freezegun import freeze_time6 7from CTFd.models import Challenges, Fails, Ratelimiteds, Solves8from CTFd.utils import set_config, text_type9from tests.helpers import (10    create_ctfd,11    destroy_ctfd,12    gen_challenge,13    gen_fail,14    gen_flag,15    gen_hint,16    login_as_user,17    register_user,18)19 20 21def test_user_get_challenges():22    """23    Can a registered user load /challenges24    """25    app = create_ctfd()26    with app.app_context():27        register_user(app)28        client = login_as_user(app)29        r = client.get("/challenges")30        assert r.status_code == 20031    destroy_ctfd(app)32 33 34def test_user_get_chals():35    """36    Can a registered user load /chals37    """38    app = create_ctfd()39    with app.app_context():40        register_user(app)41        client = login_as_user(app)42        r = client.get("/api/v1/challenges")43        assert r.status_code == 20044    destroy_ctfd(app)45 46 47def test_viewing_challenges():48    """49    Test that users can see added challenges50    """51    app = create_ctfd()52    with app.app_context():53        register_user(app)54        client = login_as_user(app)55        gen_challenge(app.db)56        r = client.get("/api/v1/challenges")57        chals = r.get_json()["data"]58        assert len(chals) == 159    destroy_ctfd(app)60 61 62def test_viewing_challenge():63    """Test that users can see individual challenges"""64    app = create_ctfd()65    with app.app_context():66        register_user(app)67        client = login_as_user(app)68        gen_challenge(app.db)69        r = client.get("/api/v1/challenges/1")70        assert r.get_json()71    destroy_ctfd(app)72 73 74# def test_chals_solves():75#     """Test that the /chals/solves endpoint works properly"""76#     app = create_ctfd()77#     with app.app_context():78#         # Generate 5 users79#         for c in range(1, 6):80#             name = "user{}".format(c)81#             email = "user{}@examplectf.com".format(c)82#             register_user(app, name=name, email=email, password="password")83#84#         # Generate 5 challenges85#         for c in range(6):86#             chal1 = gen_challenge(app.db, value=100)87#88#         user_ids = list(range(2, 7))89#         chal_ids = list(range(1, 6))90#         for u in user_ids:91#             for c in chal_ids:92#                 gen_solve(app.db, teamid=u, chalid=c)93#             chal_ids.pop()94#95#         client = login_as_user(app, name="user1")96#97#         with client.session_transaction() as sess:98#             r = client.get('/chals/solves')99#             output = r.get_data(as_text=True)100#             saved = json.loads('''{101#               "1": 5,102#               "2": 4,103#               "3": 3,104#               "4": 2,105#               "5": 1,106#               "6": 0107#             }108#             ''')109#             received = json.loads(output)110#             assert saved == received111#         set_config('hide_scores', True)112#         with client.session_transaction():113#             r = client.get('/chals/solves')114#             output = r.get_data(as_text=True)115#             saved = json.loads('''{116#               "1": -1,117#               "2": -1,118#               "3": -1,119#               "4": -1,120#               "5": -1,121#               "6": -1122#             }123#             ''')124#             received = json.loads(output)125#             assert saved == received126#     destroy_ctfd(app)127 128 129def test_submitting_correct_flag():130    """Test that correct flags are correct"""131    app = create_ctfd()132    with app.app_context():133        register_user(app)134        client = login_as_user(app)135        chal = gen_challenge(app.db)136        gen_flag(app.db, challenge_id=chal.id, content="flag")137        data = {"submission": "flag", "challenge_id": chal.id}138        r = client.post("/api/v1/challenges/attempt", json=data)139        assert r.status_code == 200140        resp = r.get_json()["data"]141        assert resp.get("status") == "correct"142        assert resp.get("message") == "Correct"143    destroy_ctfd(app)144 145 146def test_submitting_correct_static_case_insensitive_flag():147    """Test that correct static flags are correct if the static flag is marked case_insensitive"""148    app = create_ctfd()149    with app.app_context():150        register_user(app)151        client = login_as_user(app)152        chal = gen_challenge(app.db)153        gen_flag(app.db, challenge_id=chal.id, content="flag", data="case_insensitive")154        data = {"submission": "FLAG", "challenge_id": chal.id}155        r = client.post("/api/v1/challenges/attempt", json=data)156        assert r.status_code == 200157        resp = r.get_json()["data"]158        assert resp.get("status") == "correct"159        assert resp.get("message") == "Correct"160    destroy_ctfd(app)161 162 163def test_submitting_correct_regex_case_insensitive_flag():164    """Test that correct regex flags are correct if the regex flag is marked case_insensitive"""165    app = create_ctfd()166    with app.app_context():167        register_user(app)168        client = login_as_user(app)169        chal = gen_challenge(app.db)170        gen_flag(171            app.db,172            challenge_id=chal.id,173            type="regex",174            content="flag",175            data="case_insensitive",176        )177        data = {"submission": "FLAG", "challenge_id": chal.id}178        r = client.post("/api/v1/challenges/attempt", json=data)179        assert r.status_code == 200180        resp = r.get_json()["data"]181        assert resp.get("status") == "correct"182        assert resp.get("message") == "Correct"183    destroy_ctfd(app)184 185 186def test_submitting_invalid_regex_flag():187    """Test that invalid regex flags are errored out to the user"""188    app = create_ctfd()189    with app.app_context():190        register_user(app)191        client = login_as_user(app)192        chal = gen_challenge(app.db)193        gen_flag(194            app.db,195            challenge_id=chal.id,196            type="regex",197            content="**",198            data="case_insensitive",199        )200        data = {"submission": "FLAG", "challenge_id": chal.id}201        r = client.post("/api/v1/challenges/attempt", json=data)202        assert r.status_code == 200203        resp = r.get_json()["data"]204        assert resp.get("status") == "incorrect"205        assert resp.get("message") == "Regex parse error occured"206    destroy_ctfd(app)207 208 209def test_submitting_incorrect_flag():210    """Test that incorrect flags are incorrect"""211    app = create_ctfd()212    with app.app_context():213        register_user(app)214        client = login_as_user(app)215        chal = gen_challenge(app.db)216        gen_flag(app.db, challenge_id=chal.id, content="flag")217        data = {"submission": "notflag", "challenge_id": chal.id}218        r = client.post("/api/v1/challenges/attempt", json=data)219        assert r.status_code == 200220        resp = r.get_json()["data"]221        assert resp.get("status") == "incorrect"222        assert resp.get("message") == "Incorrect"223    destroy_ctfd(app)224 225 226def test_submitting_unicode_flag():227    """Test that users can submit a unicode flag"""228    app = create_ctfd()229    with app.app_context():230        register_user(app)231        client = login_as_user(app)232        chal = gen_challenge(app.db)233        gen_flag(app.db, challenge_id=chal.id, content="你好")234        with client.session_transaction():235            data = {"submission": "你好", "challenge_id": chal.id}236        r = client.post("/api/v1/challenges/attempt", json=data)237        assert r.status_code == 200238        resp = r.get_json()["data"]239        assert resp.get("status") == "correct"240        assert resp.get("message") == "Correct"241    destroy_ctfd(app)242 243 244def test_challenges_with_max_attempts():245    """Test that users are locked out of a challenge after they reach max_attempts"""246    app = create_ctfd()247    with app.app_context():248        register_user(app)249        client = login_as_user(app)250        chal = gen_challenge(app.db)251        chal = Challenges.query.filter_by(id=chal.id).first()252        chal_id = chal.id253        chal.max_attempts = 3254        app.db.session.commit()255 256        gen_flag(app.db, challenge_id=chal.id, content="flag")257        for _ in range(3):258            data = {"submission": "notflag", "challenge_id": chal_id}259            r = client.post("/api/v1/challenges/attempt", json=data)260 261        wrong_keys = Fails.query.count()262        assert wrong_keys == 3263 264        data = {"submission": "flag", "challenge_id": chal_id}265        r = client.post("/api/v1/challenges/attempt", json=data)266        assert r.status_code == 403267 268        resp = r.get_json()["data"]269        assert resp.get("status") == "ratelimited"270        assert resp.get("message") == "Not accepted. You have 0 tries remaining"271 272        solves = Solves.query.count()273        assert solves == 0274    destroy_ctfd(app)275 276 277def test_challenges_with_max_attempts_timeout_behavior():278    """Test that users are temporarily locked out of a challenge after reaching max_attempts with timeout behavior"""279 280    app = create_ctfd()281    with app.app_context():282        set_config("max_attempts_behavior", "timeout")283        set_config("max_attempts_timeout", 300)  # 300 seconds timeout for test284 285        register_user(app)286        client = login_as_user(app)287        chal = gen_challenge(app.db)288        chal = Challenges.query.filter_by(id=chal.id).first()289        chal_id = chal.id290        chal.max_attempts = 2291        app.db.session.commit()292 293        gen_flag(app.db, challenge_id=chal.id, content="flag")294        for _ in range(2):295            data = {"submission": "notflag", "challenge_id": chal_id}296            r = client.post("/api/v1/challenges/attempt", json=data)297            assert r.status_code == 200298 299        # Should be locked out now300        with freeze_time(timedelta(seconds=0)):301            data = {"submission": "flag", "challenge_id": chal_id}302            r = client.post("/api/v1/challenges/attempt", json=data)303            assert r.status_code == 429304            resp = r.get_json()["data"]305            assert resp.get("status") == "ratelimited"306            assert "Not accepted. Try again in 300 seconds" in resp.get("message")307 308        # Use freeze_time to advance time by 290 seconds309        with freeze_time(timedelta(seconds=290)):310            data = {"submission": "flag", "challenge_id": chal_id}311            r = client.post("/api/v1/challenges/attempt", json=data)312            assert r.status_code == 429313            resp = r.get_json()["data"]314            assert resp.get("status") == "ratelimited"315            assert "Not accepted. Try again in 10 seconds" in resp.get("message")316 317        # Use freeze_time to advance time by 301 seconds318        with freeze_time(timedelta(seconds=301)):319            data = {"submission": "flag", "challenge_id": chal_id}320            r = client.post("/api/v1/challenges/attempt", json=data)321            assert r.status_code == 200322            resp = r.get_json()["data"]323            # Should be correct now324            assert resp.get("status") == "correct"325            assert resp.get("message") == "Correct"326    destroy_ctfd(app)327 328 329def test_challenges_with_max_attempts_timeout_ratelimit():330    """Test that max_attempts timeout ratelimit and global ratelimit work together correctly"""331    app = create_ctfd()332    with app.app_context():333        set_config("max_attempts_behavior", "timeout")334        set_config("max_attempts_timeout", 30)  # 30 seconds timeout for test335 336        register_user(app)337        client = login_as_user(app)338 339        # Challenge 1 with max_attempts = 5340        chal1 = gen_challenge(app.db)341        chal1_obj = Challenges.query.filter_by(id=chal1.id).first()342        chal1_obj.max_attempts = 5343        app.db.session.commit()344        gen_flag(app.db, challenge_id=chal1.id, content="flag1")345 346        # Challenge 2 with no max_attempts347        chal2 = gen_challenge(app.db)348        gen_flag(app.db, challenge_id=chal2.id, content="flag2")349 350        base_time = datetime.utcnow()351 352        # Submit 5 wrong attempts to challenge 1 (triggers max_attempts ratelimit)353        with freeze_time(base_time):354            for _ in range(5):355                data = {"submission": "wrong", "challenge_id": chal1.id}356                r = client.post("/api/v1/challenges/attempt", json=data)357                assert r.status_code == 200358 359            # 6th attempt should be blocked by max_attempts timeout360            data = {"submission": "flag1", "challenge_id": chal1.id}361            r = client.post("/api/v1/challenges/attempt", json=data)362            assert r.status_code == 429363            resp = r.get_json()["data"]364            assert resp.get("status") == "ratelimited"365            assert "Try again in 30 seconds" in resp.get("message")366 367            # Now submit 5 more wrong attempts to challenge 2 (total 10 fails, triggers global ratelimit)368            for i in range(6):369                data = {"submission": "wrong", "challenge_id": chal2.id}370                r = client.post("/api/v1/challenges/attempt", json=data)371                if i < 5:372                    assert r.status_code == 200373                else:374                    # 11th attempt should be blocked by global ratelimit (60 seconds)375                    assert r.status_code == 429376                    resp = r.get_json()["data"]377                    assert resp.get("status") == "ratelimited"378                    assert "You're submitting flags too fast" in resp.get("message")379 380            # Check counts381            wrong_keys = Fails.query.count()382            ratelimiteds = Ratelimiteds.query.count()383            assert wrong_keys == 10384            assert (385                ratelimiteds == 2386            )  # One max_attempts ratelimit + one global ratelimit387 388        # After 30 seconds, max_attempts timeout should release but global ratelimit (60s) still active389        with freeze_time(base_time + timedelta(seconds=31)):390            # Try challenge 1 - should still be blocked by global ratelimit391            data = {"submission": "flag1", "challenge_id": chal1.id}392            r = client.post("/api/v1/challenges/attempt", json=data)393            assert r.status_code == 429394            resp = r.get_json()["data"]395            assert resp.get("status") == "ratelimited"396            assert "Try again in 30 seconds" in resp.get("message")397 398            ratelimiteds = Ratelimiteds.query.count()399            assert ratelimiteds == 3  # Another ratelimit entry400 401        # After 60 seconds, both ratelimits should be released402        with freeze_time(base_time + timedelta(seconds=61)):403            # Should be able to solve challenge 1 now404            data = {"submission": "flag1", "challenge_id": chal1.id}405            r = client.post("/api/v1/challenges/attempt", json=data)406            assert r.status_code == 200407            resp = r.get_json()["data"]408            assert resp.get("status") == "correct"409            assert resp.get("message") == "Correct"410 411            # Verify solve was recorded412            solves = Solves.query.count()413            assert solves == 1414    destroy_ctfd(app)415 416 417def test_challenges_max_attempts_timeout_config_change():418    """Test that changing max_attempts_timeout resets attempt count because cache key changes"""419    app = create_ctfd()420    with app.app_context():421        set_config("max_attempts_behavior", "timeout")422        set_config("max_attempts_timeout", 300)  # Start with 300 seconds423 424        register_user(app)425        client = login_as_user(app)426        chal = gen_challenge(app.db)427        chal = Challenges.query.filter_by(id=chal.id).first()428        chal_id = chal.id429        chal.max_attempts = 3430        app.db.session.commit()431 432        gen_flag(app.db, challenge_id=chal.id, content="flag")433 434        # Make 3 wrong attempts (hit the limit)435        for _ in range(3):436            data = {"submission": "notflag", "challenge_id": chal_id}437            r = client.post("/api/v1/challenges/attempt", json=data)438            assert r.status_code == 200439 440        # Verify we're locked out441        data = {"submission": "flag", "challenge_id": chal_id}442        r = client.post("/api/v1/challenges/attempt", json=data)443        assert r.status_code == 429444        resp = r.get_json()["data"]445        assert resp.get("status") == "ratelimited"446        assert "300 seconds" in resp.get("message")447 448        # Change the max_attempts_timeout config (this changes the cache key)449        set_config("max_attempts_timeout", 30)  # Change to 30 seconds450 451        # Jump forward in time to ensure we're past the new 30 second rate limit452        with freeze_time(timedelta(seconds=35)):453            # Now we should be able to submit again because the cache key changed454            # Old submissions have also fallen off the 30 second window455            data = {"submission": "flag", "challenge_id": chal_id}456            r = client.post("/api/v1/challenges/attempt", json=data)457            assert r.status_code == 200458            resp = r.get_json()["data"]459            assert resp.get("status") == "correct"460            assert resp.get("message") == "Correct"461 462        # Verify solve was recorded463        solves = Solves.query.count()464        assert solves == 1465 466        # Verify the fail count is still accurate (3 fails from before)467        fails = Fails.query.count()468        assert fails == 3469 470        ratelimiteds = Ratelimiteds.query.count()471        assert ratelimiteds == 1472    destroy_ctfd(app)473 474 475def test_challenge_kpm_limit_no_freeze():476    """Test that users are properly ratelimited when submitting flags"""477    app = create_ctfd()478    with app.app_context():479        register_user(app)480        client = login_as_user(app)481        chal = gen_challenge(app.db)482        chal_id = chal.id483 484        gen_flag(app.db, challenge_id=chal.id, content="flag")485        for _ in range(11):486            with client.session_transaction():487                data = {"submission": "notflag", "challenge_id": chal_id}488            client.post("/api/v1/challenges/attempt", json=data)489 490        wrong_keys = Fails.query.count()491        ratelimiteds = Ratelimiteds.query.count()492        assert wrong_keys == 10493        assert ratelimiteds == 1494 495        # We just want a consistent flag response countdown496        with freeze_time(timedelta(seconds=0)):497            data = {"submission": "flag", "challenge_id": chal_id}498            r = client.post("/api/v1/challenges/attempt", json=data)499            assert r.status_code == 429500 501            wrong_keys = Fails.query.count()502            ratelimiteds = Ratelimiteds.query.count()503            assert wrong_keys == 10504            assert ratelimiteds == 2505 506            resp = r.get_json()["data"]507            assert resp.get("status") == "ratelimited"508            assert (509                resp.get("message")510                == "You're submitting flags too fast. Try again in 60 seconds."511            )512 513        solves = Solves.query.count()514        assert solves == 0515    destroy_ctfd(app)516 517 518def test_challenge_kpm_limit_freeze_time():519    """Test that users are properly ratelimited when submitting flags"""520    app = create_ctfd()521    with app.app_context():522        register_user(app)523        client = login_as_user(app)524        chal = gen_challenge(app.db)525        chal_id = chal.id526 527        gen_flag(app.db, challenge_id=chal.id, content="flag")528        base_time = datetime.utcnow()529 530        # First section: Use API to generate 10 fails + 1 ratelimit531        with freeze_time(base_time):532            for _ in range(11):533                with client.session_transaction():534                    data = {"submission": "notflag", "challenge_id": chal_id}535                r = client.post("/api/v1/challenges/attempt", json=data)536 537            wrong_keys = Fails.query.count()538            ratelimiteds = Ratelimiteds.query.count()539            assert wrong_keys == 10540            assert ratelimiteds == 1541 542        # Within the 1 min time frame we should still be ratelimited543        with freeze_time(base_time + timedelta(seconds=11)):544            data = {"submission": "flag", "challenge_id": chal_id}545            r = client.post("/api/v1/challenges/attempt", json=data)546            assert r.status_code == 429547 548            wrong_keys = Fails.query.count()549            ratelimiteds = Ratelimiteds.query.count()550            assert wrong_keys == 10551            assert ratelimiteds == 2552 553            resp = r.get_json()["data"]554            assert resp.get("status") == "ratelimited"555            assert (556                resp.get("message")557                == "You're submitting flags too fast. Try again in 50 seconds."558            )559 560        # Generate 10 more fails at +60 seconds using gen_fail because freezegun cannot patch to sqlalchemy's default561        for _ in range(10):562            fail = gen_fail(app.db, user_id=2, challenge_id=chal_id, provided="notflag")563            fail.date = base_time + timedelta(seconds=60)564            app.db.session.commit()565 566        # The 11th attempt via API should trigger another ratelimit567        with freeze_time(base_time + timedelta(seconds=60)):568            data = {"submission": "notflag", "challenge_id": chal_id}569            client.post("/api/v1/challenges/attempt", json=data)570 571            wrong_keys = Fails.query.count()572            ratelimiteds = Ratelimiteds.query.count()573            assert wrong_keys == 20574            assert ratelimiteds == 3575    destroy_ctfd(app)576 577 578def test_that_view_challenges_unregistered_works():579    """Test that view_challenges_unregistered works"""580    app = create_ctfd()581    with app.app_context():582        chal = gen_challenge(app.db, name=text_type("🐺"))583        chal_id = chal.id584        gen_hint(app.db, chal_id)585 586        client = app.test_client()587        r = client.get("/api/v1/challenges", json="")588        assert r.status_code == 403589        r = client.get("/api/v1/challenges")590        assert r.status_code == 302591 592        set_config("challenge_visibility", "public")593 594        client = app.test_client()595        r = client.get("/api/v1/challenges")596        assert r.get_json()["data"]597 598        r = client.get("/api/v1/challenges/1/solves")599        assert r.get_json().get("data") is not None600 601        data = {"submission": "not_flag", "challenge_id": chal_id}602        r = client.post("/api/v1/challenges/attempt", json=data)603        assert r.status_code == 403604        assert r.get_json().get("data").get("status") == "authentication_required"605        assert r.get_json().get("data").get("message") is None606    destroy_ctfd(app)607 608 609def test_hidden_challenge_is_unreachable():610    """Test that hidden challenges return 404 and do not insert a solve or wrong key"""611    app = create_ctfd()612    with app.app_context():613        register_user(app)614        client = login_as_user(app)615        chal = gen_challenge(app.db, state="hidden")616        gen_flag(app.db, challenge_id=chal.id, content="flag")617        chal_id = chal.id618 619        assert Challenges.query.count() == 1620 621        r = client.get("/api/v1/challenges", json="")622        data = r.get_json().get("data")623        assert data == []624 625        r = client.get("/api/v1/challenges/1", json="")626        assert r.status_code == 404627        data = r.get_json().get("data")628        assert data is None629 630        data = {"submission": "flag", "challenge_id": chal_id}631 632        r = client.post("/api/v1/challenges/attempt", json=data)633        assert r.status_code == 404634 635        r = client.post("/api/v1/challenges/attempt?preview=true", json=data)636        assert r.status_code == 404637        assert r.get_json().get("data") is None638 639        solves = Solves.query.count()640        assert solves == 0641 642        wrong_keys = Fails.query.count()643        assert wrong_keys == 0644    destroy_ctfd(app)645 646 647def test_hidden_challenge_is_unsolveable():648    """Test that hidden challenges return 404 and do not insert a solve or wrong key"""649    app = create_ctfd()650    with app.app_context():651        register_user(app)652        client = login_as_user(app)653        chal = gen_challenge(app.db, state="hidden")654        gen_flag(app.db, challenge_id=chal.id, content="flag")655 656        data = {"submission": "flag", "challenge_id": chal.id}657 658        r = client.post("/api/v1/challenges/attempt", json=data)659        assert r.status_code == 404660 661        solves = Solves.query.count()662        assert solves == 0663 664        wrong_keys = Fails.query.count()665        assert wrong_keys == 0666    destroy_ctfd(app)667 668 669def test_invalid_requirements_are_rejected():670    """Test that invalid requirements JSON blobs are rejected by the API"""671    app = create_ctfd()672    with app.app_context():673        gen_challenge(app.db)674        gen_challenge(app.db)675        with login_as_user(app, "admin") as client:676            # Test None/null values677            r = client.patch(678                "/api/v1/challenges/1", json={"requirements": {"prerequisites": [None]}}679            )680            assert r.status_code == 400681            assert r.get_json() == {682                "success": False,683                "errors": {684                    "requirements": [685                        "Challenge requirements cannot have a null prerequisite"686                    ]687                },688            }689            # Test empty strings690            r = client.patch(691                "/api/v1/challenges/1", json={"requirements": {"prerequisites": [""]}}692            )693            assert r.status_code == 400694            assert r.get_json() == {695                "success": False,696                "errors": {697                    "requirements": [698                        "Challenge requirements cannot have a null prerequisite"699                    ]700                },701            }702            # Test a valid integer703            r = client.patch(704                "/api/v1/challenges/1", json={"requirements": {"prerequisites": [2]}}705            )706            assert r.status_code == 200707    destroy_ctfd(app)708 709 710def test_challenge_with_requirements_is_unsolveable():711    """Test that a challenge with a requirement is unsolveable without first solving the requirement"""712    app = create_ctfd()713    with app.app_context():714        register_user(app)715        client = login_as_user(app)716        chal1 = gen_challenge(app.db)717        gen_flag(app.db, challenge_id=chal1.id, content="flag")718 719        requirements = {"prerequisites": [1]}720        chal2 = gen_challenge(app.db, requirements=requirements)721        app.db.session.commit()722 723        gen_flag(app.db, challenge_id=chal2.id, content="flag")724 725        r = client.get("/api/v1/challenges")726        challenges = r.get_json()["data"]727        assert len(challenges) == 1728        assert challenges[0]["id"] == 1729 730        r = client.get("/api/v1/challenges/2")731        assert r.status_code == 403732        assert r.get_json().get("data") is None733 734        # Attempt to solve hidden Challenge 2735        data = {"submission": "flag", "challenge_id": 2}736        r = client.post("/api/v1/challenges/attempt", json=data)737        assert r.status_code == 403738        assert r.get_json().get("data") is None739 740        # Solve Challenge 1741        data = {"submission": "flag", "challenge_id": 1}742        r = client.post("/api/v1/challenges/attempt", json=data)743        resp = r.get_json()["data"]744        assert resp["status"] == "correct"745 746        # Challenge 2 should now be visible747        r = client.get("/api/v1/challenges")748        challenges = r.get_json()["data"]749        assert len(challenges) == 2750 751        r = client.get("/api/v1/challenges/2")752        assert r.status_code == 200753        assert r.get_json().get("data")["id"] == 2754 755        # Attempt to solve the now-visible Challenge 2756        data = {"submission": "flag", "challenge_id": 2}757        r = client.post("/api/v1/challenges/attempt", json=data)758        assert r.status_code == 200759        assert resp["status"] == "correct"760 761    destroy_ctfd(app)762 763 764def test_challenges_cannot_be_solved_while_paused():765    """Test that challenges cannot be solved when the CTF is paused"""766    app = create_ctfd()767    with app.app_context():768        set_config("paused", True)769 770        register_user(app)771        client = login_as_user(app)772 773        r = client.get("/challenges")774        assert r.status_code == 200775 776        # Assert that there is a paused message777        data = r.get_data(as_text=True)778        assert "paused" in data779 780        chal = gen_challenge(app.db)781        gen_flag(app.db, challenge_id=chal.id, content="flag")782 783        data = {"submission": "flag", "challenge_id": chal.id}784        r = client.post("/api/v1/challenges/attempt", json=data)785 786        # Assert that the JSON message is correct787        resp = r.get_json()["data"]788        assert r.status_code == 403789        assert resp["status"] == "paused"790        assert resp["message"] == "CTFd is paused"791 792        # There are no solves saved793        solves = Solves.query.count()794        assert solves == 0795 796        # There are no wrong keys saved797        wrong_keys = Fails.query.count()798        assert wrong_keys == 0799    destroy_ctfd(app)800 801 802def test_challenge_board_under_view_after_ctf():803    """Test that the challenge board does not show an error under view_after_ctf"""804    app = create_ctfd()805    with app.app_context():806        set_config("view_after_ctf", True)807        set_config(808            "start", "1507089600"809        )  # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST810        set_config(811            "end", "1507262400"812        )  # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST813 814        register_user(app)815        client = login_as_user(app)816 817        gen_challenge(app.db)818        gen_flag(app.db, challenge_id=1, content="flag")819 820        gen_challenge(app.db)821        gen_flag(app.db, challenge_id=2, content="flag")822 823        # CTF hasn't started yet. There should be an error message.824        with freeze_time("2017-10-3"):825            r = client.get("/challenges")826            assert r.status_code == 403827            assert "has not started yet" in r.get_data(as_text=True)828 829            data = {"submission": "flag", "challenge_id": 2}830            r = client.post("/api/v1/challenges/attempt", json=data)831            assert r.status_code == 403832            assert Solves.query.count() == 0833 834        # CTF is ongoing. Normal operation.835        with freeze_time("2017-10-5"):836            r = client.get("/challenges")837            assert r.status_code == 200838            assert "has ended" not in r.get_data(as_text=True)839 840            data = {"submission": "flag", "challenge_id": 1}841            r = client.post("/api/v1/challenges/attempt", json=data)842            assert r.status_code == 200843            assert r.get_json()["data"]["status"] == "correct"844            assert Solves.query.count() == 1845 846        # CTF is now over. There should be a message and challenges should show submission status but not store solves847        with freeze_time("2017-10-7"):848            r = client.get("/challenges")849            assert r.status_code == 200850            assert "has ended" in r.get_data(as_text=True)851 852            data = {"submission": "flag", "challenge_id": 2}853            r = client.post("/api/v1/challenges/attempt", json=data)854            assert r.status_code == 200855            assert r.get_json()["data"]["status"] == "correct"856            assert Solves.query.count() == 1857    destroy_ctfd(app)858 859 860def test_challenges_under_view_after_ctf():861    app = create_ctfd()862    with app.app_context(), freeze_time("2017-10-7"):863        set_config(864            "start", "1507089600"865        )  # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST866        set_config(867            "end", "1507262400"868        )  # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST869 870        register_user(app)871        client = login_as_user(app)872 873        gen_challenge(app.db)874        gen_flag(app.db, challenge_id=1, content="flag")875 876        r = client.get("/challenges")877        assert r.status_code == 403878 879        r = client.get("/api/v1/challenges")880        assert r.status_code == 403881        assert r.get_json().get("data") is None882 883        r = client.get("/api/v1/challenges/1")884        assert r.status_code == 403885        assert r.get_json().get("data") is None886 887        data = {"submission": "flag", "challenge_id": 1}888        r = client.post("/api/v1/challenges/attempt", json=data)889        assert r.status_code == 403890        assert r.get_json().get("data") is None891        assert Solves.query.count() == 0892 893        data = {"submission": "notflag", "challenge_id": 1}894        r = client.post("/api/v1/challenges/attempt", json=data)895        assert r.status_code == 403896        assert r.get_json().get("data") is None897        assert Fails.query.count() == 0898 899        set_config("view_after_ctf", True)900 901        r = client.get("/challenges")902        assert r.status_code == 200903 904        r = client.get("/api/v1/challenges")905        assert r.status_code == 200906        assert r.get_json()["data"][0]["id"] == 1907 908        r = client.get("/api/v1/challenges/1")909        assert r.status_code == 200910        assert r.get_json()["data"]["id"] == 1911 912        data = {"submission": "flag", "challenge_id": 1}913        r = client.post("/api/v1/challenges/attempt", json=data)914        assert r.status_code == 200915        assert r.get_json()["data"]["status"] == "correct"916        assert Solves.query.count() == 0917 918        data = {"submission": "notflag", "challenge_id": 1}919        r = client.post("/api/v1/challenges/attempt", json=data)920        assert r.status_code == 200921        assert r.get_json()["data"]["status"] == "incorrect"922        assert Fails.query.count() == 0923 924    destroy_ctfd(app)925 926 927def test_challenges_admin_only_as_user():928    app = create_ctfd()929    with app.app_context():930        set_config("challenge_visibility", "admins")931 932        register_user(app)933        admin = login_as_user(app, name="admin")934 935        gen_challenge(app.db)936        gen_flag(app.db, challenge_id=1, content="flag")937 938        r = admin.get("/challenges")939        assert r.status_code == 200940 941        r = admin.get("/api/v1/challenges", json="")942        assert r.status_code == 200943 944        r = admin.get("/api/v1/challenges/1", json="")945        assert r.status_code == 200946 947        data = {"submission": "flag", "challenge_id": 1}948        r = admin.post("/api/v1/challenges/attempt", json=data)949        assert r.status_code == 200950    destroy_ctfd(app)951