Example #1
0
def test_rate_limiting_on_form_posts(client, msend):
    # confirm a form
    client.post(
        "/[email protected]",
        headers={"referer": "http://somewhere.com"},
        data={"name": "john"},
    )
    form = Form.get_with(host="somewhere.com", email="*****@*****.**")
    form.confirmed = True
    DB.session.add(form)
    DB.session.commit()

    # submit form many times
    replies = []
    for _ in range(1000):
        r = client.post(
            "/[email protected]",
            headers={"referer": "http://somewhere.com"},
            data={"name": "attacker"},
        )
        replies.append(r.status_code)

    limit = int(settings.RATE_LIMIT.split(" ")[0])

    # the number of submissions should not be more than the rate limit
    form = Form.get_with(host="somewhere.com", email="*****@*****.**")
    assert form.counter < limit

    # should have gotten some 302 and then many 429 responses
    assert replies.count(302) <= limit
    assert replies.count(429) >= 900 - limit
Example #2
0
def test_backwards_none_confirmed(client, msend):
    """
    This time no form is confirmed.
    """

    prepare()
    try:
        for host in hosts:
            f = Form(email=email, confirmed=False, host=host)
            DB.session.add(f)
            DB.session.commit()

        for referer in hosts:
            r = client.post(
                "/" + email,
                headers={"Referer": "http://" + referer},
                data={"name": "example"},
            )

            assert r.status_code == 200
            assert b"confirm" in r.get_data()
            assert Form.get_with(email=email, host=referer).host == hosts[0]

        assert Form.query.count() == len(hosts)
        assert Form.query.filter_by(confirmed=True).count() == 0
        assert Submission.query.count() == 0

    finally:
        revert()
Example #3
0
def test_unconfirm_process(client, msend):
    # confirm some forms for the same email address
    f1 = Form('*****@*****.**', 'testwebsite.com')
    f1.confirmed = True
    DB.session.add(f1)

    f2 = Form('*****@*****.**', 'othertestwebsite.com')
    f2.confirmed = True
    DB.session.add(f2)

    f3 = Form('*****@*****.**', 'anothertestwebsite.com')
    f3.confirmed = True
    DB.session.add(f3)
    DB.session.commit()

    # try a submission
    r = client.post('/[email protected]',
                    headers={'Referer': 'http://testwebsite.com'},
                    data={'name': 'carol'})
    assert msend.called
    request_unconfirm_url = url_for('request_unconfirm_form',
                                    form_id=f1.id,
                                    _external=True)
    assert request_unconfirm_url in msend.call_args[1]['text']
    msend.reset_mock()

    # this should send a confirmation email
    r = client.get(request_unconfirm_url)
    assert r.status_code == 200
    assert msend.called

    unconfirm_url_with_digest = url_for('unconfirm_form',
                                        form_id=f1.id,
                                        digest=f1.unconfirm_digest(),
                                        _external=True)
    assert unconfirm_url_with_digest in msend.call_args[1]['text']
    msend.reset_mock()

    # unconfirm this
    r = client.get(unconfirm_url_with_digest)
    assert f1.confirmed == False

    # should show a page with the other options
    assert r.status_code == 200
    assert 'Select all' in r.data.decode('utf-8')
    assert f2.host in r.data.decode('utf-8')
    assert f3.host in r.data.decode('utf-8')

    unconfirm_multiple_url = url_for('unconfirm_multiple')
    assert unconfirm_multiple_url in r.data.decode('utf-8')

    # we can use unconfirm_multiple to unconfirm f2
    assert f2.confirmed == True
    r = client.post(unconfirm_multiple_url, data={'form_ids': [f2.id]})
    assert r.status_code == 200
    assert 'Success' in r.data.decode('utf-8')
    assert f2.confirmed == False
Example #4
0
def test_a_confusing_case(client, msend):
    """
    Final boss.
    """

    prepare()
    try:
        _, uf = create_user_and_form(client)
        DB.session.add(Form(email=uf.email, confirmed=False, host="example.com"))
        DB.session.add(Form(email=uf.email, confirmed=True, host="example.com/contact"))
        DB.session.add(Form(email=uf.email, confirmed=True, host="www.example.com/"))
        DB.session.commit()

        assert Form.query.count() == 4

        form = Form.get_with(email=uf.email, host="example.com/")
        assert form
        assert form.confirmed
        assert form.host == "www.example.com/"

        form2 = Form.get_with(email=uf.email, host="www.example.com")
        assert form2
        assert form2.confirmed
        assert form.host == "www.example.com/"

        contact = form.get_with(email=uf.email, host="www.example.com/contact/")
        assert contact
        assert contact.host == "example.com/contact"

        assert form.id != contact.id
        assert form.id == form2.id

        r = client.post(
            "/" + uf.email,
            headers={"Referer": "http://example.com/"},
            data={"name": "example"},
        )
        assert r.status_code == 302
        assert "next" in r.location

        r = client.post(
            "/" + uf.email,
            headers={"Referer": "www.example.com"},
            data={"name": "example"},
        )
        assert r.status_code == 302
        assert "next" in r.location

        assert msend.call_count == 2
        assert Form.query.count() == 4
        form3 = Form.get_with(email=uf.email, host="example.com")
        assert 2 == form.submissions.count()
        assert form3.id == form2.id == form.id

    finally:
        revert()
Example #5
0
def test_backwards_single(client, msend, host):
    """
    Here we have a single form with one of the 8 host formats,
    then try to submit using all the 8.
    Everything must succeed.
    """

    prepare()
    try:
        f = Form(email=email, confirmed=True, host=host)
        DB.session.add(f)
        DB.session.commit()

        for referrer in hosts:
            r = client.post(
                "/" + email,
                headers={"Referer": "http://" + referrer},
                data={"name": "example"},
            )

            assert 1 == Form.query.count()
            assert r.status_code == 302
            assert "next" in r.location

        assert len(hosts) == Form.query.first().submissions.count()

    finally:
        revert()
Example #6
0
def confirm_email(nonce):
    """
    Confirmation emails point to this endpoint
    It either rejects the confirmation or
    flags associated email+host to be confirmed
    """

    # get the form for this request
    form = Form.confirm(nonce)

    if not form:
        return (
            render_template(
                "error.html",
                title="Not a valid link",
                text="Confirmation token not found.<br />Please check the link and try again.",
            ),
            400,
        )

    else:
        return render_template(
            "forms/form_activated.html",
            email=form.email,
            host=form.host,
            user=current_user,
        )
Example #7
0
def test_backwards_multiple(client, msend, confirmed_host):
    """
    Same as previous, but now instead of having a single form, we have all,
    but each time only one of the them is confirmed.
    """

    prepare()
    try:
        for host in hosts:
            f = Form(email=email,
                     confirmed=(host == confirmed_host),
                     host=host)
            DB.session.add(f)
            DB.session.commit()

        for referer in hosts:
            r = client.post(
                "/" + email,
                headers={"Referer": "http://" + referer},
                data={"name": "example"},
            )

            assert r.status_code == 302
            assert "next" in r.location

        assert (len(hosts) == Form.query.filter_by(
            host=confirmed_host).first().submissions.count())

    finally:
        revert()
Example #8
0
def confirm_email(nonce):
    """
    Confirmation emails point to this endpoint
    It either rejects the confirmation or
    flags associated email+host to be confirmed
    """

    # get the form for this request
    form = Form.confirm(nonce)

    if not form:
        return (
            render_template(
                "error.html",
                title="Not a valid link",
                text=
                "Confirmation token not found.<br />Please check the link and try again.",
            ),
            400,
        )

    else:
        return render_template(
            "forms/form_activated.html",
            email=form.email,
            host=form.host,
            user=current_user,
        )
Example #9
0
def google_call(hashid):
    form = Form.get_with(hashid=hashid)
    session["ggl:form"] = form.hashid

    hint = form.email
    if not form.email.endswith("@gmail.com") and current_user.email.endswith(
        "@gmail.com"
    ):
        hint = current_user.email

    flow = google_auth_oauthlib.flow.Flow.from_client_config(
        settings.GOOGLE_CLIENT_CONFIG,
        scopes=["https://www.googleapis.com/auth/drive.file"],
    )
    flow.redirect_uri = url_for(
        "google_callback",
        _external=True,
        _scheme="https",  # can't figure out how to get this to honor PREFERRED_URL_SCHEME
    )
    authorization_url, _ = flow.authorization_url(
        access_type="offline",
        login_hint=hint,
        prompt="consent",
        state=hashid,
        include_granted_scopes="true",
    )
    return redirect(authorization_url)
Example #10
0
def test_backwards_none_confirmed(client, msend):
    """
    This time no form is confirmed.
    """

    prepare()
    try:
        for host in hosts:
            f = Form(email=email, confirmed=False, host=host)
            DB.session.add(f)
            DB.session.commit()

        for referer in hosts:
            r = client.post(
                "/" + email,
                headers={"Referer": "http://" + referer},
                data={"name": "example"},
            )

            assert r.status_code == 200
            assert b"confirm" in r.get_data()
            assert Form.get_with(email=email, host=referer).host == hosts[0]

        assert Form.query.count() == len(hosts)
        assert Form.query.filter_by(confirmed=True).count() == 0
        assert Submission.query.count() == 0

    finally:
        revert()
    def test_upgraded_user_access(self):
        httpretty.register_uri(httpretty.POST, 'https://api.sendgrid.com/api/mail.send.json')

        # register user
        r = self.client.post('/register',
            data={'email': '*****@*****.**',
                  'password': '******'}
        )

        # upgrade user manually
        user = User.query.filter_by(email='*****@*****.**').first()
        user.upgraded = True
        DB.session.add(user)
        DB.session.commit()

        # create form
        r = self.client.post('/forms',
            headers={'Accept': 'application/json',
                     'Content-type': 'application/json'},
            data=json.dumps({'email': '*****@*****.**'})
        )
        resp = json.loads(r.data)
        form_endpoint = resp['random_like_string']

        # manually confirm the form
        form = Form.get_form_by_random_like_string(form_endpoint)
        form.confirmed = True
        DB.session.add(form)
        DB.session.commit()
        
        # submit form
        r = self.client.post('/' + form_endpoint,
            headers={'Referer': 'formspree.io'},
            data={'name': 'bruce', 'message': 'hi!'}
        )

        # test submissions endpoint (/forms/<random_like_string>/)
        r = self.client.get('/forms/' + form_endpoint + '/',
                            headers={'Accept': 'application/json'})
        submissions = json.loads(r.data)['submissions']

        self.assertEqual(len(submissions), 1)
        self.assertEqual(submissions[0]['name'], 'bruce')
        self.assertEqual(submissions[0]['message'], 'hi!')

        # test submissions endpoint with the user downgraded
        user.upgraded = False
        DB.session.add(user)
        DB.session.commit()
        r = self.client.get('/forms/' + form_endpoint + '/')
        self.assertEqual(r.status_code, 402) # it should fail

        # test submissions endpoint without a logged user
        self.client.get('/logout')
        r = self.client.get('/forms/' + form_endpoint + '/')
        self.assertEqual(r.status_code, 302) # it should return a redirect (via @user_required)
