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
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_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
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_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()
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, )
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()
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, )
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)
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)
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
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
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)
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})
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
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)
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]) }, )
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, } ) )
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, } ) )
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})
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()
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
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", } ) ) )
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]) } )
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"]} )
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)
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
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"]
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)
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!"
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
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
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, )
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)
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
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
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)
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)
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
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"]