Esempio n. 1
0
def test_used_reset_token(client, get_message):
    with capture_reset_password_requests() as requests:
        client.post("/reset",
                    data=dict(email="*****@*****.**"),
                    follow_redirects=True)

    token = requests[0]["token"]

    # use the token
    response = client.post(
        "/reset/" + token,
        data={
            "password": "******",
            "password_confirm": "awesome sunset"
        },
        follow_redirects=True,
    )

    assert get_message("PASSWORD_RESET") in response.data

    logout(client)

    # attempt to use it a second time
    response2 = client.post(
        "/reset/" + token,
        data={
            "password": "******",
            "password_confirm": "otherpassword"
        },
        follow_redirects=True,
    )

    msg = get_message("INVALID_RESET_PASSWORD_TOKEN")
    assert msg in response2.data
Esempio n. 2
0
def test_password_normalization(app, client, get_message):
    with capture_reset_password_requests() as requests:
        response = client.post(
            "/reset",
            json=dict(email="*****@*****.**"),
        )
        assert response.status_code == 200
    token = requests[0]["token"]

    response = client.post(
        "/reset/" + token,
        json=dict(password="******", password_confirm="HöheHöhe"),
    )
    assert response.status_code == 200
    logout(client)

    # make sure can log in with new password both normnalized or not
    response = client.post(
        "/login",
        json=dict(email="*****@*****.**", password="******"),
    )
    assert response.status_code == 200
    # verify actually logged in
    response = client.get("/profile", follow_redirects=False)
    assert response.status_code == 200
    logout(client)

    response = client.post(
        "/login",
        json=dict(email="*****@*****.**", password="******"),
    )
    assert response.status_code == 200
    # verify actually logged in
    response = client.get("/profile", follow_redirects=False)
    assert response.status_code == 200
Esempio n. 3
0
def test_reset_token_deleted_user(app, client, get_message,
                                  sqlalchemy_datastore):
    with capture_reset_password_requests() as requests:
        client.post("/reset",
                    data=dict(email="*****@*****.**"),
                    follow_redirects=True)

    token = requests[0]["token"]

    # Delete user
    with app.app_context():
        # load user (and role) to get into session so cascade delete works.
        user = app.security.datastore.find_user(email="*****@*****.**")
        sqlalchemy_datastore.delete(user)
        sqlalchemy_datastore.commit()

    response = client.post(
        "/reset/" + token,
        data={
            "password": "******",
            "password_confirm": "newpassword"
        },
        follow_redirects=True,
    )

    msg = get_message("INVALID_RESET_PASSWORD_TOKEN")
    assert msg in response.data
Esempio n. 4
0
def test_expired_reset_token(client, get_message):
    with capture_reset_password_requests() as requests:
        client.post("/reset",
                    data=dict(email="*****@*****.**"),
                    follow_redirects=True)

    user = requests[0]["user"]
    token = requests[0]["token"]

    time.sleep(1)

    with capture_flashes() as flashes:
        msg = get_message("PASSWORD_RESET_EXPIRED",
                          within="1 milliseconds",
                          email=user.email)

        # Test getting reset form with expired token
        response = client.get("/reset/" + token, follow_redirects=True)
        assert msg in response.data

        # Test trying to reset password with expired token
        response = client.post(
            "/reset/" + token,
            data={
                "password": "******",
                "password_confirm": "newpassword"
            },
            follow_redirects=True,
        )

        assert msg in response.data
    assert len(flashes) == 2