Example #12
0
def get_or_create_form(email, host):
    """
    Gets the form if it already exits, otherwise checks to ensure
    that this is a valid new form submission. If so, creates a
    new form.
    """

    form = Form.get_with(email=email, host=host)

    if not form:
        if request_wants_json():
            # Can't create a new ajax form unless from the dashboard
            ajax_error_str = (
                "To prevent spam, only "
                + settings.UPGRADED_PLAN_NAME
                + " accounts may create AJAX forms."
            )
            raise SubmitFormError((jsonify({"error": ajax_error_str}), 400))

        if (
            url_domain(settings.SERVICE_URL) in host
            and host.rstrip("/") != settings.TEST_URL
        ):
            # Bad user is trying to submit a form spoofing formspree.io
            g.log.info(
                "User attempting to create new form spoofing SERVICE_URL. Ignoring."
            )
            raise SubmitFormError(
                (
                    render_template(
                        "error.html", title="Unable to submit form", text="Sorry."
                    ),
                    400,
                )
            )

        # all good, create form
        form = Form(email, host=host, confirmed=False, normalize=True)

    if form.disabled:
        raise SubmitFormError(errors.disabled_error())

    return form
Example #13
0
def test_unconfirm_process(client, msend):
    # confirm some forms for the same email address
    f1 = Form('*****@*****.**', 'testwebsite.com')
    f1.confirmed = True
    DB.session.add(f1)

    f2 = Form('*****@*****.**', 'othertestwebsite.com')
    f2.confirmed = True
    DB.session.add(f2)

    f3 = Form('*****@*****.**', 'anothertestwebsite.com')
    f3.confirmed = True
    DB.session.add(f3)
    DB.session.commit()

    # try a submission
    r = client.post('/[email protected]',
        headers = {'Referer': 'http://testwebsite.com'},
        data={'name': 'carol'}
    )
    assert msend.called
    request_unconfirm_url = url_for('request_unconfirm_form', form_id=f1.id, _external=True)
    assert request_unconfirm_url in msend.call_args[1]['text']
    msend.reset_mock()

    # this should send a confirmation email
    r = client.get(request_unconfirm_url)
    assert r.status_code == 200
    assert msend.called

    unconfirm_url_with_digest = url_for(
        'unconfirm_form',
        form_id=f1.id,
        digest=f1.unconfirm_digest(),
        _external=True
    )
    assert unconfirm_url_with_digest in msend.call_args[1]['text']
    msend.reset_mock()
    
    # unconfirm this
    r = client.get(unconfirm_url_with_digest)
    assert f1.confirmed == False

    # should show a page with the other options
    assert r.status_code == 200
    assert 'Select all' in r.data.decode('utf-8')
    assert f2.host in r.data.decode('utf-8')
    assert f3.host in r.data.decode('utf-8')

    unconfirm_multiple_url = url_for('unconfirm_multiple')
    assert unconfirm_multiple_url in r.data.decode('utf-8')

    # we can use unconfirm_multiple to unconfirm f2
    assert f2.confirmed == True
    r = client.post(unconfirm_multiple_url, data={'form_ids': [f2.id]})
    assert r.status_code == 200
    assert 'Success' in r.data.decode('utf-8')
    assert f2.confirmed == False
Example #14
0
def test_a_confusing_case(client, msend):
    """
    Final boss.
    """

    prepare()
    try:
        _, uf = create_user_and_form(client)
        DB.session.add(
            Form(email=uf.email, confirmed=False, host="example.com"))
        DB.session.add(
            Form(email=uf.email, confirmed=True, host="example.com/contact"))
        DB.session.add(
            Form(email=uf.email, confirmed=True, host="www.example.com/"))
        DB.session.commit()

        assert Form.query.count() == 4

        form = Form.get_with(email=uf.email, host="example.com/")
        assert form
        assert form.confirmed
        assert form.host == "www.example.com/"

        form2 = Form.get_with(email=uf.email, host="www.example.com")
        assert form2
        assert form2.confirmed
        assert form.host == "www.example.com/"

        contact = form.get_with(email=uf.email,
                                host="www.example.com/contact/")
        assert contact
        assert contact.host == "example.com/contact"

        assert form.id != contact.id
        assert form.id == form2.id

        r = client.post(
            "/" + uf.email,
            headers={"Referer": "http://example.com/"},
            data={"name": "example"},
        )
        assert r.status_code == 302
        assert "next" in r.location

        r = client.post(
            "/" + uf.email,
            headers={"Referer": "www.example.com"},
            data={"name": "example"},
        )
        assert r.status_code == 302
        assert "next" in r.location

        assert msend.call_count == 2
        assert Form.query.count() == 4
        form3 = Form.get_with(email=uf.email, host="example.com")
        assert 2 == form.submissions.count()
        assert form3.id == form2.id == form.id

    finally:
        revert()
    def test_upgraded_user_access(self):
        httpretty.register_uri(httpretty.POST, "https://api.sendgrid.com/api/mail.send.json")

        # register user
        r = self.client.post("/register", data={"email": "*****@*****.**", "password": "******"})

        # upgrade user manually
        user = User.query.filter_by(email="*****@*****.**").first()
        user.upgraded = True
        DB.session.add(user)
        DB.session.commit()

        # create form
        r = self.client.post(
            "/forms",
            headers={"Accept": "application/json", "Content-type": "application/json"},
            data=json.dumps({"email": "*****@*****.**"}),
        )
        resp = json.loads(r.data)
        form_endpoint = resp["hashid"]

        # manually confirm the form
        form = Form.get_with_hashid(form_endpoint)
        form.confirmed = True
        DB.session.add(form)
        DB.session.commit()

        # submit form
        r = self.client.post(
            "/" + form_endpoint, headers={"Referer": "formspree.io"}, data={"name": "bruce", "message": "hi!"}
        )

        # test submissions endpoint (/forms/<hashid>/)
        r = self.client.get("/forms/" + form_endpoint + "/", headers={"Accept": "application/json"})
        submissions = json.loads(r.data)["submissions"]

        self.assertEqual(len(submissions), 1)
        self.assertEqual(submissions[0]["name"], "bruce")
        self.assertEqual(submissions[0]["message"], "hi!")

        # test submissions endpoint with the user downgraded
        user.upgraded = False
        DB.session.add(user)
        DB.session.commit()
        r = self.client.get("/forms/" + form_endpoint + "/")
        self.assertEqual(r.status_code, 402)  # it should fail

        # test submissions endpoint without a logged user
        self.client.get("/logout")
        r = self.client.get("/forms/" + form_endpoint + "/")
        self.assertEqual(r.status_code, 302)  # it should return a redirect (via @user_required)
Example #16
0
def google_callback():
    hashid = session["ggl:form"]
    del session["ggl:form"]
    if hashid != request.args.get("state"):
        return script_error("oauth-failed")

    form = Form.get_with(hashid=hashid)

    flow = google_auth_oauthlib.flow.Flow.from_client_config(
        settings.GOOGLE_CLIENT_CONFIG,
        state=hashid,
        scopes=["https://www.googleapis.com/auth/drive.file"],
    )

    flow.redirect_uri = url_for("google_callback", _external=True, _scheme="https")

    flow.fetch_token(authorization_response=request.url.replace("http://", "https://"))

    plugin = Plugin(form_id=form.id, kind=PluginKind.google_sheets)
    plugin.access_token = json.dumps(
        {
            "token": flow.credentials.token,
            "refresh_token": flow.credentials.refresh_token,
            "token_uri": flow.credentials.token_uri,
            "id_token": flow.credentials.id_token,
            "client_id": flow.credentials.client_id,
            "client_secret": flow.credentials.client_secret,
        }
    )

    spreadsheet_title = (
        form.name
        if form.name
        else f"{settings.SERVICE_NAME} submissions for {form.hashid}"
    )[:128]

    google_creds = json.loads(plugin.access_token)
    credentials = google.oauth2.credentials.Credentials(**google_creds)
    spreadsheet_id, worksheet_id = create_google_sheet(
        form, spreadsheet_title, credentials
    )

    plugin.plugin_data = {
        "spreadsheet_id": spreadsheet_id,
        "worksheet_id": worksheet_id,
    }
    DB.session.add(plugin)
    DB.session.commit()

    return script_data({"spreadsheet_id": spreadsheet_id})
Example #17
0
def validate_user_form(hashid):
    """
    Gets a form from a hashid, created on the dashboard.
    Checks to make sure the submission can be accepted by this form.
    """

    form = Form.get_with(hashid=hashid)

    if not form:
        raise SubmitFormError(errors.bad_hashid_error(hashid))

    if form.disabled:
        raise SubmitFormError(errors.disabled_error())

    return form
Example #18
0
def confirm_email(nonce):
    '''
    Confirmation emails point to this endpoint
    It either rejects the confirmation or
    flags associated email+host to be confirmed
    '''

    # get the form for this request
    form = Form.confirm(nonce)

    if not form:
        return render_template('error.html',
                               title='Not a valid link',
                               text='Confirmation token not found.<br />Please check the link and try again.'), 400

    else:
        return render_template('forms/email_confirmed.html', email=form.email, host=form.host)
