CoolFace
Apppublic

vishalbhat07/ctfd

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
test_auth.py838 linesDownload Raw Back to users
1#!/usr/bin/env python2# -*- coding: utf-8 -*-3 4from unittest.mock import patch5 6from freezegun import freeze_time7 8from CTFd.models import Users, db9from CTFd.utils import get_config, set_config10from CTFd.utils.crypto import verify_password11from tests.helpers import create_ctfd, destroy_ctfd, login_as_user, register_user12 13 14def test_register_user():15    """Can a user be registered"""16    app = create_ctfd()17    with app.app_context():18        register_user(app)19        user_count = Users.query.count()20        assert user_count == 2  # There's the admin user and the created user21    destroy_ctfd(app)22 23 24def test_register_unicode_user():25    """Can a user with a unicode name be registered"""26    app = create_ctfd()27    with app.app_context():28        register_user(app, name="你好")29        user_count = Users.query.count()30        assert user_count == 2  # There's the admin user and the created user31    destroy_ctfd(app)32 33 34def test_register_duplicate_username():35    """A user shouldn't be able to use an already registered team name"""36    app = create_ctfd()37    with app.app_context():38        register_user(39            app,40            name="user1",41            email="user1@examplectf.com",42            password="password",43            raise_for_error=False,44        )45        register_user(46            app,47            name="user1",48            email="user2@examplectf.com",49            password="password",50            raise_for_error=False,51        )52        register_user(53            app,54            name="admin  ",55            email="admin2@examplectf.com",56            password="password",57            raise_for_error=False,58        )59        user_count = Users.query.count()60        assert user_count == 2  # There's the admin user and the first created user61    destroy_ctfd(app)62 63 64def test_register_duplicate_email():65    """A user shouldn't be able to use an already registered email address"""66    app = create_ctfd()67    with app.app_context():68        register_user(69            app,70            name="user1",71            email="user1@examplectf.com",72            password="password",73            raise_for_error=False,74        )75        register_user(76            app,77            name="user2",78            email="user1@examplectf.com",79            password="password",80            raise_for_error=False,81        )82        user_count = Users.query.count()83        assert user_count == 2  # There's the admin user and the first created user84    destroy_ctfd(app)85 86 87def test_register_whitelisted_email():88    """A user shouldn't be able to register with an email that isn't on the whitelist"""89    app = create_ctfd()90    with app.app_context():91        set_config(92            "domain_whitelist", "whitelisted.com, whitelisted.org, whitelisted.net"93        )94        register_user(95            app, name="not_whitelisted", email="user@nope.com", raise_for_error=False96        )97        assert Users.query.count() == 198 99        register_user(app, name="user1", email="user@whitelisted.com")100        assert Users.query.count() == 2101 102        register_user(app, name="user2", email="user@whitelisted.org")103        assert Users.query.count() == 3104 105        register_user(app, name="user3", email="user@whitelisted.net")106        assert Users.query.count() == 4107    destroy_ctfd(app)108 109 110def test_register_blacklisted_email():111    """A user shouldn't be able to register with an email that is on the blacklist"""112    app = create_ctfd()113    with app.app_context():114        set_config(115            "domain_blacklist", "blacklisted.com, blacklisted.org, blacklisted.net"116        )117        register_user(118            app, name="blacklisted", email="user@blacklisted.com", raise_for_error=False119        )120        assert Users.query.count() == 1121 122        register_user(app, name="user1", email="user@yep.com")123        assert Users.query.count() == 2124 125        register_user(app, name="user2", email="user@yay.org")126        assert Users.query.count() == 3127 128        register_user(app, name="user3", email="user@yipee.net")129        assert Users.query.count() == 4130    destroy_ctfd(app)131 132 133def test_user_bad_login():134    """A user should not be able to login with an incorrect password"""135    app = create_ctfd()136    with app.app_context():137        register_user(app)138        client = login_as_user(139            app, name="user", password="wrong_password", raise_for_error=False140        )141        with client.session_transaction() as sess:142            assert sess.get("id") is None143        r = client.get("/profile")144        assert r.location.startswith("/login")  # We got redirected to login145    destroy_ctfd(app)146 147 148def test_user_login():149    """Can a registered user can login"""150    app = create_ctfd()151    with app.app_context():152        register_user(app)153        client = login_as_user(app)154        r = client.get("/profile")155        assert r.location is None  # We didn't get redirected to login156        assert r.status_code == 200157    destroy_ctfd(app)158 159 160def test_user_login_with_email():161    """Can a registered user can login with an email address instead of a team name"""162    app = create_ctfd()163    with app.app_context():164        register_user(app)165        client = login_as_user(app, name="user@examplectf.com", password="password")166        r = client.get("/profile")167        assert r.location is None  # We didn't get redirected to login168        assert r.status_code == 200169    destroy_ctfd(app)170 171 172def test_user_get_logout():173    """Can a registered user load /logout"""174    app = create_ctfd()175    with app.app_context():176        register_user(app)177        client = login_as_user(app)178        client.get("/logout", follow_redirects=True)179        r = client.get("/challenges")180        assert r.location == "/login?next=%2Fchallenges%3F"181        assert r.status_code == 302182    destroy_ctfd(app)183 184 185def test_user_isnt_admin():186    """A registered user cannot access admin pages"""187    app = create_ctfd()188    with app.app_context():189        register_user(app)190        client = login_as_user(app)191        for page in [192            "pages",193            "users",194            "teams",195            "scoreboard",196            "challenges",197            "statistics",198            "config",199        ]:200            r = client.get("/admin/{}".format(page))201            assert r.location.startswith("/login?next=")202            assert r.status_code == 302203    destroy_ctfd(app)204 205 206def test_expired_confirmation_links():207    """Test that expired confirmation links are reported to the user"""208    app = create_ctfd()209    with app.app_context():210        set_config("mail_server", "localhost")211        set_config("mail_port", 25)212        set_config("mail_useauth", True)213        set_config("mail_username", "username")214        set_config("mail_password", "password")215        set_config("verify_emails", True)216 217        register_user(app, email="user@user.com")218        client = login_as_user(app, name="user", password="password")219 220        # user@user.com "2012-01-14 03:21:34"221        confirm_link = (222            "http://localhost/confirm/bb8a8526146e50778b211ae63074595880edbc0b"223        )224        r = client.get(confirm_link)225 226        assert (227            "Your confirmation link is invalid, please generate a new one"228            in r.get_data(as_text=True)229        )230        user = Users.query.filter_by(email="user@user.com").first()231        assert user.verified is not True232    destroy_ctfd(app)233 234 235def test_invalid_confirmation_links():236    """Test that invalid confirmation links are reported to the user"""237    app = create_ctfd()238    with app.app_context():239        set_config("mail_server", "localhost")240        set_config("mail_port", 25)241        set_config("mail_useauth", True)242        set_config("mail_username", "username")243        set_config("mail_password", "password")244        set_config("verify_emails", True)245 246        register_user(app, email="user@user.com")247        client = login_as_user(app, name="user", password="password")248 249        # user@user.com "2012-01-14 03:21:34"250        confirm_link = "http://localhost/confirm/a8375iyu<script>alert(1)<script>hn3048wueorighkgnsfg"251        r = client.get(confirm_link)252 253        assert (254            "Your confirmation link is invalid, please generate a new one"255            in r.get_data(as_text=True)256        )257        user = Users.query.filter_by(email="user@user.com").first()258        assert user.verified is not True259    destroy_ctfd(app)260 261 262def test_expired_reset_password_link():263    """Test that expired reset password links are reported to the user"""264    app = create_ctfd()265    with app.app_context():266        set_config("mail_server", "localhost")267        set_config("mail_port", 25)268        set_config("mail_useauth", True)269        set_config("mail_username", "username")270        set_config("mail_password", "password")271 272        register_user(app, name="user1", email="user@user.com")273 274        with app.test_client() as client:275            forgot_link = "http://localhost/reset_password/bb8a8526146e50778b211ae63074595880edbc0b"276            r = client.get(forgot_link)277 278            assert (279                "Your reset link is invalid, please generate a new one"280                in r.get_data(as_text=True)281            )282    destroy_ctfd(app)283 284 285def test_invalid_reset_password_link():286    """Test that invalid reset password links are reported to the user"""287    app = create_ctfd()288    with app.app_context():289        set_config("mail_server", "localhost")290        set_config("mail_port", 25)291        set_config("mail_useauth", True)292        set_config("mail_username", "username")293        set_config("mail_password", "password")294 295        register_user(app, name="user1", email="user@user.com")296 297        with app.test_client() as client:298            # user@user.com "2012-01-14 03:21:34"299            forgot_link = "http://localhost/reset_password/5678ytfghjiu876tyfg<INVALID DATA>hvbnmkoi9u87y6trdf"300            r = client.get(forgot_link)301 302            assert (303                "Your reset link is invalid, please generate a new one"304                in r.get_data(as_text=True)305            )306    destroy_ctfd(app)307 308 309def test_contact_for_password_reset():310    """Test that if there is no mailserver configured, users should contact admins"""311    app = create_ctfd()312    with app.app_context():313        register_user(app, name="user1", email="user@user.com")314 315        with app.test_client() as client:316            forgot_link = "http://localhost/reset_password"317            r = client.get(forgot_link)318 319            assert "contact an organizer" in r.get_data(as_text=True)320    destroy_ctfd(app)321 322 323@patch("smtplib.SMTP")324def test_user_can_confirm_email(mock_smtp):325    """Test that a user is capable of confirming their email address"""326    app = create_ctfd()327    with app.app_context(), freeze_time("2012-01-14 03:21:34"):328        # Set CTFd to only allow confirmed users and send emails329        set_config("verify_emails", True)330        set_config("mail_server", "localhost")331        set_config("mail_port", 25)332        set_config("mail_useauth", True)333        set_config("mail_username", "username")334        set_config("mail_password", "password")335 336        register_user(app, name="user1", email="user@user.com")337 338        # Teams are not verified by default339        user = Users.query.filter_by(email="user@user.com").first()340        assert user.verified is False341 342        client = login_as_user(app, name="user1", password="password")343 344        r = client.get("/confirm")345        assert "We've sent a confirmation email" in r.get_data(as_text=True)346 347        # smtp send message function was called348        mock_smtp.return_value.send_message.assert_called()349 350        with client.session_transaction() as sess:351            urandom_value = b"\xff" * 32352            with patch("os.urandom", return_value=urandom_value):353                data = {"nonce": sess.get("nonce")}354                r = client.post("http://localhost/confirm", data=data)355            assert "Confirmation email sent to" in r.get_data(as_text=True)356 357            r = client.get("/challenges")358            assert r.location == "/confirm"  # We got redirected to /confirm359 360            r = client.get(361                "http://localhost/confirm/ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"362            )363            assert r.location == "/challenges"364 365            # The team is now verified366            user = Users.query.filter_by(email="user@user.com").first()367            assert user.verified is True368 369            r = client.get("http://localhost/confirm")370            assert r.location == "/settings"371    destroy_ctfd(app)372 373 374@patch("smtplib.SMTP")375def test_user_can_reset_password(mock_smtp):376    """Test that a user is capable of resetting their password"""377    from email.message import EmailMessage378 379    app = create_ctfd()380    with app.app_context():381        # Set CTFd to send emails382        set_config("mail_server", "localhost")383        set_config("mail_port", 25)384        set_config("mail_useauth", True)385        set_config("mail_username", "username")386        set_config("mail_password", "password")387 388        # Create a user389        register_user(app, name="user1", email="user@user.com")390 391        with app.test_client() as client:392            client.get("/reset_password")393 394            # Build reset password data395            with client.session_transaction() as sess:396                data = {"nonce": sess.get("nonce"), "email": "user@user.com"}397 398            # Issue the password reset request399            urandom_value = b"\xff" * 32400            with patch("os.urandom", return_value=urandom_value):401                client.post("/reset_password", data=data)402 403            ctf_name = get_config("ctf_name")404            from_addr = get_config("mailfrom_addr") or app.config.get("MAILFROM_ADDR")405            from_addr = "{} <{}>".format(ctf_name, from_addr)406 407            to_addr = "user@user.com"408 409            # Build the email410            msg = (411                "Did you initiate a password reset on CTFd? If you didn't initiate this request you can ignore this email. "412                "\n\nClick the following link to reset your password:\n"413                "http://localhost/reset_password/ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\n\n"414                "If the link is not clickable, try copying and pasting it into your browser."415            )416            ctf_name = get_config("ctf_name")417 418            email_msg = EmailMessage()419            email_msg.set_content(msg)420 421            email_msg["Subject"] = "Password Reset Request from {ctf_name}".format(422                ctf_name=ctf_name423            )424            email_msg["From"] = from_addr425            email_msg["To"] = to_addr426 427            # Make sure that the reset password email is sent428            mock_smtp.return_value.send_message.assert_called()429            assert str(mock_smtp.return_value.send_message.call_args[0][0]) == str(430                email_msg431            )432 433            # Get user's original password434            user = Users.query.filter_by(email="user@user.com").first()435 436            # Build the POST data437            with client.session_transaction() as sess:438                data = {"nonce": sess.get("nonce"), "password": "passwordtwo"}439 440            # Do the password reset441            client.get(442                "/reset_password/ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"443            )444            client.post(445                "/reset_password/ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",446                data=data,447            )448 449            # Make sure that the user's password changed450            user = Users.query.filter_by(email="user@user.com").first()451            assert verify_password("passwordtwo", user.password)452    destroy_ctfd(app)453 454 455def test_banned_user():456    app = create_ctfd()457    with app.app_context():458        register_user(app)459        client = login_as_user(app)460        user = Users.query.filter_by(id=2).first()461        user.banned = True462        db.session.commit()463 464        routes = ["/", "/challenges", "/api/v1/challenges"]465        for route in routes:466            r = client.get(route)467            assert r.status_code == 403468    destroy_ctfd(app)469 470 471def test_registration_code_required():472    """473    Test that registration code configuration properly blocks logins474    with missing and incorrect registration codes475    """476    app = create_ctfd()477    with app.app_context():478        # Set a registration code479        set_config("registration_code", "secret-sauce")480 481        with app.test_client() as client:482            # Load CSRF nonce483            r = client.get("/register")484            resp = r.get_data(as_text=True)485            assert "Registration Code" in resp486            with client.session_transaction() as sess:487                data = {488                    "name": "user",489                    "email": "user1@examplectf.com",490                    "password": "password",491                    "nonce": sess.get("nonce"),492                }493            # Attempt registration without password494            r = client.post("/register", data=data)495            resp = r.get_data(as_text=True)496            assert "The registration code you entered was incorrect" in resp497 498            # Attempt registration with wrong password499            data["registration_code"] = "wrong-sauce"500            r = client.post("/register", data=data)501            resp = r.get_data(as_text=True)502            assert "The registration code you entered was incorrect" in resp503 504            # Attempt registration with right password505            data["registration_code"] = "secret-sauce"506            r = client.post("/register", data=data)507            assert r.status_code == 302508            assert r.location.startswith("/challenges")509    destroy_ctfd(app)510 511 512def test_registration_code_allows_numeric():513    """514    Test that registration code is allowed to be all numeric515    """516    app = create_ctfd()517    with app.app_context():518        # Set a registration code519        set_config("registration_code", "1234567890")520 521        with app.test_client() as client:522            # Load CSRF nonce523            r = client.get("/register")524            resp = r.get_data(as_text=True)525            assert "Registration Code" in resp526            with client.session_transaction() as sess:527                data = {528                    "name": "user",529                    "email": "user1@examplectf.com",530                    "password": "password",531                    "nonce": sess.get("nonce"),532                }533 534            # Attempt registration with numeric registration code535            data["registration_code"] = "1234567890"536            r = client.post("/register", data=data)537            assert r.status_code == 302538            assert r.location.startswith("/challenges")539    destroy_ctfd(app)540 541 542def test_registration_password_minimum_length():543    """544    Test that registration enforces minimum password length when configured545    """546    app = create_ctfd()547    with app.app_context():548        # Set a minimum password length549        set_config("password_min_length", 8)550 551        with app.test_client() as client:552            # Load CSRF nonce553            r = client.get("/register")554            with client.session_transaction() as sess:555                data = {556                    "name": "user",557                    "email": "user1@examplectf.com",558                    "password": "short",  # Only 5 characters559                    "nonce": sess.get("nonce"),560                }561 562            # Attempt registration with password too short563            r = client.post("/register", data=data)564            resp = r.get_data(as_text=True)565            assert "Password must be at least 8 characters" in resp566            assert r.status_code == 200  # Should stay on registration page567 568            # Verify user was not created569            user_count = Users.query.count()570            assert user_count == 1  # Only admin user exists571 572            # Attempt registration with password meeting minimum length573            data["password"] = "validpassword"  # 13 characters, meets minimum574            r = client.post("/register", data=data)575            assert r.status_code == 302576            assert r.location.startswith("/challenges")577 578            # Verify user was created579            user_count = Users.query.count()580            assert user_count == 2  # Admin user + new user581 582        # Test with minimum length set to 0 (disabled)583        set_config("password_min_length", 0)584 585        with app.test_client() as client:586            # Load CSRF nonce587            r = client.get("/register")588            with client.session_transaction() as sess:589                data = {590                    "name": "user2",591                    "email": "user2@examplectf.com",592                    "password": "x",  # Only 1 character593                    "nonce": sess.get("nonce"),594                }595 596            # Should allow short password when minimum length is 0597            r = client.post("/register", data=data)598            assert r.status_code == 302599            assert r.location.startswith("/challenges")600 601            # Verify user was created602            user_count = Users.query.count()603            assert user_count == 3  # Admin user + 2 new users604    destroy_ctfd(app)605 606 607def test_user_change_password_required():608    """609    Test that users with change_password=True are redirected to reset password610    and cannot access other pages until they change their password611    """612    app = create_ctfd()613    with app.app_context():614        # Create a user with change_password=True615        register_user(616            app, name="testuser", email="test@example.com", password="oldpassword"617        )618        user = Users.query.filter_by(name="testuser").first()619        user.change_password = True620        db.session.commit()621 622        with app.test_client() as client:623            # Login as the user624            with client.session_transaction() as sess:625                data = {626                    "name": "testuser",627                    "password": "oldpassword",628                    "nonce": sess.get("nonce"),629                }630 631            # Get login page first to get nonce632            client.get("/login")633            with client.session_transaction() as sess:634                data["nonce"] = sess.get("nonce")635 636            # Login637            r = client.post("/login", data=data)638            assert r.status_code == 302639 640            # Test that user is redirected to reset_password when accessing various pages641            protected_routes = [642                "/",643                "/challenges",644                "/scoreboard",645                "/profile",646                "/settings",647                "/api/v1/challenges",648            ]649 650            for route in protected_routes:651                r = client.get(route, follow_redirects=False)652                # Should be redirected to reset_password with a token653                assert r.status_code == 302654                assert "/reset_password/" in r.location655 656            # Test that the user can access the reset_password page directly657            r = client.get(route, follow_redirects=True)658            # Should end up on reset_password page659            final_url = r.request.path660            assert "/reset_password/" in final_url661 662            # Get redirected to reset password page with token663            r = client.get("/challenges", follow_redirects=False)664            assert r.status_code == 302665            assert "/reset_password/" in r.location666 667            # Extract the token from the redirect URL668            reset_url = r.location669            token = reset_url.split("/reset_password/")[-1]670 671            # Access the reset password page with the token672            r = client.get(f"/reset_password/{token}")673            assert r.status_code == 200674 675            # Actually reset the password using the reset password form676            with client.session_transaction() as sess:677                reset_data = {"password": "newpassword123", "nonce": sess.get("nonce")}678 679            # Submit the password reset form680            r = client.post(f"/reset_password/{token}", data=reset_data)681            assert r.status_code == 302  # Should redirect after successful reset682 683            # Verify the password was actually changed and change_password flag was cleared684            user = Users.query.filter_by(name="testuser").first()685            assert user.change_password is False686            assert verify_password("newpassword123", user.password)687 688            client.get("/login")689            with client.session_transaction() as sess:690                data = {691                    "name": "testuser",692                    "password": "newpassword123",693                    "nonce": sess.get("nonce"),694                }695 696            r = client.post("/login", data=data)697            assert r.status_code == 302698 699            # Now user should be able to access protected routes normally700            r = client.get("/challenges")701            assert r.status_code == 200702            assert r.location is None  # No redirect703 704            r = client.get("/profile")705            assert r.status_code == 200706            assert r.location is None  # No redirect707 708    destroy_ctfd(app)709 710 711def test_admin_can_set_change_password_via_api():712    """713    Test that admins can set the change_password attribute via the API714    """715    app = create_ctfd()716    with app.app_context():717        # Login as admin718        client = login_as_user(app, name="admin", password="password")719 720        # Create a user via API with change_password=True721        r = client.post(722            "/api/v1/users",723            json={724                "name": "apiuser",725                "email": "apiuser@example.com",726                "password": "password123",727                "change_password": True,728            },729        )730        assert r.status_code == 200731 732        # Verify the user was created with change_password=True733        response_data = r.get_json()734        assert response_data["success"] is True735        user_id = response_data["data"]["id"]736 737        user = Users.query.filter_by(id=user_id).first()738        assert user is not None739        assert user.change_password is True740 741        # Update user via API to set change_password=False742        r = client.patch(f"/api/v1/users/{user_id}", json={"change_password": False})743        assert r.status_code == 200744 745        # Verify the change_password was updated746        user = Users.query.filter_by(id=user_id).first()747        assert user.change_password is False748 749        # Test that non-admin users cannot set change_password via API750        register_user(app, name="normaluser", email="normal@example.com")751        normal_client = login_as_user(app, name="normaluser", password="password")752 753        # Try to modify change_password on their own account (should fail)754        normal_user = Users.query.filter_by(name="normaluser").first()755        r = normal_client.patch(756            f"/api/v1/users/{normal_user.id}",757            json={758                "change_password": True,759            },760        )761        # Normal users shouldn't be able to modify change_password field762        assert r.status_code == 403  # Forbidden763 764        # Verify that change_password was not modified765        normal_user = Users.query.filter_by(name="normaluser").first()766        assert normal_user.change_password is False767 768        # Also test via the /me endpoint769        r = normal_client.patch(770            "/api/v1/users/me",771            json={772                "change_password": True,773            },774        )775        # Request goes through but doesn't actually modify the attribute776        assert r.status_code == 200777 778        # Verify that change_password was still not modified779        normal_user = Users.query.filter_by(name="normaluser").first()780        assert normal_user.change_password is False781 782    destroy_ctfd(app)783 784 785@patch("smtplib.SMTP")786def test_user_reset_password_rate_limit(mock_smtp):787    """788    Test that a user can only create 5 reset password attempts before they are rate limited789    """790    app = create_ctfd()791    with app.app_context():792        # Create a user before configuring mail to not send any extra emails793        register_user(app, name="user1", email="user@user.com")794 795        # Set CTFd to send emails796        set_config("mail_server", "localhost")797        set_config("mail_port", 25)798        set_config("mail_useauth", True)799        set_config("mail_username", "username")800        set_config("mail_password", "password")801 802        with app.test_client() as client:803            # Make 5 password reset requests (which should all succeed)804            for _ in range(5):805                client.get("/reset_password")806 807                # Build reset password data808                with client.session_transaction() as sess:809                    data = {"nonce": sess.get("nonce"), "email": "user@user.com"}810 811                # Issue the password reset request812                r = client.post("/reset_password", data=data)813                assert r.status_code == 200814                resp = r.get_data(as_text=True)815                assert (816                    "If that account exists you will receive an email, please check your inbox"817                    in resp818                )819                assert "Too many password reset attempts" not in resp820 821            # Verify that the email was sent 5 times822            assert mock_smtp.return_value.send_message.call_count == 5823 824            # 6th attempt should be rate limited825            client.get("/reset_password")826            with client.session_transaction() as sess:827                data = {"nonce": sess.get("nonce"), "email": "user@user.com"}828 829            r = client.post("/reset_password", data=data)830            assert r.status_code == 200831            resp = r.get_data(as_text=True)832            assert "Too many password reset attempts. Please try again later." in resp833 834            # Verify that no additional email was sent835            assert mock_smtp.return_value.send_message.call_count == 5836 837    destroy_ctfd(app)838