Esempio n. 5
0
def test_recover_invalidates_session(app, client):
    # Make sure that if we reset our password - prior sessions are invalidated.

    other_client = app.test_client()
    authenticate(other_client)
    response = other_client.get("/profile", follow_redirects=True)
    assert b"Profile Page" in response.data

    # use normal client to reset password
    with capture_reset_password_requests() as requests:
        response = client.post(
            "/reset",
            json=dict(email="*****@*****.**"),
            headers={"Content-Type": "application/json"},
        )
        assert response.headers["Content-Type"] == "application/json"

    assert response.status_code == 200
    token = requests[0]["token"]

    # Test submitting a new password
    response = client.post(
        "/reset/" + token + "?include_auth_token",
        json=dict(password="******",
                  password_confirm="awesome sunset"),
        headers={"Content-Type": "application/json"},
    )
    assert all(k in response.json["response"]["user"]
               for k in ["email", "authentication_token"])

    # try to access protected endpoint with old session - shouldn't work
    response = other_client.get("/profile")
    assert response.status_code == 302
    assert response.headers[
        "Location"] == "http://localhost/login?next=%2Fprofile"
Esempio n. 6
0
def test_custom_reset_templates(client):
    response = client.get("/reset")
    assert b"CUSTOM FORGOT PASSWORD" in response.data

    with capture_reset_password_requests() as requests:
        client.post("/reset", data=dict(email="*****@*****.**"), follow_redirects=True)
        token = requests[0]["token"]

    response = client.get("/reset/" + token)
    assert b"CUSTOM RESET PASSWORD" in response.data
Esempio n. 7
0
def test_basic_custom_forms(app, sqlalchemy_datastore):
    class MyLoginForm(LoginForm):
        email = StringField("My Login Email Address Field")

    class MyRegisterForm(RegisterForm):
        email = StringField("My Register Email Address Field")

    class MyForgotPasswordForm(ForgotPasswordForm):
        email = StringField(
            "My Forgot Email Address Field",
            validators=[email_required, email_validator, valid_user_email],
        )

    class MyResetPasswordForm(ResetPasswordForm):
        password = StringField("My Reset Password Field")

    class MyChangePasswordForm(ChangePasswordForm):
        password = PasswordField("My Change Password Field")

    app.security = Security(
        app,
        datastore=sqlalchemy_datastore,
        login_form=MyLoginForm,
        register_form=MyRegisterForm,
        forgot_password_form=MyForgotPasswordForm,
        reset_password_form=MyResetPasswordForm,
        change_password_form=MyChangePasswordForm,
    )

    populate_data(app)
    client = app.test_client()

    response = client.get("/login")
    assert b"My Login Email Address Field" in response.data

    response = client.get("/register")
    assert b"My Register Email Address Field" in response.data

    response = client.get("/reset")
    assert b"My Forgot Email Address Field" in response.data

    with capture_reset_password_requests() as requests:
        response = client.post("/reset", data=dict(email="*****@*****.**"))

    token = requests[0]["token"]
    response = client.get("/reset/" + token)
    assert b"My Reset Password Field" in response.data

    authenticate(client)

    response = client.get("/change")
    assert b"My Change Password Field" in response.data
Esempio n. 8
0
def test_easy_password(client, get_message):
    with capture_reset_password_requests() as requests:
        client.post("/reset", data=dict(email="*****@*****.**"), follow_redirects=True)

    token = requests[0]["token"]

    # use the token
    response = client.post(
        "/reset/" + token,
        data={"password": "******", "password_confirm": "mypassword"},
        follow_redirects=True,
    )

    assert b"This is a very common password" in response.data
Esempio n. 9
0
def test_reset_passwordless_user(client, get_message):
    with capture_reset_password_requests() as requests:
        client.post("/reset", data=dict(email="*****@*****.**"), follow_redirects=True)

    token = requests[0]["token"]

    # use the token
    response = client.post(
        "/reset/" + token,
        data={"password": "******", "password_confirm": "awesome sunset"},
        follow_redirects=True,
    )

    assert get_message("PASSWORD_RESET") in response.data