Example #19
0
def export_submissions(hashid, format=None):
    if not current_user.has_feature("dashboard"):
        return jsonify({"error": "Please upgrade your account."}), 402

    form = Form.get_with(hashid=hashid)
    if not form.controlled_by(current_user):
        return abort(401)

    submissions, fields = form.submissions_with_fields(with_ids=False)

    if format == "json":
        return Response(
            json.dumps(
                {
                    "host": form.host,
                    "email": form.email,
                    "fields": fields,
                    "submissions": submissions,
                },
                sort_keys=True,
                indent=2,
            ),
            mimetype="application/json",
            headers={
                "Content-Disposition":
                "attachment; filename=form-%s-submissions-%s.json" %
                (hashid, datetime.datetime.now().isoformat().split(".")[0])
            },
        )
    elif format == "csv":
        out = io.BytesIO()

        w = csv.DictWriter(out, fieldnames=fields, encoding="utf-8")
        w.writeheader()
        for sub in submissions:
            w.writerow(sub)

        return Response(
            out.getvalue(),
            mimetype="text/csv",
            headers={
                "Content-Disposition":
                "attachment; filename=form-%s-submissions-%s.csv" %
                (hashid, datetime.datetime.now().isoformat().split(".")[0])
            },
        )
Example #20
0
def slack_call(hashid):
    form = Form.get_with(hashid=hashid)
    session["slk:form"] = form.hashid

    return redirect(
        "https://slack.com/oauth/authorize?"
        + urlencode(
            {
                "client_id": settings.SLACK_CLIENT_ID,
                "scope": "incoming-webhook",
                "redirect_uri": url_for(
                    "slack_callback", _external=True, _scheme="https"
                ),
                "state": form.hashid,
            }
        )
    )
Example #21
0
def mailchimp_call(hashid):
    form = Form.get_with(hashid=hashid)
    session["mcp:form"] = form.hashid

    return redirect(
        "https://login.mailchimp.com/oauth2/authorize?"
        + urlencode(
            {
                "response_type": "code",
                "client_id": settings.MAILCHIMP_CLIENT_ID,
                "redirect_uri": url_for(
                    "mailchimp_callback", _external=True, _scheme="https"
                ),
                "state": form.hashid,
            }
        )
    )
Example #22
0
def mailchimp_callback():
    error = request.args.get("error")
    if error:
        return script_error(error if error == "access-denied" else "oauth-failed")

    hashid = session["mcp:form"]
    del session["mcp:form"]
    if hashid != request.args.get("state"):
        return script_error("oauth-failed")

    code = request.args.get("code")
    r = requests.post(
        "https://login.mailchimp.com/oauth2/token",
        data={
            "grant_type": "authorization_code",
            "client_id": settings.MAILCHIMP_CLIENT_ID,
            "client_secret": settings.MAILCHIMP_CLIENT_SECRET,
            "code": code,
            "redirect_uri": url_for(
                "mailchimp_callback", _external=True, _scheme="https"
            ),
        },
    )
    if not r.ok:
        return script_error("oauth-failed")

    data = r.json()
    form = Form.get_with(hashid=hashid)
    plugin = Plugin(form_id=form.id, kind=PluginKind.mailchimp)

    r = requests.get(
        "https://login.mailchimp.com/oauth2/metadata",
        headers={"Authorization": "OAuth " + data["access_token"]},
    )
    if not r.ok:
        return script_error("oauth-failed")

    meta = r.json()

    plugin.access_token = data["access_token"]
    plugin.plugin_data = {"api_endpoint": meta["api_endpoint"], "dc": meta["dc"]}
    plugin.enabled = False
    DB.session.add(plugin)
    DB.session.commit()

    return script_data({"ok": True})
Example #23
0
def export_submissions(hashid, format=None):
    if not current_user.has_feature("dashboard"):
        return jsonify({"error": "Please upgrade your account."}), 402

    form = Form.get_with(hashid=hashid)
    if not form.controlled_by(current_user):
        return abort(401)

    submissions, fields = form.submissions_with_fields(with_ids=False)

    if format == "json":
        return Response(
            json.dumps(
                {
                    "host": form.host,
                    "email": form.email,
                    "fields": fields,
                    "submissions": submissions,
                },
                sort_keys=True,
                indent=2,
            ),
            mimetype="application/json",
            headers={
                "Content-Disposition": "attachment; filename=form-%s-submissions-%s.json"
                % (hashid, datetime.datetime.now().isoformat().split(".")[0])
            },
        )
    elif format == "csv":
        out = io.BytesIO()

        w = csv.DictWriter(out, fieldnames=fields, encoding="utf-8")
        w.writeheader()
        for sub in submissions:
            w.writerow(sub)

        return Response(
            out.getvalue(),
            mimetype="text/csv",
            headers={
                "Content-Disposition": "attachment; filename=form-%s-submissions-%s.csv"
                % (hashid, datetime.datetime.now().isoformat().split(".")[0])
            },
        )
Example #24
0
def test_backwards_multiple_confirmed(client, msend, unconfirmed_host):
    """
    Same as previous, but now instead of having a single form confirmed, we
    have all but one confirmed. Submissions should go through the form which they
    specify directly, and fallback to the first in the priority when that is not
    confirmed.
    """

    prepare()
    try:
        for host in hosts:
            f = Form(email=email,
                     confirmed=(host != unconfirmed_host),
                     host=host)
            DB.session.add(f)
            DB.session.commit()

        for referer in hosts:
            r = client.post(
                "/" + email,
                headers={"Referer": "http://" + referer},
                data={"name": "example"},
            )

            assert r.status_code == 302
            assert "next" in r.location

        first = None
        for host in hosts:
            form = Form.query.filter_by(host=host).first()

            if host == unconfirmed_host:
                assert form.submissions.count() == 0
            else:
                if not first:
                    first = form
                    continue

                assert form.submissions.count() == 1
        assert first.submissions.count() == 2

    finally:
        revert()
Example #25
0
def create_user_and_form(client, login=True):
    # create user and form
    username = "".join(
        [random.choice(string.ascii_lowercase) for i in range(10)])
    email = username + "@example.com"
    user, _ = User.register(email, PASSWORD)
    user.plan = Plan.gold
    user.emails[0].verified = True
    form = Form(username + "@example.com",
                name="example",
                owner=user,
                confirmed=True)
    DB.session.add(user)
    DB.session.add(form)
    DB.session.commit()

    if login:
        client.post("/login", data={"email": email, "password": PASSWORD})

    return user, form
Example #26
0
def trello_call(hashid):
    form = Form.get_with(hashid=hashid)
    session["trl:form"] = form.hashid

    return redirect(
        "https://trello.com/1/authorize?{}".format(
            urlencode(
                {
                    "return_url": url_for(
                        "fragment_postmessage", _external=True, _scheme="https"
                    ),
                    "expiration": "never",
                    "scope": "read,write",
                    "name": settings.SERVICE_NAME,
                    "key": settings.TRELLO_API_KEY,
                    "callback_method": "fragment",
                    "response_type": "fragment",
                }
            )
        )
    )
Example #27
0
def export_submissions(hashid, format=None):
    if not current_user.has_feature('dashboard'):
        return jsonerror(402, {'error': "Please upgrade your account."})

    form = Form.get_with_hashid(hashid)
    if not form.controlled_by(current_user):
        return abort(401)

    submissions, fields = form.submissions_with_fields()

    if format == 'json':
        return Response(
            json.dumps({
                'host': form.host,
                'email': form.email,
                'fields': fields,
                'submissions': submissions
            }, sort_keys=True, indent=2),
            mimetype='application/json',
            headers={
                'Content-Disposition': 'attachment; filename=form-%s-submissions-%s.json' \
                            % (hashid, datetime.datetime.now().isoformat().split('.')[0])
            }
        )
    elif format == 'csv':
        out = io.BytesIO()
        
        w = csv.DictWriter(out, fieldnames=['id'] + fields, encoding='utf-8')
        w.writeheader()
        for sub in submissions:
            w.writerow(sub)

        return Response(
            out.getvalue(),
            mimetype='text/csv',
            headers={
                'Content-Disposition': 'attachment; filename=form-%s-submissions-%s.csv' \
                            % (hashid, datetime.datetime.now().isoformat().split('.')[0])
            }
        )
Example #28
0
def slack_callback():
    error = request.args.get("error")
    if error:
        return script_error(error if error == "access-denied" else "oauth-failed")

    hashid = session["slk:form"]
    del session["slk:form"]
    if hashid != request.args.get("state"):
        return script_error("oauth-failed")

    code = request.args.get("code")
    r = requests.get(
        "https://slack.com/api/oauth.access",
        params={
            "client_id": settings.SLACK_CLIENT_ID,
            "client_secret": settings.SLACK_CLIENT_SECRET,
            "code": code,
            "redirect_uri": url_for("slack_callback", _external=True, _scheme="https"),
        },
    )
    if not r.ok:
        return script_error("oauth-failed")

    data = r.json()

    form = Form.get_with(hashid=hashid)
    plugin = Plugin(form_id=form.id, kind=PluginKind.slack)
    plugin.access_token = data["access_token"]
    plugin.plugin_data = {
        "team_name": data["team_name"],
        "team_id": data["team_id"],
        "incoming_webhook": data["incoming_webhook"],
    }
    plugin.enabled = True
    DB.session.add(plugin)
    DB.session.commit()

    return script_data(
        {"channel": data["incoming_webhook"]["channel"], "team": data["team_name"]}
    )
Example #29
0
def export_submissions(hashid, format=None):
    if not current_user.has_feature('dashboard'):
        return jsonerror(402, {'error': "Please upgrade your account."})

    form = Form.get_with_hashid(hashid)
    if not form.controlled_by(current_user):
        return abort(401)

    submissions, fields = form.submissions_with_fields()

    if format == 'json':
        return Response(
            json.dumps({
                'host': form.host,
                'email': form.email,
                'fields': fields,
                'submissions': submissions
            }, sort_keys=True, indent=2),
            mimetype='application/json',
            headers={
                'Content-Disposition': 'attachment; filename=form-%s-submissions-%s.json' \
                            % (hashid, datetime.datetime.now().isoformat().split('.')[0])
            }
        )
    elif format == 'csv':
        out = io.BytesIO()

        w = csv.DictWriter(out, fieldnames=['id'] + fields, encoding='utf-8')
        w.writeheader()
        for sub in submissions:
            w.writerow(sub)

        return Response(
            out.getvalue(),
            mimetype='text/csv',
            headers={
                'Content-Disposition': 'attachment; filename=form-%s-submissions-%s.csv' \
                            % (hashid, datetime.datetime.now().isoformat().split('.')[0])
            }
        )
Example #30
0
def confirm_email(nonce):
    '''
    Confirmation emails point to this endpoint
    It either rejects the confirmation or
    flags associated email+host to be confirmed
    '''

    # get the form for this request
    form = Form.confirm(nonce)

    if not form:
        return render_template(
            'error.html',
            title='Not a valid link',
            text=
            'Confirmation token not found.<br />Please check the link and try again.'
        ), 400

    else:
        return render_template('forms/email_confirmed.html',
                               email=form.email,
                               host=form.host)
