def send_confirmation(addr, user_id): addr = addr.lower().strip() if not IS_VALID_EMAIL(addr): raise ValueError( 'Cannot send confirmation. %s is not a valid email.' % addr) message = 'email={email}&user_id={user_id}'.format(email=addr, user_id=user_id) digest = hmac.new(settings.NONCE_SECRET, message, hashlib.sha256).hexdigest() link = url_for('confirm-account-email', digest=digest, email=addr, _external=True) res = send_email(to=addr, subject='Confirm email for your account at %s' % settings.SERVICE_NAME, text=render_template('email/confirm-account.txt', email=addr, link=link), html=render_template('email/confirm-account.html', email=addr, link=link), sender=settings.ACCOUNT_SENDER) if not res[0]: return False else: return True
def send(email_or_string): ''' Main endpoint, finds or creates the form row from the database, checks validity and state of the form and sends either form data or verification to email. ''' g.log = g.log.bind(target=email_or_string) if request.method == 'GET': return errors.bad_method_error() if request.form: received_data, sorted_keys = http_form_to_dict(request.form) else: received_data = request.get_json() or {} sorted_keys = received_data.keys() sorted_keys = [k for k in sorted_keys if k not in KEYS_EXCLUDED_FROM_EMAIL] try: # NOTE: host in this function generally refers to the referrer hostname. host, referrer = get_host_and_referrer(received_data) except SubmitFormError as vfe: return vfe.response if not host: return errors.no_referrer_error() g.log = g.log.bind(host=host, wants='json' if request_wants_json() else 'html') if not IS_VALID_EMAIL(email_or_string): # in this case it can be a hashid identifying a # form generated from the dashboard try: form = validate_user_form(email_or_string, host) except SubmitFormError as vfe: return vfe.response else: # in this case, it is a normal email try: form = get_or_create_form(email_or_string.lower(), host) except SubmitFormError as vfe: return vfe.response # If form exists and is confirmed, send email # otherwise send a confirmation email if form.confirmed: captcha_page = check_captcha(form, email_or_string, received_data, sorted_keys) if captcha_page: return captcha_page status = form.send(received_data, sorted_keys, referrer) else: status = form.send_confirmation(store_data=received_data) return response_for_status(form, host, referrer, status)
def send(email_or_string): ''' Main endpoint, finds or creates the form row from the database, checks validity and state of the form and sends either form data or verification to email. ''' if request.method == 'GET': if request_wants_json(): return jsonerror(405, {'error': "Please submit POST request."}) else: return render_template( 'info.html', title='Form should POST', text= 'Make sure your form has the <span class="code"><strong>method="POST"</strong></span> attribute' ), 405 host = referrer_to_path(flask.request.referrer) if not host: if request_wants_json(): return jsonerror(400, {'error': "Invalid \"Referrer\" header"}) else: return render_template( 'error.html', title='Unable to submit form', text= 'Make sure your form is running on a proper server. For geeks: could not find the "Referrer" header.' ), 400 if not IS_VALID_EMAIL(email_or_string): # in this case it can be a hashid identifying a # form generated from the dashboard hashid = email_or_string form = Form.get_with_hashid(hashid) if form: email = form.email if not form.host: # add the host to the form form.host = host DB.session.add(form) DB.session.commit() elif form.host != host: # if the form submission came from a different host, it is an error if request_wants_json(): return jsonerror( 403, { 'error': "Submission from different host than confirmed", 'submitted': host, 'confirmed': form.host }) else: return render_template( 'error.html', title='Check form address', text='This submission came from "%s" but the form was\ confirmed for the address "%s"' % (host, form.host)), 403 else: # no form row found. it is an error. if request_wants_json(): return jsonerror(400, {'error': "Invalid email address"}) else: return render_template('error.html', title='Check email address', text='Email address %s is not formatted correctly' \ % str(email_or_string)), 400 else: # in this case, it is a normal email email = email_or_string # get the form for this request form = Form.query.filter_by(hash=HASH(email, host)).first() \ or Form(email, host) # or create it if it doesn't exists # If form exists and is confirmed, send email # otherwise send a confirmation email if form.confirmed: status = form.send(request.form, request.referrer) else: status = form.send_confirmation() # Respond to the request accordingly to the status code if status['code'] == Form.STATUS_EMAIL_SENT: if request_wants_json(): return jsonify({'success': "email sent", 'next': status['next']}) else: return redirect(status['next'], code=302) elif status['code'] == Form.STATUS_EMAIL_EMPTY: if request_wants_json(): return jsonerror(400, {'error': "Can't send an empty form"}) else: return render_template('error.html', title='Can\'t send an empty form', text=str('<a href="%s">Return to form</a>' % request.referrer)), 400 elif status['code'] == Form.STATUS_CONFIRMATION_SENT or \ status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED: if request_wants_json(): return jsonify({'success': "confirmation email sent"}) else: return render_template('forms/confirmation_sent.html', email=email, host=host) if request_wants_json(): return jsonerror(500, {'error': "Unable to send email"}) else: return render_template('error.html', title='Unable to send email', text='Unable to send email'), 500
def forms(): if request.method == 'GET': ''' A reminder: this is the /forms endpoint, but for GET requests it is also the /dashboard endpoint. The /dashboard endpoint, the address gave by url_for('dashboard'), is the target of a lot of redirects around the app, but it can be changed later to point to somewhere else. ''' # grab all the forms this user controls if current_user.upgraded: forms = current_user.forms.order_by(Form.id.desc()).all() else: forms = [] if request_wants_json(): return jsonify({ 'ok': True, 'forms': [{ 'email': f.email, 'host': f.host, 'confirm_sent': f.confirm_sent, 'confirmed': f.confirmed, 'is_public': bool(f.hash), 'url': '{S}/{E}'.format(S=settings.SERVICE_URL, E=f.hashid) } for f in forms] }) else: return render_template('forms/list.html', forms=forms) elif request.method == 'POST': # create a new form if not current_user.upgraded: return jsonerror(402, {'error': "Please upgrade your account."}) if request.get_json(): email = request.get_json().get('email') else: email = request.form.get('email') if not IS_VALID_EMAIL(email): if request_wants_json(): return jsonerror( 400, {'error': "The email you sent is not a valid email."}) else: flash('The email you provided is not a valid email.', 'error') return redirect(url_for('dashboard')) form = Form(email, owner=current_user) DB.session.add(form) DB.session.commit() if request_wants_json(): return jsonify({ 'ok': True, 'hashid': form.hashid, 'submission_url': settings.API_ROOT + '/' + form.hashid }) else: flash('Your new form endpoint was created!', 'success') return redirect(url_for('dashboard') + '#view-code-' + form.hashid)
def send(self, submitted_data, referrer): ''' Sends form to user's email. Assumes sender's email has been verified. ''' if type(submitted_data) in (ImmutableMultiDict, ImmutableOrderedMultiDict): data, keys = http_form_to_dict(submitted_data) else: data, keys = submitted_data, submitted_data.keys() subject = data.get('_subject', 'New submission from %s' % referrer_to_path(referrer)) reply_to = data.get('_replyto', data.get('email', data.get('Email', ''))).strip() cc = data.get('_cc', None) next = next_url(referrer, data.get('_next')) spam = data.get('_gotcha', None) format = data.get('_format', None) # turn cc emails into array if cc: cc = [email.strip() for email in cc.split(',')] # prevent submitting empty form if not any(data.values()): return { 'code': Form.STATUS_EMAIL_EMPTY } # return a fake success for spam if spam: return { 'code': Form.STATUS_EMAIL_SENT, 'next': next } # validate reply_to, if it is not a valid email address, reject if reply_to and not IS_VALID_EMAIL(reply_to): return { 'code': Form.STATUS_REPLYTO_ERROR, 'error-message': '"%s" is not a valid email address.' % reply_to } # increase the monthly counter request_date = datetime.datetime.now() self.increase_monthly_counter(basedate=request_date) # increment the forms counter self.counter = Form.counter + 1 DB.session.add(self) # archive the form contents sub = Submission(self.id) sub.data = data DB.session.add(sub) # commit changes DB.session.commit() # delete all archived submissions over the limit records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT newest = self.submissions.with_entities(Submission.id).limit(records_to_keep) DB.engine.execute( delete('submissions'). \ where(Submission.form_id == self.id). \ where(~Submission.id.in_(newest)) ) # check if the forms are over the counter and the user is not upgraded overlimit = False monthly_counter = self.get_monthly_counter() if monthly_counter > settings.MONTHLY_SUBMISSIONS_LIMIT: overlimit = True if self.controllers: for c in self.controllers: if c.upgraded: overlimit = False break now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y') if not overlimit: text = render_template('email/form.txt', data=data, host=self.host, keys=keys, now=now) # check if the user wants a new or old version of the email if format == 'plain': html = render_template('email/plain_form.html', data=data, host=self.host, keys=keys, now=now) else: html = render_template('email/form.html', data=data, host=self.host, keys=keys, now=now) else: if monthly_counter - settings.MONTHLY_SUBMISSIONS_LIMIT > 25: # only send this overlimit notification for the first 25 overlimit emails # after that, return an error so the user can know the website owner is not # going to read his message. return { 'code': Form.STATUS_OVERLIMIT } text = render_template('email/overlimit-notification.txt', host=self.host) html = render_template('email/overlimit-notification.html', host=self.host) result = send_email(to=self.email, subject=subject, text=text, html=html, sender=settings.DEFAULT_SENDER, reply_to=reply_to, cc=cc) if not result[0]: if result[1].startswith('Invalid replyto email address'): return { 'code': Form.STATUS_REPLYTO_ERROR} return{ 'code': Form.STATUS_EMAIL_FAILED, 'mailer-code': result[2], 'error-message': result[1] } return { 'code': Form.STATUS_EMAIL_SENT, 'next': next }
def send(email_or_string): ''' Main endpoint, finds or creates the form row from the database, checks validity and state of the form and sends either form data or verification to email. ''' if request.method == 'GET': if request_wants_json(): return jsonerror(405, {'error': "Please submit POST request."}) else: return render_template( 'info.html', title='Form should POST', text= 'Make sure your form has the <span class="code"><strong>method="POST"</strong></span> attribute' ), 405 host = referrer_to_path(flask.request.referrer) if not host: if request_wants_json(): return jsonerror(400, {'error': "Invalid \"Referrer\" header"}) else: return render_template( 'error.html', title='Unable to submit form', text= 'Make sure you open this page through a web server, Formspree will not work in pages browsed as HTML files. For geeks: could not find the "Referrer" header.' ), 400 if not IS_VALID_EMAIL(email_or_string): # in this case it can be a hashid identifying a # form generated from the dashboard hashid = email_or_string form = Form.get_with_hashid(hashid) if form: email = form.email if not form.host: # add the host to the form form.host = host DB.session.add(form) DB.session.commit() elif form.host != host: # if the form submission came from a different host, it is an error if request_wants_json(): return jsonerror( 403, { 'error': "Submission from different host than confirmed", 'submitted': host, 'confirmed': form.host }) else: return render_template( 'error.html', title='Check form address', text='This submission came from "%s" but the form was\ confirmed for the address "%s"' % (host, form.host)), 403 else: # no form row found. it is an error. if request_wants_json(): return jsonerror(400, {'error': "Invalid email address"}) else: return render_template('error.html', title='Check email address', text='Email address %s is not formatted correctly' \ % str(email_or_string)), 400 else: # in this case, it is a normal email email = email_or_string # get the form for this request form = Form.query.filter_by(hash=HASH(email, host)).first() \ or Form(email, host) # or create it if it doesn't exists # If form exists and is confirmed, send email # otherwise send a confirmation email if form.confirmed: status = form.send(request.form, request.referrer) else: status = form.send_confirmation(with_data=request.form) # Respond to the request accordingly to the status code if status['code'] == Form.STATUS_EMAIL_SENT: if request_wants_json(): return jsonify({'success': "email sent", 'next': status['next']}) else: return redirect(status['next'], code=302) elif status['code'] == Form.STATUS_EMAIL_EMPTY: if request_wants_json(): return jsonerror(400, {'error': "Can't send an empty form"}) else: return render_template( 'error.html', title='Can\'t send an empty form', text=str( '<p>Make sure you have placed the <a href="http://www.w3schools.com/tags/att_input_name.asp" target="_blank">"name" attribute</a> in all your form elements. Also, to prevent empty form submissions, take a look at the <a href="http://www.w3schools.com/tags/att_input_required.asp" target="_blank">"required" property</a> or <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input" target="_blank">see more HTML form customization info</a>.</p><p><a href="%s">Return to form</a></p>' % request.referrer)), 400 elif status['code'] == Form.STATUS_CONFIRMATION_SENT or \ status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED: if request_wants_json(): return jsonify({'success': "confirmation email sent"}) else: return render_template( 'forms/confirmation_sent.html', email=email, host=host, resend=status['code'] == Form.STATUS_CONFIRMATION_DUPLICATED) elif status['code'] == Form.STATUS_OVERLIMIT: if request_wants_json(): return jsonify({'error': "form over quota"}) else: return render_template( 'error.html', title='Form over quota', text= 'It looks like this form is getting a lot of submissions and ran out of its quota. Try contacting this website through other means or try submitting again later.' ) elif status['code'] == Form.STATUS_REPLYTO_ERROR: if request_wants_json(): return jsonerror(500, { 'error': "_replyto or email field has not been sent correctly" }) else: return render_template( 'error.html', title='Unable to send email', text= 'Unable to send email. The field with a name attribute _replyto or email was not set correctly. This may be the result of you have multiple _replyto or email fields. If you cannot find your error, please contact <b>[email protected]</b> with a link to your form and this error message: <p><pre><code>' + status['error-message'] + '</code></pre></p>'), 500 # error fallback -- shouldn't happen if request_wants_json(): return jsonerror(500, {'error': "Unable to send email"}) else: return render_template( 'error.html', title='Unable to send email', text= 'Unable to send email. If you can, please send the link to your form and the error information to <b>[email protected]</b>. And send them the following: <p><pre><code>' + json.dumps(status) + '</code></pre></p>'), 500
def create(): # check that this request came from user dashboard to prevent XSS and CSRF referrer = referrer_to_baseurl(request.referrer) service = referrer_to_baseurl(settings.SERVICE_URL) if referrer != service: return jsonerror(400, {'error': 'Improper request.'}) if not current_user.has_feature('dashboard'): g.log.info('Failed to create form from dashboard. Forbidden.') return jsonerror(402, {'error': "Please upgrade your account."}) email = request.get_json().get('email') url = request.get_json().get('url') sitewide = request.get_json().get('sitewide') g.log = g.log.bind(email=email, url=url, sitewide=sitewide) if not IS_VALID_EMAIL(email): g.log.info('Failed to create form from dashboard. Invalid address.') return jsonerror(400, {'error': "The provided email address is not valid."}) g.log.info('Creating a new form from the dashboard.') email = email.lower().strip() # case-insensitive form = Form(email, owner=current_user) if url: url = 'http://' + url if not url.startswith('http') else url form.host = referrer_to_path(url) # sitewide forms, verified with a file at the root of the target domain if sitewide: if sitewide_file_check(url, email): form.host = remove_www( referrer_to_path(urljoin(url, '/'))[:-1]) form.sitewide = True else: return jsonerror( 403, {'error': u"Couldn't verify the file at {}.".format(url)}) DB.session.add(form) DB.session.commit() if form.host: # when the email and url are provided, we can automatically confirm the form # but only if the email is registered for this account for email in current_user.emails: if email.address == form.email: g.log.info('No need for email confirmation.') form.confirmed = True DB.session.add(form) DB.session.commit() break else: # in case the email isn't registered for this user # we automatically send the email confirmation form.send_confirmation() return jsonify({ 'ok': True, 'hashid': form.hashid, 'submission_url': settings.API_ROOT + '/' + form.hashid, 'confirmed': form.confirmed })
def create_form(): # create a new form if not current_user.upgraded: return jsonerror(402, {'error': "Please upgrade your account."}) if request.get_json(): email = request.get_json().get('email') url = request.get_json().get('url') sitewide = request.get_json().get('sitewide') else: email = request.form.get('email') url = request.form.get('url') sitewide = request.form.get('sitewide') if not IS_VALID_EMAIL(email): if request_wants_json(): return jsonerror(400, {'error': "The provided email address is not valid."}) else: flash('The provided email address is not valid.', 'error') return redirect(url_for('dashboard')) form = Form(email, owner=current_user) if url: url = 'http://' + url if not url.startswith('http') else url form.host = referrer_to_path(url) # sitewide forms, verified with a file at the root of the target domain if sitewide: if sitewide_file_exists(url, email): form.host = remove_www(referrer_to_path(urljoin(url, '/'))[:-1]) form.sitewide = True else: return jsonerror(403, {'error': "Couldn't find the verification file."}) DB.session.add(form) DB.session.commit() if form.host: # when the email and url are provided, we can automatically confirm the form # but only if the email is registered for this account for email in current_user.emails: if email.address == form.email: form.confirmed = True DB.session.add(form) DB.session.commit() break else: # in case the email isn't registered for this user # we automatically send the email confirmation form.send_confirmation() if request_wants_json(): return jsonify({ 'ok': True, 'hashid': form.hashid, 'submission_url': settings.API_ROOT + '/' + form.hashid, 'confirmed': form.confirmed }) else: flash('Your new form endpoint was created!', 'success') return redirect(url_for('dashboard') + '#view-code-' + form.hashid)