Esempio n. 10
0
def test_spa_get_bad_token(app, client, get_message):
    """Test expired and invalid token"""
    with capture_flashes() as flashes:
        with capture_reset_password_requests() as requests:
            response = client.post(
                "/reset",
                json=dict(email="*****@*****.**"),
                headers={"Content-Type": "application/json"},
            )
            assert response.headers["Content-Type"] == "application/json"
            assert "user" not in response.json["response"]
        token = requests[0]["token"]
        time.sleep(1)

        response = client.get("/reset/" + token)
        assert response.status_code == 302
        split = urlsplit(response.headers["Location"])
        assert "localhost:8081" == split.netloc
        assert "/reset-error" == split.path
        qparams = dict(parse_qsl(split.query))
        assert all(k in qparams for k in ["email", "error", "identity"])

        msg = get_message(
            "PASSWORD_RESET_EXPIRED", within="1 milliseconds", email="*****@*****.**"
        )
        assert msg == qparams["error"].encode("utf-8")

        # Test mangled token
        token = (
            "WyIxNjQ2MzYiLCIxMzQ1YzBlZmVhM2VhZjYwODgwMDhhZGU2YzU0MzZjMiJd."
            "BZEw_Q.lQyo3npdPZtcJ_sNHVHP103syjM"
            "&url_id=fbb89a8328e58c181ea7d064c2987874bc54a23d"
        )
        response = client.get("/reset/" + token)
        assert response.status_code == 302
        split = urlsplit(response.headers["Location"])
        assert "localhost:8081" == split.netloc
        assert "/reset-error" == split.path
        qparams = dict(parse_qsl(split.query))
        assert len(qparams) == 1
        assert all(k in qparams for k in ["error"])

        msg = get_message("INVALID_RESET_PASSWORD_TOKEN")
        assert msg == qparams["error"].encode("utf-8")
    assert len(flashes) == 0
Esempio n. 11
0
def test_recoverable_template(app, client, get_message):
    # Check contents of email template - this uses a test template
    # in order to check all context vars since the default template
    # doesn't have all of them.
    with capture_reset_password_requests() as resets:
        with app.mail.record_messages() as outbox:
            response = client.post(
                "/reset", data=dict(email="*****@*****.**"), follow_redirects=True
            )
        assert len(outbox) == 1
        matcher = re.findall(r"\w+:.*", outbox[0].body, re.IGNORECASE)
        # should be 4 - link, email, token, config item
        assert matcher[1].split(":")[1] == "*****@*****.**"
        assert matcher[2].split(":")[1] == resets[0]["reset_token"]
        assert matcher[3].split(":")[1] == app.config["SECURITY_CONFIRM_URL"]

        # check link
        link = matcher[0].split(":", 1)[1]
        response = client.get(link, follow_redirects=True)
        assert b"Reset Password" in response.data
Esempio n. 12
0
def test_spa_get(app, client):
    """
    Test 'single-page-application' style redirects
    This uses json only.
    """
    with capture_reset_password_requests() as requests:
        response = client.post(
            "/reset",
            json=dict(email="*****@*****.**"),
            headers={"Content-Type": "application/json"},
        )
        assert response.headers["Content-Type"] == "application/json"
        assert "user" not in response.json["response"]
    token = requests[0]["token"]

    response = client.get("/reset/" + token)
    assert response.status_code == 302
    split = urlsplit(response.headers["Location"])
    assert "localhost:8081" == split.netloc
    assert "/reset-redirect" == split.path
    qparams = dict(parse_qsl(split.query))
    assert qparams["email"] == "*****@*****.**"
    assert qparams["token"] == token