Example #31
0
def get_or_create_form(email, host):
    '''
    Gets the form if it already exits, otherwise checks to ensure
    that this is a valid new form submission. If so, creates a
    new form.
    '''

    form = Form.query.filter_by(hash=HASH(email, host)).first()

    if not form:

        if request_wants_json():
            # Can't create a new ajax form unless from the dashboard
            ajax_error_str = "To prevent spam, only " + \
                                settings.UPGRADED_PLAN_NAME + \
                                " accounts may create AJAX forms."
            raise SubmitFormError(jsonerror(400, {'error': ajax_error_str}))

        if url_domain(settings.SERVICE_URL) in host:
            # Bad user is trying to submit a form spoofing formspree.io
            g.log.info(
                'User attempting to create new form spoofing SERVICE_URL. Ignoring.'
            )
            raise SubmitFormError(
                (render_template('error.html',
                                 title='Unable to submit form',
                                 text='Sorry'), 400))

        # all good, create form
        form = Form(email, host)

    # Check if it has been assigned using AJAX or not
    assign_ajax(form, request_wants_json())

    if form.disabled:
        raise SubmitFormError(errors.disabled_error())

    return form
Example #32
0
def validate_user_form(hashid, host):
    '''
    Gets a form from a hashid, created on the dashboard. 
    Checks to make sure the submission can be accepted by this form.
    '''

    form = Form.get_with_hashid(hashid)

    if not form:
        raise SubmitFormError(errors.bad_hashid_error(hashid))

    # Check if it has been assigned about using AJAX or not
    assign_ajax(form, request_wants_json())

    if form.disabled:
        raise SubmitFormError(errors.disabled_error())

    if not form.host:
        # add the host to the form
        # ALERT: As a side effect, sets the form's host if not already set
        form.host = host
        DB.session.add(form)
        DB.session.commit()

    # it is an error when
    #   form is not sitewide, and submission came from a different host
    #   form is sitewide, but submission came from a host rooted somewhere else, or
    elif (not form.sitewide and
          # ending slashes can be safely ignored here:
          form.host.rstrip('/') != host.rstrip('/')) \
         or (form.sitewide and \
             # removing www from both sides makes this a neutral operation:

             not remove_www(host).startswith(remove_www(form.host))):
        raise SubmitFormError(errors.mismatched_host_error(host, form))

    return form
Example #33
0
def validate_user_form(hashid, host):
    '''
    Gets a form from a hashid, created on the dashboard. 
    Checks to make sure the submission can be accepted by this form.
    '''

    form = Form.get_with_hashid(hashid)

    if not form:
        raise SubmitFormError(errors.bad_hashid_error(hashid))

    # Check if it has been assigned about using AJAX or not
    assign_ajax(form, request_wants_json())

    if form.disabled:
        raise SubmitFormError(errors.disabled_error())

    if not form.host:
        # add the host to the form
        # ALERT: As a side effect, sets the form's host if not already set
        form.host = host
        DB.session.add(form)
        DB.session.commit()

    # it is an error when
    #   form is not sitewide, and submission came from a different host
    #   form is sitewide, but submission came from a host rooted somewhere else, or
    elif (not form.sitewide and
          # ending slashes can be safely ignored here:
          form.host.rstrip('/') != host.rstrip('/')) \
         or (form.sitewide and \
             # removing www from both sides makes this a neutral operation:
             not remove_www(host).startswith(remove_www(form.host))):
        raise SubmitFormError(errors.mismatched_host_error(host, form))

    return form
def test_grandfather_limit_and_decrease(client, msend):
    settings.GRANDFATHER_MONTHLY_LIMIT = 2
    settings.MONTHLY_SUBMISSIONS_LIMIT = 1
    settings.FORM_LIMIT_DECREASE_ACTIVATION_SEQUENCE = 1
    # the form limit should be 2 for the first form and 1 for the second

    # submit the forms
    client.post(
        "/[email protected]",
        headers={"referer": "http://somewhere.com"},
        data={"name": "john"},
    )
    form_grandfathered = Form.get_with(
        host="somewhere.com", email="*****@*****.**"
    )

    client.post(
        "/[email protected]",
        headers={"referer": "http://somewhere.com"},
        data={"name": "john"},
    )
    form_new = Form.get_with(host="somewhere.com", email="*****@*****.**")

    # confirm formS
    form_grandfathered.confirmed = True
    DB.session.add(form_grandfathered)
    form_new.confirmed = True
    DB.session.add(form_new)
    DB.session.commit()

    # submit each form 3 times
    msend.reset_mock()
    for i in range(3):
        client.post(
            "/[email protected]",
            headers={"referer": "http://somewhere.com"},
            data={"_replyto": "*****@*****.**", "name": "johann", "value": "v%s" % i},
        )

    assert len(msend.call_args_list) == 4
    assert "*****@*****.**" == msend.call_args_list[-4][1]["to"]
    assert "90%" in msend.call_args_list[-4][1]["text"]
    assert "*****@*****.**" == msend.call_args_list[-3][1]["to"]
    assert "v0" in msend.call_args_list[-3][1]["text"]
    assert "*****@*****.**" == msend.call_args_list[-2][1]["to"]
    assert "v1" in msend.call_args_list[-2][1]["text"]
    assert "*****@*****.**" == msend.call_args_list[-1][1]["to"]
    assert "limit" in msend.call_args_list[-1][1]["text"]

    msend.reset_mock()
    for i in range(3):
        client.post(
            "/[email protected]",
            headers={"referer": "http://somewhere.com"},
            data={"_replyto": "*****@*****.**", "name": "johann", "value": "v%s" % i},
        )

    assert len(msend.call_args_list) == 3
    assert "*****@*****.**" == msend.call_args_list[-3][1]["to"]
    assert "v0" in msend.call_args_list[-3][1]["text"]
    assert "*****@*****.**" == msend.call_args_list[-2][1]["to"]
    assert "limit" in msend.call_args_list[-2][1]["text"]
    assert "*****@*****.**" == msend.call_args_list[-1][1]["to"]
    assert "limit" in msend.call_args_list[-1][1]["text"]
Example #35
0
def test_form_and_submission_deletion(client, msend):
    # create and login a user
    r = client.post('/register',
        data={'email': '*****@*****.**',
              'password': '******'}
    )
    assert r.status_code == 302
    assert 1 == User.query.count()

    # upgrade user
    user = User.query.filter_by(email='*****@*****.**').first()
    user.upgraded = True
    DB.session.add(user)
    DB.session.commit()

    # successfully create form
    r = client.post('/forms',
        headers={'Accept': 'application/json', 'Content-type': 'application/json'},
        data=json.dumps({'email': '*****@*****.**'})
    )
    resp = json.loads(r.data.decode('utf-8'))
    assert r.status_code == 200
    assert 'submission_url' in resp
    assert 'hashid' in resp
    form_endpoint = resp['hashid']
    assert resp['hashid'] in resp['submission_url']
    assert 1 == Form.query.count()
    assert Form.query.first().id == Form.get_with_hashid(resp['hashid']).id

    # post to form
    r = client.post('/' + form_endpoint,
        headers={'Referer': 'formspree.io'},
        data={'name': 'bruce'}
    )

    # confirm form
    form = Form.query.first()
    client.get('/confirm/%s:%s' % (HASH(form.email, str(form.id)), form.hashid))
    assert Form.query.first().confirmed
    assert 0 == Submission.query.count()

    # increase the submission limit
    old_submission_limit = settings.ARCHIVED_SUBMISSIONS_LIMIT
    settings.ARCHIVED_SUBMISSIONS_LIMIT = 10
    # make 5 submissions
    for i in range(5):
        r = client.post('/' + form_endpoint,
            headers={'Referer': 'formspree.io'},
            data={'name': 'ana',
                  'submission': '__%s__' % i}
        )

    assert 5 == Submission.query.count()

    # delete a submission in form
    first_submission = Submission.query.first()
    r = client.post('/forms/' + form_endpoint + '/delete/' + str(first_submission.id),
        headers={'Referer': settings.SERVICE_URL},
        follow_redirects=True)
    assert 200 == r.status_code
    assert 4 == Submission.query.count()
    assert DB.session.query(Submission.id).filter_by(id='0').scalar() is None
    # make sure you've deleted the submission

    # logout user
    client.get('/logout')

    # attempt to delete form you don't have access to (while logged out)
    r = client.post('/forms/' + form_endpoint + '/delete',
        headers={'Referer': settings.SERVICE_URL})
    assert 302 == r.status_code
    assert 1 == Form.query.count()

    # create different user
    r = client.post('/register',
        data={'email': '*****@*****.**',
              'password': '******'}
    )

    # attempt to delete form we don't have access to
    r = client.post('/forms/' + form_endpoint + '/delete',
        headers={'Referer': settings.SERVICE_URL})
    assert 400 == r.status_code
    assert 1 == Form.query.count()

    client.get('/logout')

    #log back in to original account
    r = client.post('/login',
        data={'email': '*****@*****.**',
              'password': '******'}
    )

    # delete the form created
    r = client.post('/forms/' + form_endpoint + '/delete',
        headers={'Referer': settings.SERVICE_URL},
        follow_redirects=True)
    assert 200 == r.status_code
    assert 0 == Form.query.count()

    # reset submission limit
    settings.ARCHIVED_SUBMISSIONS_LIMIT = old_submission_limit
def test_upgraded_user_access(client, msend):
    # register user
    r = client.post('/register',
        data={'email': '*****@*****.**',
              'password': '******'}
    )

    # upgrade user manually
    user = User.query.filter_by(email='*****@*****.**').first()
    user.upgraded = True
    DB.session.add(user)
    DB.session.commit()

    # create form
    r = client.post('/forms',
        headers={'Accept': 'application/json',
                 'Content-type': 'application/json'},
        data=json.dumps({'email': '*****@*****.**'})
    )
    resp = json.loads(r.data.decode('utf-8'))
    form_endpoint = resp['hashid']

    # manually confirm the form
    form = Form.get_with_hashid(form_endpoint)
    form.confirmed = True
    DB.session.add(form)
    DB.session.commit()
    
    # submit form
    r = client.post('/' + form_endpoint,
        headers={'Referer': 'formspree.io'},
        data={'name': 'bruce', 'message': 'hi, my name is bruce!'}
    )

    # test submissions endpoint (/forms/<hashid>/)
    r = client.get('/forms/' + form_endpoint + '/',
        headers={'Accept': 'application/json'}
    )
    submissions = json.loads(r.data.decode('utf-8'))['submissions']
    assert len(submissions) == 1
    assert submissions[0]['name'] == 'bruce'
    assert submissions[0]['message'] == 'hi, my name is bruce!'

    # test exporting feature (both json and csv file downloads)
    r = client.get('/forms/' + form_endpoint + '.json')
    submissions = json.loads(r.data.decode('utf-8'))['submissions']
    assert len(submissions) == 1
    assert submissions[0]['name'] == 'bruce'
    assert submissions[0]['message'] == 'hi, my name is bruce!'

    r = client.get('/forms/' + form_endpoint + '.csv')
    lines = r.data.decode('utf-8').splitlines()
    assert len(lines) == 2
    assert lines[0] == 'date,message,name'
    assert '"hi in my name is bruce!"', lines[1]

    # test submissions endpoint with the user downgraded
    user.upgraded = False
    DB.session.add(user)
    DB.session.commit()
    r = client.get('/forms/' + form_endpoint + '/')
    assert r.status_code == 402 # it should fail

    # test submissions endpoint without a logged user
    client.get('/logout')
    r = client.get('/forms/' + form_endpoint + '/')
    assert r.status_code == 302 # it should return a redirect (via @user_required
def test_form_creation(client, msend):
    # register user
    r = client.post(
        "/register", data={"email": "*****@*****.**", "password": "******"}
    )
    assert r.status_code == 302
    assert 1 == User.query.count()

    email = Email.query.first()
    email.verified = True
    DB.session.add(email)
    DB.session.commit()

    # fail to create form
    r = client.post(
        "/api-int/forms",
        headers={"Content-type": "application/json", "Referer": settings.SERVICE_URL},
        data={"email": "*****@*****.**"},
    )
    assert r.status_code == 402
    assert "error" in json.loads(r.data.decode("utf-8"))
    assert 0 == Form.query.count()

    # upgrade user manually
    user = User.query.first()
    user.plan = Plan.gold
    DB.session.add(user)
    DB.session.commit()

    # successfully create form
    r = client.post(
        "/api-int/forms",
        headers={"Content-type": "application/json", "Referer": settings.SERVICE_URL},
        data=json.dumps({"email": "*****@*****.**"}),
    )
    resp = json.loads(r.data.decode("utf-8"))
    assert r.status_code == 201
    assert "submission_url" in resp
    assert "hashid" in resp
    form_endpoint = resp["hashid"]
    assert resp["hashid"] in resp["submission_url"]
    assert 1 == Form.query.count()
    assert Form.query.first().id == Form.get_with(hashid=resp["hashid"]).id

    # post to form, already confirmed because it was created from the dashboard
    r = client.post(
        "/" + form_endpoint,
        headers={
            "Referer": "http://formspree.io",
            "Content-Type": "application/x-www-form-urlencoded",
        },
        data=urllib.parse.urlencode({"name": "bruce"}),
        follow_redirects=True,
    )
    assert "Thanks!" in r.data.decode("utf-8")
    assert (
        "Someone just submitted your form on {}".format("formspree.io")
        in msend.call_args[1]["text"]
    )
    assert 1 == Form.query.count()

    # send 5 forms (monthly limits should not apply to the gold user)
    assert settings.MONTHLY_SUBMISSIONS_LIMIT == 2
    for i in range(5):
        r = client.post(
            "/" + form_endpoint,
            headers={"Referer": "formspree.io"},
            data={"name": "ana", "submission": "__%s__" % i},
        )
    form = Form.query.first()
    assert form.counter == 6
    assert form.get_monthly_counter() == 6
    assert "ana" in msend.call_args[1]["text"]
    assert "__4__" in msend.call_args[1]["text"]
    assert "past the limit" not in msend.call_args[1]["text"]

    # submit from a different host
    r = client.post(
        "/" + form_endpoint,
        headers={"Referer": "http://different.com"},
        data={"name": "ignored"},
        follow_redirects=True,
    )
    assert r.status_code == 200
    assert "Thanks!" in r.data.decode("utf-8")
    assert (
        "Someone just submitted your form on {}".format("different.com")
        in msend.call_args[1]["text"]
    )
    assert 1 == Form.query.count()

    # test submissions endpoint with the user downgraded
    user.plan = Plan.free
    DB.session.add(user)
    DB.session.commit()
    r = client.get(
        "/api-int/forms/" + form_endpoint, headers={"Referer": settings.SERVICE_URL}
    )
    assert r.status_code == 402  # it should fail

    # test submissions endpoint without a logged user
    client.get("/logout")
    r = client.get(
        "/api-int/forms/" + form_endpoint, headers={"Referer": settings.SERVICE_URL}
    )
    assert r.status_code == 401  # should return a json error (via flask login)
    assert "error" in r.json
    def test_form_creation(self):
        httpretty.register_uri(httpretty.POST, 'https://api.sendgrid.com/api/mail.send.json')

        # register user
        r = self.client.post('/register',
            data={'email': '*****@*****.**',
                  'password': '******'}
        )
        self.assertEqual(r.status_code, 302)
        self.assertEqual(1, User.query.count())

        # fail to create form
        r = self.client.post('/forms',
            headers={'Content-type': 'application/json'},
            data={'email': '*****@*****.**'}
        )
        self.assertEqual(r.status_code, 402)
        self.assertIn('error', json.loads(r.data))
        self.assertEqual(0, Form.query.count())

        # upgrade user manually
        user = User.query.filter_by(email='*****@*****.**').first()
        user.upgraded = True
        DB.session.add(user)
        DB.session.commit()

        # successfully create form
        r = self.client.post('/forms',
            headers={'Accept': 'application/json', 'Content-type': 'application/json'},
            data=json.dumps({'email': '*****@*****.**'})
        )
        resp = json.loads(r.data)
        self.assertEqual(r.status_code, 200)
        self.assertIn('submission_url', resp)
        self.assertIn('hashid', resp)
        form_endpoint = resp['hashid']
        self.assertIn(resp['hashid'], resp['submission_url'])
        self.assertEqual(1, Form.query.count())
        self.assertEqual(Form.query.first().id, Form.get_with_hashid(resp['hashid']).id)

        # post to form
        r = self.client.post('/' + form_endpoint,
            headers={'Referer': 'http://formspree.io'},
            data={'name': 'bruce'}
        )
        self.assertIn("sent an email confirmation", r.data)
        self.assertIn('confirm+your+email', httpretty.last_request().body)
        self.assertEqual(1, Form.query.count())

        # confirm form
        form = Form.query.first()
        self.client.get('/confirm/%s:%s' % (HASH(form.email, str(form.id)), form.hashid))
        self.assertTrue(Form.query.first().confirmed)

        # send 5 forms (monthly limits should not apply to the upgraded user)
        self.assertEqual(settings.MONTHLY_SUBMISSIONS_LIMIT, 2)
        for i in range(5):
            r = self.client.post('/' + form_endpoint,
                headers={'Referer': 'formspree.io'},
                data={'name': 'ana',
                      'submission': '__%s__' % i}
            )
        form = Form.query.first()
        self.assertEqual(form.counter, 5)
        self.assertEqual(form.get_monthly_counter(), 5)
        self.assertIn('ana', httpretty.last_request().body)
        self.assertIn('__4__', httpretty.last_request().body)
        self.assertNotIn('You+are+past+our+limit', httpretty.last_request().body)

        # try (and fail) to submit from a different host
        r = self.client.post('/' + form_endpoint,
            headers={'Referer': 'bad.com'},
            data={'name': 'usurper'}
        )
        self.assertEqual(r.status_code, 403)
        self.assertIn('ana', httpretty.last_request().body) # no more data is sent to sendgrid
        self.assertIn('__4__', httpretty.last_request().body)
Example #39
0
def test_form_creation(client, msend):
    # register user
    r = client.post('/register',
        data={'email': '*****@*****.**',
              'password': '******'}
    )
    assert r.status_code == 302
    assert 1 == User.query.count()

    # fail to create form
    r = client.post(
        "/api-int/forms",
        headers={"Content-type": "application/json", "Referer": settings.SERVICE_URL},
        data={"email": "*****@*****.**"},
    )
    assert r.status_code == 402
    assert 'error' in json.loads(r.data.decode('utf-8'))
    assert 0 == Form.query.count()

    # upgrade user manually
    user = User.query.filter_by(email='*****@*****.**').first()
    user.plan = Plan.gold
    DB.session.add(user)
    DB.session.commit()

    # successfully create form
    r = client.post(
        "/api-int/forms",
        headers={
            "Accept": "application/json",
            "Content-type": "application/json",
            "Referer": settings.SERVICE_URL,
        },
        data=json.dumps({"email": "*****@*****.**"}),
    )
    resp = json.loads(r.data.decode('utf-8'))
    assert r.status_code == 200
    assert 'submission_url' in resp
    assert 'hashid' in resp
    form_endpoint = resp['hashid']
    assert resp['hashid'] in resp['submission_url']
    assert 1 == Form.query.count()
    assert Form.query.first().id == Form.get_with_hashid(resp['hashid']).id

    # post to form
    r = client.post('/' + form_endpoint,
        headers={'Referer': 'http://testsite.com'},
        data={'name': 'bruce'}
    )
    assert 'sent an email confirmation' in r.data.decode('utf-8')
    assert 'confirm your email' in msend.call_args[1]['text']
    assert 1 == Form.query.count()

    # confirm form
    form = Form.query.first()
    client.get('/confirm/%s:%s' % (HASH(form.email, str(form.id)), form.hashid))
    assert Form.query.first().confirmed

    # Make sure that it marks the first form as AJAX
    assert Form.query.first().uses_ajax

    # send 5 forms (monthly limits should not apply to the gold user)
    assert settings.MONTHLY_SUBMISSIONS_LIMIT == 2
    for i in range(5):
        r = client.post(
            "/" + form_endpoint,
            headers={"Referer": "testsite.com"},
            data={"name": "ana", "submission": "__%s__" % i},
        )
    form = Form.query.first()
    assert form.counter == 5
    assert form.get_monthly_counter() == 5
    assert 'ana' in msend.call_args[1]['text']
    assert '__4__' in msend.call_args[1]['text']
    assert 'past the limit' not in msend.call_args[1]['text']

    # try (and fail) to submit from a different host
    r = client.post(
        "/" + form_endpoint, headers={"Referer": "bad.com"}, data={"name": "usurper"}
    )
    assert r.status_code == 403
    assert "ana" in msend.call_args[1]["text"]  # no more data is sent to sendgrid
    assert "__4__" in msend.call_args[1]["text"]