Esempio n. 13
0
def test_context_processors(client, app):
    @app.security.context_processor
    def default_ctx_processor():
        return {"global": "global"}

    @app.security.forgot_password_context_processor
    def forgot_password():
        return {"foo": "bar-forgot"}

    response = client.get("/reset")
    assert b"global" in response.data
    assert b"bar-forgot" in response.data

    @app.security.login_context_processor
    def login():
        return {"foo": "bar-login"}

    response = client.get("/login")
    assert b"global" in response.data
    assert b"bar-login" in response.data

    @app.security.verify_context_processor
    def verify():
        return {"foo": "bar-verify"}

    authenticate(client)
    response = client.get("/verify")
    assert b"CUSTOM VERIFY USER" in response.data
    assert b"global" in response.data
    assert b"bar-verify" in response.data
    logout(client)

    @app.security.register_context_processor
    def register():
        return {"foo": "bar-register"}

    response = client.get("/register")
    assert b"global" in response.data
    assert b"bar-register" in response.data

    @app.security.reset_password_context_processor
    def reset_password():
        return {"foo": "bar-reset"}

    # /reset/token - need to generate a token
    with capture_reset_password_requests() as requests:
        response = client.post("/reset",
                               data=dict(email="*****@*****.**"),
                               follow_redirects=True)
    token = requests[0]["token"]
    response = client.get(f"/reset/{token}")
    assert b"global" in response.data
    assert b"bar-reset" in response.data

    @app.security.change_password_context_processor
    def change_password():
        return {"foo": "bar-change"}

    authenticate(client)
    response = client.get("/change")
    assert b"global" in response.data
    assert b"bar-change" in response.data

    @app.security.send_confirmation_context_processor
    def send_confirmation():
        return {"foo": "bar-confirm"}

    response = client.get("/confirm")
    assert b"global" in response.data
    assert b"bar-confirm" in response.data

    @app.security.mail_context_processor
    def mail():
        return {"foo": "bar-mail"}

    client.get("/logout")

    with app.mail.record_messages() as outbox:
        client.post("/reset", data=dict(email="*****@*****.**"))

    email = outbox[0]
    assert "global" in email.body
    assert "bar-mail" in email.body
Esempio n. 14
0
def test_recoverable_flag(app, clients, get_message):
    recorded_resets = []
    recorded_instructions_sent = []

    @password_reset.connect_via(app)
    def on_password_reset(app, user):
        recorded_resets.append(user)

    @reset_password_instructions_sent.connect_via(app)
    def on_instructions_sent(app, user, token):
        assert isinstance(app, Flask)
        assert isinstance(user, UserMixin)
        assert isinstance(token, str)
        recorded_instructions_sent.append(user)

    # Test the reset view
    response = clients.get("/reset")
    assert b"<h1>Send password reset instructions</h1>" in response.data

    # Test submitting email to reset password creates a token and sends email
    with capture_reset_password_requests() as requests:
        with app.mail.record_messages() as outbox:
            response = clients.post("/reset",
                                    data=dict(email="*****@*****.**"),
                                    follow_redirects=True)

    assert len(recorded_instructions_sent) == 1
    assert len(outbox) == 1
    assert response.status_code == 200
    assert get_message("PASSWORD_RESET_REQUEST",
                       email="*****@*****.**") in response.data
    token = requests[0]["token"]

    # Test view for reset token
    response = clients.get("/reset/" + token)
    assert b"<h1>Reset password</h1>" in response.data

    # Test submitting a new password but leave out confirm
    response = clients.post("/reset/" + token,
                            data={"password": "******"},
                            follow_redirects=True)
    assert get_message("PASSWORD_NOT_PROVIDED") in response.data
    assert len(recorded_resets) == 0

    # Test submitting a new password
    response = clients.post(
        "/reset/" + token,
        data={
            "password": "******",
            "password_confirm": "awesome sunset"
        },
        follow_redirects=True,
    )

    assert get_message("PASSWORD_RESET") in response.data
    assert len(recorded_resets) == 1

    logout(clients)

    # Test logging in with the new password
    response = authenticate(clients,
                            "*****@*****.**",
                            "awesome sunset",
                            follow_redirects=True)
    assert b"Welcome [email protected]" in response.data

    logout(clients)

    # Test invalid email
    response = clients.post("/reset",
                            data=dict(email="*****@*****.**"),
                            follow_redirects=True)
    assert get_message("USER_DOES_NOT_EXIST") in response.data

    logout(clients)

    # Test invalid token
    response = clients.post(
        "/reset/bogus",
        data={
            "password": "******",
            "password_confirm": "awesome sunset"
        },
        follow_redirects=True,
    )
    assert get_message("INVALID_RESET_PASSWORD_TOKEN") in response.data

    # Test mangled token
    token = ("WyIxNjQ2MzYiLCIxMzQ1YzBlZmVhM2VhZjYwODgwMDhhZGU2YzU0MzZjMiJd."
             "BZEw_Q.lQyo3npdPZtcJ_sNHVHP103syjM"
             "&url_id=fbb89a8328e58c181ea7d064c2987874bc54a23d")
    response = clients.post(
        "/reset/" + token,
        data={
            "password": "******",
            "password_confirm": "newpassword"
        },
        follow_redirects=True,
    )
    assert get_message("INVALID_RESET_PASSWORD_TOKEN") in response.data