def test_automatically_created_forms(
    client, host="somewhere.com", email="*****@*****.**"
):

    form = create_and_activate_form(
        client, host="somewhere.com", email="*****@*****.**"
    )

    # add four filler submissions
    DB.session.add(Submission(form.id))
    DB.session.add(Submission(form.id))
    DB.session.add(Submission(form.id))
    DB.session.commit()

    # submit again
    client.post(
        "/[email protected]",
        headers={"referer": "http://somewhere.com"},
        data={
            "_replyto": "*****@*****.**",
            "_next": "http://google.com",
            "name": "johannes",
            "message": "yahoo!",
        },
    )

    form = Form.query.first()
    assert form.submissions.count() == 4

    # check archived values
    submissions = form.submissions.all()

    assert 4 == len(submissions)
    assert "message" in submissions[0].data
    assert "_next" not in submissions[0].data

    # check if submissions over the limit are correctly deleted
    assert settings.ARCHIVED_SUBMISSIONS_LIMIT == 4

    client.post(
        "/[email protected]",
        headers={"referer": "http://somewhere.com"},
        data={"which-submission-is-this": "the fifth!"},
    )

    form = Form.query.first()
    assert 4 == form.submissions.count()
    newest = form.submissions.first()  # first should be the newest
    assert newest.data["which-submission-is-this"] == "the fifth!"

    client.post(
        "/[email protected]",
        headers={"referer": "http://somewhere.com"},
        data={"which-submission-is-this": "the sixth!"},
    )
    client.post(
        "/[email protected]",
        headers={"referer": "http://somewhere.com"},
        data={"which-submission-is-this": "the seventh!"},
    )

    form = Form.query.first()
    assert 4 == form.submissions.count()
    submissions = form.submissions.all()
    assert submissions[0].data["which-submission-is-this"] == "the seventh!"
    assert submissions[3].data["message"] == "yahoo!"

    #
    # try another form (to ensure that a form is not deleting wrong submissions)
    client.post(
        "/[email protected]",
        headers={"referer": "http://here.com"},
        data={"name": "send me the confirmation!"},
    )
    secondform = Form.get_with(host="here.com", email="*****@*****.**")
    assert secondform is not None

    # this form wasn't confirmed, so it still has no submissions
    assert secondform.submissions.count() == 0

    # confirm
    secondform.confirmed = True
    DB.session.add(form)
    DB.session.commit()

    # submit more times and test
    client.post(
        "/[email protected]",
        headers={"referer": "http://here.com"},
        data={"name": "leibniz"},
    )

    secondform = Form.query.filter_by(
        host="here.com", email="*****@*****.**"
    ).first()
    assert 1 == secondform.submissions.count()
    assert secondform.submissions.first().data["name"] == "leibniz"

    client.post(
        "/[email protected]",
        headers={"referer": "http://here.com"},
        data={"name": "schelling"},
    )

    secondform = Form.query.filter_by(
        host="here.com", email="*****@*****.**"
    ).first()
    assert 2 == secondform.submissions.count()
    newest, last = secondform.submissions.all()
    assert newest.data["name"] == "schelling"
    assert last.data["name"] == "leibniz"

    client.post(
        "/[email protected]",
        headers={"referer": "http://here.com"},
        data={"name": "husserl"},
    )
    client.post(
        "/[email protected]",
        headers={"referer": "http://here.com"},
        data={"name": "barban"},
    )
    client.post(
        "/[email protected]",
        headers={"referer": "http://here.com"},
        data={"name": "gliffet"},
    )

    secondform = Form.query.filter_by(
        host="here.com", email="*****@*****.**"
    ).first()
    assert 4 == secondform.submissions.count()
    last = secondform.submissions.order_by(Submission.id.desc()).first()
    assert last.data["name"] == "gliffet"

    # now check the previous form again
    form = Form.query.first()
    submissions = form.submissions.all()
    assert submissions[0].data["which-submission-is-this"] == "the seventh!"
    assert submissions[3].data["message"] == "yahoo!"
Example #41
0
def test_unconfirm_process(client, msend):
    # confirm some forms for the same email address
    f1 = Form("*****@*****.**", host="testwebsite.com", confirmed=True)
    DB.session.add(f1)

    f2 = Form("*****@*****.**", host="othertestwebsite.com", confirmed=True)
    DB.session.add(f2)

    f3 = Form("*****@*****.**", host="anothertestwebsite.com", confirmed=True)
    DB.session.add(f3)
    DB.session.commit()

    # try a submission
    r = client.post(
        "/[email protected]",
        headers={"Referer": "http://testwebsite.com"},
        data={"name": "carol"},
    )
    assert msend.called
    request_unconfirm_url = url_for(
        "request_unconfirm_form", form_id=f1.id, _external=True, _scheme="https"
    )
    assert request_unconfirm_url in msend.call_args[1]["text"]
    msend.reset_mock()

    # this should send a confirmation email
    r = client.get(request_unconfirm_url)

    # actually, it should fail unless the request comes from a browser
    assert not msend.called

    # now it must work
    r = client.get(
        request_unconfirm_url,
        headers={
            "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
        },
    )
    assert r.status_code == 200
    assert msend.called

    unconfirm_url_with_digest = url_for(
        "unconfirm_form",
        form_id=f1.id,
        digest=f1.unconfirm_digest(),
        _external=True,
        _scheme="https",
    )
    assert unconfirm_url_with_digest in msend.call_args[1]["text"]
    msend.reset_mock()

    # unconfirm this
    r = client.get(unconfirm_url_with_digest)
    assert f1.confirmed == False

    # should show a page with the other options
    assert r.status_code == 200
    assert "Select all" in r.data.decode("utf-8")
    assert f2.host in r.data.decode("utf-8")
    assert f3.host in r.data.decode("utf-8")

    unconfirm_multiple_url = url_for("unconfirm_multiple")
    assert unconfirm_multiple_url in r.data.decode("utf-8")

    # we can use unconfirm_multiple to unconfirm f2
    assert f2.confirmed == True
    r = client.post(unconfirm_multiple_url, data={"form_ids": [f2.id]})
    assert r.status_code == 200
    assert "Success" in r.data.decode("utf-8")
    assert f2.confirmed == False
Example #42
0
def create_and_activate_form(client, email, host):
    # create user and form
    form = Form(email, host=host, confirmed=True)
    DB.session.add(form)
    DB.session.commit()
    return form
Example #43
0
def resend_confirmation(email):
    g.log = g.log.bind(email=email, host=request.form.get("host"))
    g.log.info("Resending confirmation.")

    if verify_captcha(request.form, request.remote_addr):
        # check if this email is listed on SendGrid's bounces
        r = requests.get(
            "https://api.sendgrid.com/api/bounces.get.json",
            params={
                "email": email,
                "api_user": settings.SENDGRID_USERNAME,
                "api_key": settings.SENDGRID_PASSWORD,
            },
        )
        if r.ok and len(r.json()) and "reason" in r.json()[0]:
            # tell the user to verify his mailbox
            reason = r.json()[0]["reason"]
            g.log.info("Email is blocked on SendGrid. Telling the user.")
            if request_wants_json():
                resp = jsonify({
                    "error": "Verify your mailbox, we can't reach it.",
                    "reason": reason,
                })
            else:
                resp = make_response(
                    render_template(
                        "info.html",
                        title="Verify the availability of your mailbox",
                        text=
                        "We encountered an error when trying to deliver the confirmation message to <b>"
                        + email +
                        "</b> at the first time we tried. For spam reasons, we will not try again until we are sure the problem is fixed. Here's the reason:</p><p><center><i>"
                        + reason +
                        "</i></center></p><p>Please make sure this problem is not happening still, then go to <a href='/unblock/"
                        + email +
                        "'>this page</a> to unblock your address.</p><p>After you have unblocked the address, please try to resend the confirmation again.</p>",
                    ))
            return resp
        # ~~~
        # if there's no bounce, we proceed to resend the confirmation.

        # BUG: What if this is an owned form with hashid??

        form = Form.get_with(email=email, host=request.form["host"])
        if not form:
            if request_wants_json():
                return jsonify({"error": "This form does not exist."}), 400
            else:
                return (
                    render_template(
                        "error.html",
                        title="Check email address",
                        text="This form does not exist.",
                    ),
                    400,
                )
        form.confirm_sent = False
        status = form.send_confirmation()
        if status["code"] == Form.STATUS_CONFIRMATION_SENT:
            if request_wants_json():
                return jsonify({"success": "confirmation email sent"})
            else:
                return render_template(
                    "forms/confirmation_sent.html",
                    email=email,
                    host=request.form["host"],
                    resend=status["code"] ==
                    Form.STATUS_CONFIRMATION_DUPLICATED,
                )

    # fallback response -- should happen only when the recaptcha is failed.
    g.log.warning("Failed to resend confirmation.")
    return (
        render_template(
            "error.html",
            title="Unable to send email",
            text=
            "Please make sure you pass the <i>reCaptcha</i> test before submitting.",
        ),
        500,
    )