Esempio n. 15
0
def test_recoverable_json(app, client, get_message):
    recorded_resets = []
    recorded_instructions_sent = []

    @password_reset.connect_via(app)
    def on_password_reset(app, user):
        recorded_resets.append(user)

    @reset_password_instructions_sent.connect_via(app)
    def on_instructions_sent(app, user, token):
        recorded_instructions_sent.append(user)

    with capture_flashes() as flashes:
        # Test reset password creates a token and sends email
        with capture_reset_password_requests() as requests:
            with app.mail.record_messages() as outbox:
                response = client.post(
                    "/reset",
                    json=dict(email="*****@*****.**"),
                    headers={"Content-Type": "application/json"},
                )
                assert response.headers["Content-Type"] == "application/json"

        assert len(recorded_instructions_sent) == 1
        assert len(outbox) == 1
        assert response.status_code == 200
        token = requests[0]["token"]

        # Test invalid email
        response = client.post(
            "/reset",
            json=dict(email="*****@*****.**"),
            headers={"Content-Type": "application/json"},
        )
        assert response.status_code == 400
        assert response.json["response"]["errors"]["email"][0].encode(
            "utf-8") == get_message("USER_DOES_NOT_EXIST")

        # Test submitting a new password but leave out 'confirm'
        response = client.post(
            "/reset/" + token,
            data='{"password": "******"}',
            headers={"Content-Type": "application/json"},
        )
        assert response.status_code == 400
        assert response.json["response"]["errors"]["password_confirm"][
            0].encode("utf-8") == get_message("PASSWORD_NOT_PROVIDED")

        # Test submitting a new password
        response = client.post(
            "/reset/" + token + "?include_auth_token",
            json=dict(password="******",
                      password_confirm="awesome sunset"),
            headers={"Content-Type": "application/json"},
        )
        assert all(k in response.json["response"]["user"]
                   for k in ["email", "authentication_token"])
        assert len(recorded_resets) == 1

        # reset automatically logs user in
        logout(client)

        # Test logging in with the new password
        response = client.post(
            "/login?include_auth_token",
            json=dict(email="*****@*****.**", password="******"),
            headers={"Content-Type": "application/json"},
        )
        assert all(k in response.json["response"]["user"]
                   for k in ["email", "authentication_token"])

        logout(client)

        # Use token again - should fail since already have set new password.
        response = client.post(
            "/reset/" + token,
            json=dict(password="******", password_confirm="newpassword"),
            headers={"Content-Type": "application/json"},
        )
        assert response.status_code == 400
        assert len(recorded_resets) == 1

        # Test invalid token
        response = client.post(
            "/reset/bogus",
            json=dict(password="******", password_confirm="newpassword"),
            headers={"Content-Type": "application/json"},
        )
        assert response.json["response"]["error"].encode(
            "utf-8") == get_message("INVALID_RESET_PASSWORD_TOKEN")
    assert len(flashes) == 0