Example #44
0
    def test_form_toggle(self):
                # create and login a user
        r = self.client.post('/register',
            data={'email': '*****@*****.**',
                  'password': '******'}
        )
        self.assertEqual(r.status_code, 302)
        self.assertEqual(1, User.query.count())

        # upgrade user
        user = User.query.filter_by(email='*****@*****.**').first()
        user.upgraded = True
        DB.session.add(user)
        DB.session.commit()

        # successfully create form
        r = self.client.post('/forms',
            headers={'Accept': 'application/json', 'Content-type': 'application/json'},
            data=json.dumps({'email': '*****@*****.**'})
        )
        resp = json.loads(r.data)
        self.assertEqual(r.status_code, 200)
        self.assertIn('submission_url', resp)
        self.assertIn('hashid', resp)
        form_endpoint = resp['hashid']
        self.assertIn(resp['hashid'], resp['submission_url'])
        self.assertEqual(1, Form.query.count())
        self.assertEqual(Form.query.first().id, Form.get_with_hashid(resp['hashid']).id)

        # post to form
        r = self.client.post('/' + form_endpoint,
            headers={'Referer': 'formspree.io'},
            data={'name': 'bruce'}
        )

        # confirm form
        form = Form.query.first()
        self.client.get('/confirm/%s:%s' % (HASH(form.email, str(form.id)), form.hashid))
        self.assertTrue(Form.query.first().confirmed)
        self.assertEqual(0, Submission.query.count())

        # disable the form
        r = self.client.post('/forms/' + form_endpoint + '/toggle',
            headers={'Referer': settings.SERVICE_URL})
        self.assertEqual(302, r.status_code)
        self.assertTrue(r.location.endswith('/dashboard'))
        self.assertTrue(Form.query.first().disabled)
        self.assertEqual(0, Form.query.first().counter)

        # logout and attempt to enable the form
        self.client.get('/logout')
        r = self.client.post('/forms/' + form_endpoint + '/toggle',
            headers={'Referer': settings.SERVICE_URL},
            follow_redirects=True)
        self.assertEqual(200, r.status_code)
        self.assertTrue(Form.query.first().disabled)

        # fail when attempting to post to form
        r = self.client.post('/' + form_endpoint,
            headers={'Referer': 'formspree.io'},
            data={'name': 'bruce'}
        )
        self.assertEqual(403, r.status_code)
        self.assertEqual(0, Form.query.first().counter)

        # log back in and re-enable form
        r = self.client.post('/login',
            data={'email': '*****@*****.**',
                  'password': '******'}
        )
        r = self.client.post('/forms/' + form_endpoint + '/toggle',
            headers={'Referer': settings.SERVICE_URL},
            follow_redirects=True)
        self.assertEqual(200, r.status_code)
        self.assertFalse(Form.query.first().disabled)

        # successfully post to form
        r = self.client.post('/' + form_endpoint,
            headers={'Referer': 'formspree.io'},
            data={'name': 'bruce'}
        )
        self.assertEqual(1, Form.query.first().counter)
Example #45
0
    def test_form_and_submission_deletion(self):
        # create and login a user
        r = self.client.post('/register',
            data={'email': '*****@*****.**',
                  'password': '******'}
        )
        self.assertEqual(r.status_code, 302)
        self.assertEqual(1, User.query.count())

        # upgrade user
        user = User.query.filter_by(email='*****@*****.**').first()
        user.upgraded = True
        DB.session.add(user)
        DB.session.commit()

        # successfully create form
        r = self.client.post('/forms',
            headers={'Accept': 'application/json', 'Content-type': 'application/json'},
            data=json.dumps({'email': '*****@*****.**'})
        )
        resp = json.loads(r.data)
        self.assertEqual(r.status_code, 200)
        self.assertIn('submission_url', resp)
        self.assertIn('hashid', resp)
        form_endpoint = resp['hashid']
        self.assertIn(resp['hashid'], resp['submission_url'])
        self.assertEqual(1, Form.query.count())
        self.assertEqual(Form.query.first().id, Form.get_with_hashid(resp['hashid']).id)

        # post to form
        r = self.client.post('/' + form_endpoint,
            headers={'Referer': 'formspree.io'},
            data={'name': 'bruce'}
        )

        # confirm form
        form = Form.query.first()
        self.client.get('/confirm/%s:%s' % (HASH(form.email, str(form.id)), form.hashid))
        self.assertTrue(Form.query.first().confirmed)
        self.assertEqual(0, Submission.query.count())

        # increase the submission limit
        old_submission_limit = settings.ARCHIVED_SUBMISSIONS_LIMIT
        settings.ARCHIVED_SUBMISSIONS_LIMIT = 10
        # make 5 submissions
        for i in range(5):
            r = self.client.post('/' + form_endpoint,
                headers={'Referer': 'formspree.io'},
                data={'name': 'ana',
                      'submission': '__%s__' % i}
            )

        self.assertEqual(5, Submission.query.count())

        # delete a submission in form
        first_submission = Submission.query.first()
        r = self.client.post('/forms/' + form_endpoint + '/delete/' + unicode(first_submission.id),
            headers={'Referer': settings.SERVICE_URL},
            follow_redirects=True)
        self.assertEqual(200, r.status_code)
        self.assertEqual(4, Submission.query.count())
        self.assertTrue(DB.session.query(Submission.id).filter_by(id='0').scalar() is None) #make sure you deleted the submission

        # logout user
        self.client.get('/logout')

        # attempt to delete form you don't have access to (while logged out)
        r = self.client.post('/forms/' + form_endpoint + '/delete',
            headers={'Referer': settings.SERVICE_URL})
        self.assertEqual(302, r.status_code)
        self.assertEqual(1, Form.query.count())

        # create different user
        r = self.client.post('/register',
            data={'email': '*****@*****.**',
                  'password': '******'}
        )

        # attempt to delete form we don't have access to
        r = self.client.post('/forms/' + form_endpoint + '/delete',
            headers={'Referer': settings.SERVICE_URL})
        self.assertEqual(400, r.status_code)
        self.assertEqual(1, Form.query.count())

        self.client.get('/logout')

        #log back in to original account
        r = self.client.post('/login',
            data={'email': '*****@*****.**',
                  'password': '******'}
        )

        # delete the form created
        r = self.client.post('/forms/' + form_endpoint + '/delete',
            headers={'Referer': settings.SERVICE_URL},
            follow_redirects=True)
        self.assertEqual(200, r.status_code)
        self.assertEqual(0, Form.query.count())

        # reset submission limit
        settings.ARCHIVED_SUBMISSIONS_LIMIT = old_submission_limit
Example #46
0
def test_form_and_submission_deletion(client, msend):
    # create and login a user
    r = client.post('/register',
                    data={
                        'email': '*****@*****.**',
                        'password': '******'
                    })
    assert r.status_code == 302
    assert 1 == User.query.count()

    # upgrade user
    user = User.query.filter_by(email='*****@*****.**').first()
    user.upgraded = True
    DB.session.add(user)
    DB.session.commit()

    # successfully create form
    r = client.post(
        "/api-int/forms",
        headers={
            "Accept": "application/json",
            "Content-type": "application/json",
            "Referer": settings.SERVICE_URL,
        },
        data=json.dumps({"email": "*****@*****.**"}),
    )
    resp = json.loads(r.data.decode('utf-8'))
    assert r.status_code == 200
    assert 'submission_url' in resp
    assert 'hashid' in resp
    form_endpoint = resp['hashid']
    assert resp['hashid'] in resp['submission_url']
    assert 1 == Form.query.count()
    assert Form.query.first().id == Form.get_with_hashid(resp['hashid']).id

    # post to form
    r = client.post('/' + form_endpoint,
                    headers={'Referer': 'formspree.io'},
                    data={'name': 'bruce'})

    # confirm form
    form = Form.query.first()
    client.get('/confirm/%s:%s' %
               (HASH(form.email, str(form.id)), form.hashid))
    assert Form.query.first().confirmed
    assert 0 == Submission.query.count()

    # increase the submission limit
    old_submission_limit = settings.ARCHIVED_SUBMISSIONS_LIMIT
    settings.ARCHIVED_SUBMISSIONS_LIMIT = 10
    # make 5 submissions
    for i in range(5):
        r = client.post('/' + form_endpoint,
                        headers={'Referer': 'formspree.io'},
                        data={
                            'name': 'ana',
                            'submission': '__%s__' % i
                        })

    assert 5 == Submission.query.count()

    # delete a submission in form
    first_submission = Submission.query.first()
    r = client.delete(
        "/api-int/forms/" + form_endpoint + "/submissions/" +
        str(first_submission.id),
        headers={"Referer": settings.SERVICE_URL},
    )
    assert 200 == r.status_code
    assert 4 == Submission.query.count()
    assert DB.session.query(Submission.id).filter_by(id='0').scalar() is None
    # make sure you've deleted the submission

    # logout user
    client.get('/logout')

    # attempt to delete form you don't have access to (while logged out)
    r = client.delete("/api-int/forms/" + form_endpoint,
                      headers={"Referer": settings.SERVICE_URL})
    assert 401 == r.status_code
    assert 1 == Form.query.count()

    # create different user
    r = client.post('/register',
                    data={
                        'email': '*****@*****.**',
                        'password': '******'
                    })

    # attempt to delete form we don't have access to
    r = client.delete("/api-int/forms/" + form_endpoint,
                      headers={"Referer": settings.SERVICE_URL})
    assert 401 == r.status_code
    assert 1 == Form.query.count()

    client.get('/logout')

    #log back in to original account
    r = client.post('/login',
                    data={
                        'email': '*****@*****.**',
                        'password': '******'
                    })

    # delete the form created
    r = client.delete("/api-int/forms/" + form_endpoint,
                      headers={"Referer": settings.SERVICE_URL})
    assert 200 == r.status_code
    assert 0 == Form.query.count()

    # reset submission limit
    settings.ARCHIVED_SUBMISSIONS_LIMIT = old_submission_limit
Example #47
0
    def test_upgraded_user_access(self):
        httpretty.register_uri(httpretty.POST,
                               'https://api.sendgrid.com/api/mail.send.json')

        # register user
        r = self.client.post('/register',
                             data={
                                 'email': '*****@*****.**',
                                 'password': '******'
                             })

        # upgrade user manually
        user = User.query.filter_by(email='*****@*****.**').first()
        user.upgraded = True
        DB.session.add(user)
        DB.session.commit()

        # create form
        r = self.client.post('/forms',
                             headers={
                                 'Accept': 'application/json',
                                 'Content-type': 'application/json'
                             },
                             data=json.dumps({'email': '*****@*****.**'}))
        resp = json.loads(r.data)
        form_endpoint = resp['hashid']

        # manually confirm the form
        form = Form.get_with_hashid(form_endpoint)
        form.confirmed = True
        DB.session.add(form)
        DB.session.commit()

        # submit form
        r = self.client.post('/' + form_endpoint,
                             headers={'Referer': 'formspree.io'},
                             data={
                                 'name': 'bruce',
                                 'message': 'hi, my name is bruce!'
                             })

        # test submissions endpoint (/forms/<hashid>/)
        r = self.client.get('/forms/' + form_endpoint + '/',
                            headers={'Accept': 'application/json'})
        submissions = json.loads(r.data)['submissions']
        self.assertEqual(len(submissions), 1)
        self.assertEqual(submissions[0]['name'], 'bruce')
        self.assertEqual(submissions[0]['message'], 'hi, my name is bruce!')

        # test exporting feature (both json and csv file downloads)
        r = self.client.get('/forms/' + form_endpoint + '.json')
        submissions = json.loads(r.data)['submissions']
        self.assertEqual(len(submissions), 1)
        self.assertEqual(submissions[0]['name'], 'bruce')
        self.assertEqual(submissions[0]['message'], 'hi, my name is bruce!')

        r = self.client.get('/forms/' + form_endpoint + '.csv')
        lines = r.data.splitlines()
        self.assertEqual(len(lines), 2)
        self.assertEqual(lines[0], 'date,message,name')
        self.assertIn('"hi, my name is bruce!"', lines[1])

        # test submissions endpoint with the user downgraded
        user.upgraded = False
        DB.session.add(user)
        DB.session.commit()
        r = self.client.get('/forms/' + form_endpoint + '/')
        self.assertEqual(r.status_code, 402)  # it should fail

        # test submissions endpoint without a logged user
        self.client.get('/logout')
        r = self.client.get('/forms/' + form_endpoint + '/')
        self.assertEqual(
            r.status_code,
            302)  # it should return a redirect (via @user_required)
Example #48
0
    def test_form_creation(self):
        # register user
        r = self.client.post('/register',
                             data={
                                 'email': '*****@*****.**',
                                 'password': '******'
                             })
        self.assertEqual(r.status_code, 302)
        self.assertEqual(r.location.endswith('/dashboard'), True)
        self.assertEqual(1, User.query.count())

        # fail to create form
        r = self.client.post('/forms',
                             headers={'Content-type': 'application/json'},
                             data={'email': '*****@*****.**'})
        self.assertEqual(r.status_code, 402)
        self.assertIn('error', json.loads(r.data))
        self.assertEqual(0, Form.query.count())

        # upgrade user manually
        user = User.query.filter_by(email='*****@*****.**').first()
        user.upgraded = True
        DB.session.add(user)
        DB.session.commit()

        # successfully create form
        r = self.client.post('/forms',
                             headers={
                                 'Accept': 'application/json',
                                 'Content-type': 'application/json'
                             },
                             data=json.dumps({'email': '*****@*****.**'}))
        resp = json.loads(r.data)
        self.assertEqual(r.status_code, 200)
        self.assertIn('submission_url', resp)
        self.assertIn('random_like_string', resp)
        form_endpoint = resp['random_like_string']
        self.assertIn(resp['random_like_string'], resp['submission_url'])
        self.assertEqual(1, Form.query.count())
        self.assertEqual(
            Form.query.first().id,
            Form.get_form_by_random_like_string(resp['random_like_string']).id)

        # post to form
        httpretty.register_uri(httpretty.POST,
                               'https://api.sendgrid.com/api/mail.send.json')

        r = self.client.post('/' + form_endpoint,
                             headers={'Referer': 'formspree.io'},
                             data={'name': 'bruce'})
        self.assertIn("We've sent a link to your email", r.data)
        self.assertIn('confirm+your+email', httpretty.last_request().body)
        self.assertEqual(1, Form.query.count())

        # confirm form
        form = Form.query.first()
        self.client.get(
            '/confirm/%s:%s' %
            (HASH(form.email, str(form.id)), form.get_random_like_string()))
        self.assertTrue(Form.query.first().confirmed)

        # send 5 forms (monthly limits should not apply to the upgraded user)
        self.assertEqual(settings.MONTHLY_SUBMISSIONS_LIMIT, 2)
        for i in range(5):
            r = self.client.post('/' + form_endpoint,
                                 headers={'Referer': 'formspree.io'},
                                 data={
                                     'name': 'ana',
                                     'submission': '__%s__' % i
                                 })
        form = Form.query.first()
        self.assertEqual(form.counter, 5)
        self.assertEqual(form.get_monthly_counter(), 5)
        self.assertIn('ana', httpretty.last_request().body)
        self.assertIn('__4__', httpretty.last_request().body)
        self.assertNotIn('You+are+past+our+limit',
                         httpretty.last_request().body)

        # try (and fail) to submit from a different host
        r = self.client.post('/' + form_endpoint,
                             headers={'Referer': 'bad.com'},
                             data={'name': 'usurper'})
        self.assertEqual(r.status_code, 403)
        self.assertIn(
            'ana',
            httpretty.last_request().body)  # no more data is sent to sendgrid
        self.assertIn('__4__', httpretty.last_request().body)
Example #49
0
def test_form_toggle(client, msend):
    # create and login a user
    r = client.post('/register',
                    data={
                        'email': '*****@*****.**',
                        'password': '******'
                    })
    assert r.status_code == 302
    assert 1 == User.query.count()

    # upgrade user
    user = User.query.filter_by(email='*****@*****.**').first()
    user.upgraded = True
    DB.session.add(user)
    DB.session.commit()

    # successfully create form
    r = client.post(
        "/api-int/forms",
        headers={
            "Referer": settings.SERVICE_URL,
            "Content-type": "application/json"
        },
        data=json.dumps({"email": "*****@*****.**"}),
    )
    resp = json.loads(r.data.decode('utf-8'))
    assert r.status_code == 200
    assert 'submission_url' in resp
    assert 'hashid' in resp
    form_endpoint = resp['hashid']
    assert resp['hashid'] in resp['submission_url']
    assert 1 == Form.query.count()
    assert Form.query.first().id == Form.get_with_hashid(resp['hashid']).id

    # post to form
    r = client.post('/' + form_endpoint,
                    headers={'Referer': 'formspree.io'},
                    data={'name': 'bruce'})

    # confirm form
    form = Form.query.first()
    client.get('/confirm/%s:%s' %
               (HASH(form.email, str(form.id)), form.hashid))
    assert Form.query.first().confirmed
    assert 0 == Submission.query.count()

    # disable the form
    r = client.patch(
        "/api-int/forms/" + form_endpoint,
        headers={
            "Referer": settings.SERVICE_URL,
            "Content-Type": "application/json"
        },
        data=json.dumps({"disabled": True}),
    )
    assert 200 == r.status_code
    assert r.json["ok"]
    assert Form.query.first().disabled
    assert 0 == Form.query.first().counter

    # logout and attempt to enable the form
    client.get("/logout")
    r = client.patch(
        "/api-int/forms/" + form_endpoint,
        headers={
            "Content-Type": "application/json",
            "Referer": settings.SERVICE_URL
        },
        data=json.dumps({"disabled": True}),
    )
    assert 401 == r.status_code
    assert "error" in json.loads(r.data.decode("utf-8"))
    assert Form.query.first().disabled

    # fail when attempting to post to form
    r = client.post('/' + form_endpoint,
                    headers={'Referer': 'formspree.io'},
                    data={'name': 'bruce'})
    assert 403 == r.status_code
    assert 0 == Form.query.first().counter

    # log back in and re-enable form
    r = client.post("/login",
                    data={
                        "email": "*****@*****.**",
                        "password": "******"
                    })
    r = client.patch(
        "/api-int/forms/" + form_endpoint,
        headers={
            "Referer": settings.SERVICE_URL,
            "Content-Type": "application/json"
        },
        data=json.dumps({"disabled": False}),
    )
    assert 200 == r.status_code
    assert not Form.query.first().disabled

    # successfully post to form
    r = client.post('/' + form_endpoint,
                    headers={'Referer': 'formspree.io'},
                    data={'name': 'bruce'})
    assert 1 == Form.query.first().counter
Example #50
0
def test_form_creation(client, msend):
    # register user
    r = client.post(
        "/register", data={"email": "*****@*****.**", "password": "******"}
    )
    assert r.status_code == 302
    assert 1 == User.query.count()

    # fail to create form
    r = client.post(
        "/api-int/forms",
        headers={"Content-type": "application/json", "Referer": settings.SERVICE_URL},
        data=json.dumps({"email": "*****@*****.**"}),
    )
    assert r.status_code == 402
    assert "error" in json.loads(r.data.decode("utf-8"))
    assert 0 == Form.query.count()

    # upgrade user manually
    user = User.query.filter_by(email="*****@*****.**").first()
    user.plan = Plan.gold
    DB.session.add(user)
    DB.session.commit()

    # verify email
    email = Email.query.filter_by(
        address="*****@*****.**", owner_id=user.id
    ).first()
    email.verified = True
    DB.session.add(email)
    DB.session.commit()

    # successfully create form
    msend.reset_mock()
    r = client.post(
        "/api-int/forms",
        headers={
            "Accept": "application/json",
            "Content-type": "application/json",
            "Referer": settings.SERVICE_URL,
        },
        data=json.dumps({"email": "*****@*****.**"}),
    )

    # no email should have been sent
    assert not msend.called

    # should return success payload
    resp = json.loads(r.data.decode("utf-8"))
    assert r.status_code == 201
    assert "submission_url" in resp
    assert "hashid" in resp
    form_endpoint = resp["hashid"]
    assert resp["hashid"] in resp["submission_url"]

    # a form should now exist in the db
    assert 1 == Form.query.count()
    assert Form.query.first().id == Form.get_with(hashid=resp["hashid"]).id
    assert Form.query.first().confirmed

    # post to the form
    r = client.post(
        "/" + form_endpoint,
        headers={"Referer": "http://testsite.com"},
        data={"name": "bruce"},
        follow_redirects=True,
    )

    # Should get thank-you page
    assert "Thanks!" in r.data.decode("utf-8")
    assert "You're only one step away" not in msend.call_args[1]["text"]

    # send 5 forms (monthly limits should not apply to the gold user)
    assert settings.MONTHLY_SUBMISSIONS_LIMIT == 2
    for i in range(5):
        r = client.post(
            "/" + form_endpoint,
            headers={"Referer": "http://testsite.com"},
            data={"name": "ana", "submission": "__%s__" % i},
        )

    # form should now have 6 submissions
    form = Form.query.first()
    assert form.counter == 6

    # check last submission email
    assert "ana" in msend.call_args[1]["text"]
    assert "__4__" in msend.call_args[1]["text"]
    assert "past the limit" not in msend.call_args[1]["text"]

    # submit form from a different host -- this is OK now
    r = client.post(
        "/" + form_endpoint,
        headers={"Referer": "http://different.com"},
        data={"name": "permitted"},
        follow_redirects=True,
    )
    assert r.status_code == 200
    assert "permitted" in msend.call_args[1]["text"]

    # submit form without a Referer -- this is OK now
    r = client.post(
        "/" + form_endpoint, data={"name": "permitted"}, follow_redirects=True
    )
    assert r.status_code == 200
    assert "permitted" in msend.call_args[1]["text"]
    assert "unknown webpage" in msend.call_args[1]["text"]