Example #1
0
def send_downgrade_email(customer_email):
    send_email(to=customer_email,
               subject='Successfully downgraded from {} {}'.format(
                   settings.SERVICE_NAME, settings.UPGRADED_PLAN_NAME),
               text=render_template('email/downgraded.txt'),
               html=render_template('email/downgraded.html'),
               sender=settings.DEFAULT_SENDER)
Example #2
0
def request_unconfirm_form(form_id):
    '''
    This endpoints triggers a confirmation email that directs users to the
    GET version of unconfirm_form.
    '''
    form = Form.query.get(form_id)

    unconfirm_url = url_for('unconfirm_form',
                            form_id=form.id,
                            digest=form.unconfirm_digest(),
                            _external=True)
    send_email(
        to=form.email,
        subject='Unsubscribe from form at {}'.format(form.host),
        html=render_template_string(
            TEMPLATES.get('unsubscribe-confirmation.html'),
            url=unconfirm_url,
            email=form.email,
            host=form.host),
        text=render_template('email/unsubscribe-confirmation.txt',
                             url=unconfirm_url,
                             email=form.email,
                             host=form.host),
        sender=settings.DEFAULT_SENDER,
    )

    return render_template(
        'info.html',
        title='Link sent to your address',
        text="We've sent an email to {} with a link to finish "
        "unsubscribing.".format(form.email)), 200
Example #3
0
def request_unconfirm_form(form_id):
    '''
    This endpoints triggers a confirmation email that directs users to the
    GET version of unconfirm_form.
    '''
    form = Form.query.get(form_id)

    unconfirm_url = url_for(
        'unconfirm_form',
        form_id=form.id,
        digest=form.unconfirm_digest(),
        _external=True
    )
    send_email(
        to=form.email,
        subject='Unsubscribe from form at {}'.format(form.host),
        html=render_template_string(TEMPLATES.get('unsubscribe-confirmation.html'),
                                    url=unconfirm_url,
                                    email=form.email,
                                    host=form.host),
        text=render_template('email/unsubscribe-confirmation.txt',
            url=unconfirm_url,
            email=form.email,
            host=form.host),
        sender=settings.DEFAULT_SENDER,
    )

    return render_template('info.html',
        title='Link sent to your address',
        text="We've sent an email to {} with a link to finish "
             "unsubscribing.".format(form.email)), 200
Example #4
0
def stripe_webhook():
    event = request.get_json()
    g.log.info('Webhook from Stripe', type=event['type'])

    try:
        if event['type'] == 'customer.subscription.deleted': # User subscription has expired
            customer_id = event['data']['object']['customer']
            customer = stripe.Customer.retrieve(customer_id)
            if len(customer.subscriptions.data) == 0:
                user = User.query.filter_by(stripe_id=customer_id).first()
                user.upgraded = False
                DB.session.add(user)
                DB.session.commit()
                g.log.info('Downgraded user from webhook.', account=user.email)
                send_email(to=customer.email,
                           subject='Successfully Downgraded from {} {}'.format(settings.SERVICE_NAME, settings.UPGRADED_PLAN_NAME),
                           text=render_template('email/downgraded.txt'),
                           html=render_template('email/downgraded.html'),
                           sender=settings.DEFAULT_SENDER)
        elif event['type'] == 'invoice.payment_failed': # User payment failed
            customer_id = event['data']['object']['customer']
            customer = stripe.Customer.retrieve(customer_id)
            g.log.info('User payment failed', account=customer.email)
            send_email(to=customer.email,
                       subject='[ACTION REQUIRED] Failed Payment for {} {}'.format(settings.SERVICE_NAME,
                                                                           settings.UPGRADED_PLAN_NAME),
                       text=render_template('email/payment-failed.txt'),
                       html=render_template('email/payment-failed.html'),
                       sender=settings.DEFAULT_SENDER)
    except Exception as e:
        g.log.error('Webhook failed for customer', json=event, error=e)
        return 'Failure, developer please check logs', 500
    return 'ok'
Example #5
0
def send_downgrade_reason_email(customer_email, reason):
    send_email(
        to=settings.CONTACT_EMAIL,
        reply_to=customer_email,
        subject="A customer has downgraded from {}".format(settings.UPGRADED_PLAN_NAME),
        text=render_template("email/downgraded_reason.txt", reason=reason),
        sender=settings.DEFAULT_SENDER,
    )
Example #6
0
def send_downgrade_reason_email(customer_email, reason):
    send_email(to=settings.CONTACT_EMAIL,
               reply_to=customer_email,
               subject='A customer has downgraded from {}'.format(
                   settings.UPGRADED_PLAN_NAME),
               text=render_template('email/downgraded_reason.txt',
                                    reason=reason),
               sender=settings.DEFAULT_SENDER)
Example #7
0
def send_downgrade_email(customer_email):
    send_email(
        to=customer_email,
        subject='Successfully downgraded from {} {}'.format(settings.SERVICE_NAME,
                                                            settings.UPGRADED_PLAN_NAME),
        text=render_template('email/downgraded.txt'),
        html=render_template('email/downgraded.html'),
        sender=settings.DEFAULT_SENDER
    )
Example #8
0
def stripe_webhook():
    payload = request.data
    g.log.info('Received webhook from Stripe')

    sig_header = request.headers.get('STRIPE_SIGNATURE')
    event = None

    try:
        if settings.TESTING:
            event = request.get_json()
        else:
            event = stripe.Webhook.construct_event(
                payload, sig_header, settings.STRIPE_WEBHOOK_SECRET)
        if event[
                'type'] == 'customer.subscription.deleted':  # User subscription has expired
            customer_id = event['data']['object']['customer']
            customer = stripe.Customer.retrieve(customer_id)
            if len(customer.subscriptions.data) == 0:
                user = User.query.filter_by(stripe_id=customer_id).first()
                user.upgraded = False
                DB.session.add(user)
                DB.session.commit()
                g.log.info('Downgraded user from webhook.', account=user.email)
                send_email(to=customer.email,
                           subject='Successfully Downgraded from {} {}'.format(
                               settings.SERVICE_NAME,
                               settings.UPGRADED_PLAN_NAME),
                           text=render_template('email/downgraded.txt'),
                           html=render_template('email/downgraded.html'),
                           sender=settings.DEFAULT_SENDER)
        elif event['type'] == 'invoice.payment_failed':  # User payment failed
            customer_id = event['data']['object']['customer']
            customer = stripe.Customer.retrieve(customer_id)
            g.log.info('User payment failed', account=customer.email)
            send_email(
                to=customer.email,
                subject='[ACTION REQUIRED] Failed Payment for {} {}'.format(
                    settings.SERVICE_NAME, settings.UPGRADED_PLAN_NAME),
                text=render_template('email/payment-failed.txt'),
                html=render_template('email/payment-failed.html'),
                sender=settings.DEFAULT_SENDER)
        return 'ok'
    except ValueError as e:
        g.log.error('Webhook failed for customer', json=event, error=e)
        return 'Failure, developer please check logs', 500
    except stripe.error.SignatureVerificationError as e:
        g.log.error('Webhook failed Stripe signature verification',
                    json=event,
                    error=e)
        return '', 400
    except Exception as e:
        g.log.error('Webhook failed for unknown reason', json=event, error=e)
        return '', 500
Example #9
0
    def dispatch(self, submission, sorted_keys):
        key = PLUGINFAILURE_KEY(id=self.id)
        nfailures = redis_store.get(key) or b"0"
        nfailures = int(nfailures.decode("ascii"))

        if nfailures >= settings.PLUGINS_MAX_FAILURES:
            g.log.debug(
                "Disabling plugin due to an excess of failures.",
                kind=self.kind,
                form=self.form_id,
                failures=nfailures,
            )
            redis_store.delete(key)
            self.enabled = False
            DB.session.add(self)
            DB.session.commit()

            # notify them through email
            render_args = dict(
                plugin_kind=self.kind,
                form_name=self.form.hashid,
                nfailures=nfailures,
                form_plugins_link=url_for(
                    "form-page-section",
                    hashid=self.form.hashid,
                    s="plugins",
                    _external=True,
                ),
            )
            send_email(
                to=self.form.email,
                subject="Plugin disabled due to delivery failure",
                text=render_template(
                    "email/plugin-disabled-notification.txt", **render_args
                ),
                html=render_email("plugin-disabled-notification.html", **render_args),
                sender=settings.DEFAULT_SENDER,
            )
        else:
            g.log.debug(
                "Dispatching submission to plugin.",
                kind=self.kind,
                form=self.form_id,
                failures=nfailures,
            )
            {
                PluginKind.google_sheets: self.dispatch_google_sheets,
                PluginKind.webhook: self.dispatch_webhook,
                PluginKind.trello: self.dispatch_trello,
                PluginKind.slack: self.dispatch_slack,
                PluginKind.mailchimp: self.dispatch_mailchimp,
            }[self.kind](submission, sorted_keys)
Example #10
0
def stripe_webhook():
    payload = request.data.decode("utf-8")
    g.log.info("Received webhook from Stripe")

    sig_header = request.headers.get("STRIPE_SIGNATURE")
    event = None

    try:
        if settings.TESTING:
            event = request.get_json()
        else:
            event = stripe.Webhook.construct_event(
                payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
            )
        if (
            event["type"] == "customer.subscription.deleted"
        ):  # User subscription has expired
            customer_id = event["data"]["object"]["customer"]
            customer = stripe.Customer.retrieve(customer_id)
            if len(customer.subscriptions.data) == 0:
                user = User.query.filter_by(stripe_id=customer_id).first()
                user.plan = Plan.free
                DB.session.add(user)
                DB.session.commit()
                g.log.info("Downgraded user from webhook.", account=user.email)
                send_downgrade_email.delay(user.email)
        elif event["type"] == "invoice.payment_failed":  # User payment failed
            customer_id = event["data"]["object"]["customer"]
            customer = stripe.Customer.retrieve(customer_id)
            g.log.info("User payment failed", account=customer.email)
            send_email(
                to=customer.email,
                subject="[ACTION REQUIRED] Failed Payment for {} {}".format(
                    settings.SERVICE_NAME, settings.UPGRADED_PLAN_NAME
                ),
                text=render_template("email/payment-failed.txt"),
                html=render_email("payment-failed.html"),
                sender=settings.DEFAULT_SENDER,
            )
        return "ok"
    except ValueError as e:
        g.log.error("Webhook failed for customer", json=event, error=e)
        return "Failure, developer please check logs", 500
    except stripe.error.SignatureVerificationError as e:
        g.log.error("Webhook failed Stripe signature verification", json=event, error=e)
        return "", 400
    except Exception as e:
        g.log.error("Webhook failed for unknown reason", json=event, error=e)
        return "", 500
Example #11
0
    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
Example #12
0
    def send_confirmation(addr, user_id):
        g.log = g.log.new(address=addr, user_id=user_id)
        g.log.info('Sending email confirmation for new address on account.')

        addr = addr.lower().strip()
        if not IS_VALID_EMAIL(addr):
            g.log.info('Failed. Invalid address.')
            raise ValueError(u'Cannot send confirmation. '
                             '{} is not a valid email.'.format(addr))

        message = u'email={email}&user_id={user_id}'.format(
            email=addr,
            user_id=user_id)
        digest = hmac.new(
            settings.NONCE_SECRET,
            message.encode('utf-8'),
            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_string(TEMPLATES.get('confirm-account.html'), email=addr, link=link),
            sender=settings.ACCOUNT_SENDER
        )
        if not res[0]:
            g.log.info('Failed to send email.', reason=res[1], code=res[2])
            return False
        else:
            return True
Example #13
0
def request_unconfirm_form(form_id):
    """
    This endpoints triggers a confirmation email that directs users to the
    GET version of unconfirm_form.
    """

    # repel bots
    if not request.user_agent.browser:
        return ""

    form = Form.query.get(form_id)

    unconfirm_url = url_for(
        "unconfirm_form",
        form_id=form.id,
        digest=form.unconfirm_digest(),
        _external=True,
    )
    send_email(
        to=form.email,
        subject="Unsubscribe from form at {}".format(form.host),
        html=render_email(
            "unsubscribe-confirmation.html",
            url=unconfirm_url,
            email=form.email,
            host=form.host,
        ),
        text=render_template(
            "email/unsubscribe-confirmation.txt",
            url=unconfirm_url,
            email=form.email,
            host=form.host,
        ),
        sender=settings.DEFAULT_SENDER,
    )

    return (
        render_template(
            "info.html",
            title="Link sent to your address",
            text="We've sent an email to {} with a link to finish "
            "unsubscribing.".format(form.email),
        ),
        200,
    )
Example #14
0
def request_unconfirm_form(form_id):
    """
    This endpoints triggers a confirmation email that directs users to the
    GET version of unconfirm_form.
    """

    # repel bots
    if not request.user_agent.browser:
        return ""

    form = Form.query.get(form_id)

    unconfirm_url = url_for(
        "unconfirm_form",
        form_id=form.id,
        digest=form.unconfirm_digest(),
        _external=True,
    )
    send_email(
        to=form.email,
        subject="Unsubscribe from form at {}".format(form.host),
        html=render_email(
            "unsubscribe-confirmation.html",
            url=unconfirm_url,
            email=form.email,
            host=form.host,
        ),
        text=render_template(
            "email/unsubscribe-confirmation.txt",
            url=unconfirm_url,
            email=form.email,
            host=form.host,
        ),
        sender=settings.DEFAULT_SENDER,
    )

    return (
        render_template(
            "info.html",
            title="Link sent to your address",
            text="We've sent an email to {} with a link to finish "
            "unsubscribing.".format(form.email),
        ),
        200,
    )
Example #15
0
def stripe_webhook():
    payload = request.data.decode('utf-8')
    g.log.info('Received webhook from Stripe')

    sig_header = request.headers.get('STRIPE_SIGNATURE')
    event = None

    try:
        if settings.TESTING:
            event = request.get_json()
        else:
            event = stripe.Webhook.construct_event(
                payload, sig_header, settings.STRIPE_WEBHOOK_SECRET)
        if event['type'] == 'customer.subscription.deleted':  # User subscription has expired
            customer_id = event['data']['object']['customer']
            customer = stripe.Customer.retrieve(customer_id)
            if len(customer.subscriptions.data) == 0:
                user = User.query.filter_by(stripe_id=customer_id).first()
                user.upgraded = False
                DB.session.add(user)
                DB.session.commit()
                g.log.info('Downgraded user from webhook.', account=user.email)
                send_downgrade_email.delay(user.email)
        elif event['type'] == 'invoice.payment_failed':  # User payment failed
            customer_id = event['data']['object']['customer']
            customer = stripe.Customer.retrieve(customer_id)
            g.log.info('User payment failed', account=customer.email)
            send_email(to=customer.email,
                       subject='[ACTION REQUIRED] Failed Payment for {} {}'.format(settings.SERVICE_NAME,
                                                                                   settings.UPGRADED_PLAN_NAME),
                       text=render_template('email/payment-failed.txt'),
                       html=render_template('email/payment-failed.html'),
                       sender=settings.DEFAULT_SENDER)
        return 'ok'
    except ValueError as e:
        g.log.error('Webhook failed for customer', json=event, error=e)
        return 'Failure, developer please check logs', 500
    except stripe.error.SignatureVerificationError as e:
        g.log.error('Webhook failed Stripe signature verification', json=event, error=e)
        return '', 400
    except Exception as e:
        g.log.error('Webhook failed for unknown reason', json=event, error=e)
        return '', 500
Example #16
0
    def send_confirmation(self, with_data=None):
        '''
        Helper that actually creates confirmation nonce
        and sends the email to associated email. Renders
        different templates depending on the result
        '''

        log.debug('Sending confirmation')
        if self.confirm_sent:
            return {'code': Form.STATUS_CONFIRMATION_DUPLICATED}

        # the nonce for email confirmation will be the hash when it exists
        # (whenever the form was created from a simple submission) or
        # a concatenation of HASH(email, id) + ':' + hashid
        # (whenever the form was created from the dashboard)
        id = str(self.id)
        nonce = self.hash or '%s:%s' % (HASH(self.email, id), self.hashid)
        link = url_for('confirm_email', nonce=nonce, _external=True)

        def render_content(ext):
            data, keys = None, None
            if with_data:
                if type(with_data) in (ImmutableMultiDict,
                                       ImmutableOrderedMultiDict):
                    data, keys = http_form_to_dict(with_data)
                else:
                    data, keys = with_data, with_data.keys()

            return render_template('email/confirm.%s' % ext,
                                   email=self.email,
                                   host=self.host,
                                   nonce_link=link,
                                   data=data,
                                   keys=keys)

        log.debug('Sending email')

        result = send_email(to=self.email,
                            subject='Confirm email for %s' %
                            settings.SERVICE_NAME,
                            text=render_content('txt'),
                            html=render_content('html'),
                            sender=settings.DEFAULT_SENDER)

        log.debug('Sent')

        if not result[0]:
            return {'code': Form.STATUS_CONFIRMATION_FAILED}

        self.confirm_sent = True
        DB.session.add(self)
        DB.session.commit()

        return {'code': Form.STATUS_CONFIRMATION_SENT}
Example #17
0
    def send_confirmation(self, with_data=None):
        '''
        Helper that actually creates confirmation nonce
        and sends the email to associated email. Renders
        different templates depending on the result
        '''

        log.debug('Sending confirmation')
        if self.confirm_sent:
            return { 'code': Form.STATUS_CONFIRMATION_DUPLICATED }

        # the nonce for email confirmation will be the hash when it exists
        # (whenever the form was created from a simple submission) or
        # a concatenation of HASH(email, id) + ':' + hashid
        # (whenever the form was created from the dashboard)
        id = str(self.id)
        nonce = self.hash or '%s:%s' % (HASH(self.email, id), self.hashid)
        link = url_for('confirm_email', nonce=nonce, _external=True)

        def render_content(ext):
            data, keys = None, None
            if with_data:
                if type(with_data) in (ImmutableMultiDict, ImmutableOrderedMultiDict):
                    data, keys = http_form_to_dict(with_data)
                else:
                    data, keys = with_data, with_data.keys()

            return render_template('email/confirm.%s' % ext,
                                      email=self.email,
                                      host=self.host,
                                      nonce_link=link,
                                      data=data,
                                      keys=keys)

        log.debug('Sending email')

        result = send_email(to=self.email,
                            subject='Confirm email for %s' % settings.SERVICE_NAME,
                            text=render_content('txt'),
                            html=render_content('html'),
                            sender=settings.DEFAULT_SENDER)

        log.debug('Sent')

        if not result[0]:
            return { 'code': Form.STATUS_CONFIRMATION_FAILED }

        self.confirm_sent = True
        DB.session.add(self)
        DB.session.commit()

        return { 'code': Form.STATUS_CONFIRMATION_SENT }
Example #18
0
 def send_password_reset(self):
     digest = self.reset_password_digest()
     link = url_for('reset-password', digest=digest, email=self.email, _external=True)
     res = send_email(
         to=self.email,
         subject='Reset your %s password!' % settings.SERVICE_NAME,
         text=render_template('email/reset-password.txt', addr=self.email, link=link),
         html=render_template('email/reset-password.html', add=self.email, link=link),
         sender=settings.ACCOUNT_SENDER
     )
     if not res[0]:
         return False
     else:
         return True
Example #19
0
    def send_password_reset(self):
        g.log.info('Sending password reset.', account=self.email)

        digest = self.reset_password_digest()
        link = url_for('reset-password', digest=digest, email=self.email, _external=True)
        res = send_email(
            to=self.email,
            subject='Reset your %s password!' % settings.SERVICE_NAME,
            text=render_template('email/reset-password.txt', addr=self.email, link=link),
            html=render_template_string(TEMPLATES.get('reset-password.html'), add=self.email, link=link),
            sender=settings.ACCOUNT_SENDER
        )
        if not res[0]:
            g.log.info('Failed to send email.', reason=res[1], code=res[2])
            return False
        else:
            return True
Example #20
0
    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
Example #21
0
 def send_password_reset(self):
     digest = self.reset_password_digest()
     link = url_for('reset-password',
                    digest=digest,
                    email=self.email,
                    _external=True)
     res = send_email(to=self.email,
                      subject='Reset your %s password!' %
                      settings.SERVICE_NAME,
                      text=render_template('email/reset-password.txt',
                                           addr=self.email,
                                           link=link),
                      html=render_template('email/reset-password.html',
                                           add=self.email,
                                           link=link),
                      sender=settings.ACCOUNT_SENDER)
     if not res[0]:
         return False
     else:
         return True
Example #22
0
    def send_password_reset(self):
        g.log.info("Sending password reset.", account=self.email)

        digest = self.reset_password_digest()
        link = url_for(
            "reset-password", digest=digest, email=self.email, _external=True
        )
        res = send_email(
            to=self.email,
            subject="Reset your %s password!" % settings.SERVICE_NAME,
            text=render_template(
                "email/reset-password.txt", addr=self.email, link=link
            ),
            html=render_email("reset-password.html", add=self.email, link=link),
            sender=settings.ACCOUNT_SENDER,
        )
        if not res[0]:
            g.log.info("Failed to send email.", reason=res[1], code=res[2])
            return False
        else:
            return True
Example #23
0
    def send_confirmation(addr, user_id):
        g.log = g.log.new(address=addr, user_id=user_id)
        g.log.info("Sending email confirmation for new address on account.")

        addr = addr.lower().strip()
        if not is_valid_email(addr):
            g.log.info("Failed. Invalid address.")
            raise ValueError(
                "Cannot send confirmation. " "{} is not a valid email.".format(addr)
            )

        link = url_for(
            "verify-account-email",
            nonce=store_pending_email(addr, user_id),
            _external=True,
        )
        res = send_email(
            to=addr,
            subject="Action Required: Verify email linked to %s"
            % settings.SERVICE_NAME,
            text=render_template(
                "email/verify-email-in-account.txt",
                owner_email=current_user.email,
                email=addr,
                link=link,
            ),
            html=render_email(
                "verify-email-in-account.html",
                owner_email=current_user.email,
                email=addr,
                link=link,
            ),
            sender=settings.ACCOUNT_SENDER,
        )
        if not res[0]:
            g.log.info("Failed to send email.", reason=res[1], code=res[2])
            return False
        else:
            return True
Example #24
0
    def send_confirmation(addr, user_id):
        g.log = g.log.new(address=addr, user_id=user_id)
        g.log.info("Sending email confirmation for new address on account.")

        addr = addr.lower().strip()
        if not is_valid_email(addr):
            g.log.info("Failed. Invalid address.")
            raise ValueError("Cannot send confirmation. "
                             "{} is not a valid email.".format(addr))

        link = url_for(
            "verify-account-email",
            nonce=store_pending_email(addr, user_id),
            _external=True,
        )
        res = send_email(
            to=addr,
            subject="Action Required: Verify email linked to %s" %
            settings.SERVICE_NAME,
            text=render_template(
                "email/verify-email-in-account.txt",
                owner_email=current_user.email,
                email=addr,
                link=link,
            ),
            html=render_email(
                "verify-email-in-account.html",
                owner_email=current_user.email,
                email=addr,
                link=link,
            ),
            sender=settings.ACCOUNT_SENDER,
        )
        if not res[0]:
            g.log.info("Failed to send email.", reason=res[1], code=res[2])
            return False
        else:
            return True
Example #25
0
    def send_password_reset(self):
        g.log.info("Sending password reset.", account=self.email)

        digest = self.reset_password_digest()
        link = url_for("reset-password",
                       digest=digest,
                       email=self.email,
                       _external=True)
        res = send_email(
            to=self.email,
            subject="Reset your %s password!" % settings.SERVICE_NAME,
            text=render_template("email/reset-password.txt",
                                 addr=self.email,
                                 link=link),
            html=render_email("reset-password.html", add=self.email,
                              link=link),
            sender=settings.ACCOUNT_SENDER,
        )
        if not res[0]:
            g.log.info("Failed to send email.", reason=res[1], code=res[2])
            return False
        else:
            return True
Example #26
0
    def process(self, keys):
        """
        Processes a submission, including sending submission to target email.
        Assumes sender's email has been verified.

        NOTE: shouldn't call DB.commit() when processing a submission. All actions are
        committed after processing by process_and_commit_submission()
        """

        # url to request_unconfirm_form page
        unconfirm_url = url_for(
            "request_unconfirm_form", form_id=self.form.id, _external=True
        )
        spam_url = url_for("mark-spam", id=self.spam_hash, _external=True)

        if (
            not self.form.has_feature("archive")
            and random.random() < settings.EXPENSIVELY_WIPE_SUBMISSIONS_FREQUENCY
        ):
            self.form.delete_over_archive_limit()

        if self.check_over_submission_limit(unconfirm_url) is True:
            self.append_error("Over submission limit")
            return

        self.dispatch_plugins(keys)

        # build the list of recipients: for normal forms it will be just
        # form.email; for forms with routing rules we ignore that and use
        # the results from the routing rules instead.
        if (
            self.form.has_feature("submission_routing")
            and self.form.routing_rules.count()
        ):
            recipients = {
                (rule.email, rule.id)
                for rule in self.form.routing_rules
                if rule.matches(self)
            }
            g.log.info("Got recipients from route matching", n=len(recipients))
        else:
            # if emails are disabled, don't send email notification
            # (but routing rules are applied anyway)
            if self.form.has_feature("dashboard") and self.form.disable_email:
                g.log.info("Form has email disabled, will not send.")
                return
            else:
                recipients = {(self.form.email, None)}
                g.log.info("Will send to simple recipient", recipient=self.form.email)

        # prepare email properties -----------------------------------------------

        # set variables used for sending a submission

        reply_to = get_replyto(self.data)
        subject = self.data.get("_subject") or self.get_host_path()

        cc = self.data.get("_cc", None)
        format = self.data.get("_format", None)
        from_name = None

        # turn cc emails into array
        if cc:
            cc = [e.strip() for e in cc.split(",")]

        now = datetime.datetime.utcnow().strftime("%I:%M %p UTC - %d %B %Y")

        text = render_template(
            "email/form.txt",
            data=self.data,
            host=self.get_host_path(),
            keys=keys,
            now=now,
            unconfirm_url=unconfirm_url,
        )

        template_data = EmailTemplate.make_mustache_context(
            data=self.data,
            host=self.get_host_path(),
            keys=keys,
            now=now,
            unconfirm_url=unconfirm_url,
        )

        # if there's a custom email template we should use it
        # otherwise check if the user wants a new or old version of the email
        if (
            self.form.has_feature("whitelabel")
            and self.form.template
            and self.form.template.body
        ):
            html = self.form.template.render_body(template_data, unconfirm_url)
        elif format == "plain":
            html = render_template(
                "email/plain_form.html",
                data=self.data,
                host=self.get_host_path(),
                keys=keys,
                now=now,
                unconfirm_url=unconfirm_url,
            )
        else:
            html = render_email(
                "form.html",
                data=self.data,
                host=self.get_host_path(),
                keys=keys,
                now=now,
                unconfirm_url=unconfirm_url,
                submission_count=self.form.counter,
                upgraded=self.form.has_feature("dashboard"),
                spam_url=spam_url,
            )

        # override other email properties if there's a template
        if self.form.has_feature("whitelabel") and self.form.template:
            subject = (
                self.form.template.render_subject(template_data)
                if self.form.template.subject
                else subject
            )
            from_name = self.form.template.from_name or from_name

        # send the emails ----------------------------------------------------------

        for to, rule_id in recipients:
            g.log.info("Submitting.", target=to)
            result = send_email(
                to=to,
                subject=subject,
                text=text,
                html=html,
                sender=settings.DEFAULT_SENDER,
                from_name=from_name,
                reply_to=reply_to,
                cc=cc,
                headers={
                    "List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
                    "List-Unsubscribe": "<"
                    + url_for(
                        "unconfirm_form",
                        form_id=self.form.id,
                        digest=self.form.unconfirm_digest(),
                        _external=True,
                    )
                    + ">",
                },
            )

            if not result[0]:
                g.log.warning("Failed to send email.", reason=result[1], code=result[2])
                # self.errors.append("Couldn't send email. " + result[1])
                self.append_error(
                    "Could not send email",
                    debug_msg=f"code {result[2]}: {result[1]}",
                    rule=rule_id,
                )
Example #27
0
    def send_confirmation(self, store_data=None):
        '''
        Helper that actually creates confirmation nonce
        and sends the email to associated email. Renders
        different templates depending on the result
        '''

        g.log = g.log.new(form=self.id, to=self.email, host=self.host)
        g.log.debug('Confirmation.')
        if self.confirm_sent:
            g.log.debug('Previously sent.')
            return {'code': Form.STATUS_CONFIRMATION_DUPLICATED}

        # the nonce for email confirmation will be the hash when it exists
        # (whenever the form was created from a simple submission) or
        # a concatenation of HASH(email, id) + ':' + hashid
        # (whenever the form was created from the dashboard)
        id = str(self.id)
        nonce = self.hash or '%s:%s' % (HASH(self.email, id), self.hashid)
        link = url_for('confirm_email', nonce=nonce, _external=True)

        def render_content(ext):
            data, keys = None, None
            if store_data:
                if type(store_data) in (ImmutableMultiDict,
                                        ImmutableOrderedMultiDict):
                    data, _ = http_form_to_dict(store_data)
                    store_first_submission(nonce, data)
                else:
                    store_first_submission(nonce, store_data)

            return render_template('email/confirm.%s' % ext,
                                   email=self.email,
                                   host=self.host,
                                   nonce_link=link,
                                   keys=keys)

        DB.session.add(self)
        DB.session.flush()

        result = send_email(
            to=self.email,
            subject='Confirm email for {} on {}' \
                .format(settings.SERVICE_NAME, self.host),
            text=render_content('txt'),
            html=render_content('html'),
            sender=settings.DEFAULT_SENDER,
            headers={
                'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
                'List-Unsubscribe': '<' + url_for(
                    'unconfirm_form',
                    form_id=self.id,
                    digest=self.unconfirm_digest(),
                    _external=True
                ) + '>'
            }
        )
        g.log.debug('Confirmation email queued.')

        if not result[0]:
            return {'code': Form.STATUS_CONFIRMATION_FAILED}

        self.confirm_sent = True
        DB.session.add(self)
        DB.session.commit()

        return {'code': Form.STATUS_CONFIRMATION_SENT}
Example #28
0
    def send(self, data, keys, referrer):
        '''
        Sends form to user's email.
        Assumes sender's email has been verified.
        '''

        subject = data.get('_subject') or \
            'New submission from %s' % referrer_to_path(referrer)
        reply_to = (data.get('_replyto', data.get('email', data.get('Email')))
                    or '').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:
            g.log.info('Submission rejected.', gotcha=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):
            g.log.info('Submission rejected. Reply-To is invalid.',
                       reply_to=reply_to)
            return {
                'code': Form.STATUS_REPLYTO_ERROR,
                'address': reply_to,
                'referrer': referrer
            }

        # 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

        # if submission storage is disabled and form is upgraded, don't store submission
        if self.disable_storage and self.upgraded:
            pass
        else:
            DB.session.add(self)

            # archive the form contents
            sub = Submission(self.id)
            sub.data = {
                key: data[key]
                for key in data if key not in KEYS_NOT_STORED
            }
            DB.session.add(sub)

            # commit changes
            DB.session.commit()

        # sometimes we'll delete all archived submissions over the limit
        if random.random() < settings.EXPENSIVELY_WIPE_SUBMISSIONS_FREQUENCY:
            records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT
            total_records = DB.session.query(func.count(Submission.id)) \
                .filter_by(form_id=self.id) \
                .scalar()

            if total_records > records_to_keep:
                newest = self.submissions.with_entities(
                    Submission.id).limit(records_to_keep)
                DB.engine.execute(
                  delete(table('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 and not self.upgraded:
            overlimit = True

        if monthly_counter == int(settings.MONTHLY_SUBMISSIONS_LIMIT *
                                  0.9) and not self.upgraded:
            # send email notification
            send_email(to=self.email,
                       subject="[WARNING] Approaching submission limit",
                       text=render_template('email/90-percent-warning.txt'),
                       html=render_template('email/90-percent-warning.html'),
                       sender=settings.DEFAULT_SENDER)

        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:
                g.log.info('Submission rejected. Form over quota.',
                           monthly_counter=monthly_counter)
                # 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)

        # if emails are disabled and form is upgraded, don't send email notification
        if self.disable_email and self.upgraded:
            return {'code': Form.STATUS_NO_EMAIL, 'next': next}
        else:
            result = send_email(to=self.email,
                                subject=subject,
                                text=text,
                                html=html,
                                sender=settings.DEFAULT_SENDER,
                                reply_to=reply_to,
                                cc=cc,
                                headers={
                                    'List-Unsubscribe-Post':
                                    'List-Unsubscribe=One-Click',
                                    'List-Unsubscribe':
                                    '<' +
                                    url_for('unconfirm_form',
                                            form_id=self.id,
                                            digest=self.unconfirm_digest(),
                                            _external=True) + '>'
                                })

            if not result[0]:
                g.log.warning('Failed to send email.',
                              reason=result[1],
                              code=result[2])
                if result[1].startswith('Invalid replyto email address'):
                    return {
                        'code': Form.STATUS_REPLYTO_ERROR,
                        'address': reply_to,
                        'referrer': referrer
                    }

                return {
                    'code': Form.STATUS_EMAIL_FAILED,
                    'mailer-code': result[2],
                    'error-message': result[1]
                }

            return {'code': Form.STATUS_EMAIL_SENT, 'next': next}
Example #29
0
    def send(self, http_form, referrer):
        '''
        Sends form to user's email.
        Assumes sender's email has been verified.
        '''

        data, keys = http_form_to_dict(http_form)

        subject = data.get('_subject', 'New submission from %s' % referrer_to_path(referrer))
        reply_to = data.get('_replyto', data.get('email', data.get('Email', None)))
        cc = data.get('_cc', None)
        next = next_url(referrer, data.get('_next'))
        spam = data.get('_gotcha', None)

        # 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 }

        # 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)
            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_EMAIL_FAILED }

            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]:
            return{ 'code': Form.STATUS_EMAIL_FAILED }

        return { 'code': Form.STATUS_EMAIL_SENT, 'next': next }
Example #30
0
    def send_confirmation(self, store_data=None, sorted_keys=[]):
        """
        Helper that actually creates confirmation nonce
        and sends the email to associated email. Renders
        different templates depending on the result
        """

        g.log = g.log.new(form=self.id, to=self.email, host=self.host)
        g.log.debug("Confirmation.")
        if not self.host:
            g.log.error("Trying to send confirmation without host.")

        if self.confirm_sent:
            g.log.debug("Previously sent.")
            return {"code": Form.STATUS_CONFIRMATION_DUPLICATED}

        # the nonce for email confirmation will be the hash
        # (we only send confirmation emails for legacy forms created
        #  automatically now)
        nonce = self.hash
        link = url_for("confirm_email", nonce=nonce, _external=True)

        def render_content(ext):
            if store_data:
                store_first_submission(nonce, store_data, sorted_keys)

            params = dict(
                email=self.email, host=referrer_to_path(self.host), nonce_link=link
            )
            if ext == "html":
                return render_email("confirm.html", **params)
            elif ext == "txt":
                return render_template("email/confirm.txt", **params)

        DB.session.add(self)
        try:
            DB.session.flush()
        except IntegrityError:
            return {"code": Form.STATUS_CONFIRMATION_DUPLICATED}

        result = send_email(
            to=self.email,
            subject=self.SUBJECT_ACTIVATION % (settings.SERVICE_NAME, self.host),
            text=render_content("txt"),
            html=render_content("html"),
            sender=settings.DEFAULT_SENDER,
            headers={
                "List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
                "List-Unsubscribe": "<"
                + url_for(
                    "unconfirm_form",
                    form_id=self.id,
                    digest=self.unconfirm_digest(),
                    _external=True,
                )
                + ">",
            },
        )
        g.log.debug("Confirmation email queued.")

        if not result[0]:
            return {"code": Form.STATUS_CONFIRMATION_FAILED}

        self.confirm_sent = True
        DB.session.add(self)
        DB.session.commit()

        return {"code": Form.STATUS_CONFIRMATION_SENT}
Example #31
0
    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:
            g.log.info('Submission rejected.', gotcha=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):
            g.log.info('Submission rejected. Reply-To is invalid.', reply_to=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:
                g.log.info('Submission rejected. Form over quota.', monthly_counter=monthly_counter)
                # 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]:
            g.log.warning('Failed to send email.', reason=result[1], code=result[2])
            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 }
Example #32
0
    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 }
Example #33
0
    def send(self, http_form, referrer):
        '''
        Sends form to user's email.
        Assumes sender's email has been verified.
        '''

        data, keys = http_form_to_dict(http_form)

        subject = data.get('_subject', 'New submission from %s' % referrer_to_path(referrer))
        reply_to = data.get('_replyto', data.get('email', data.get('Email', None)))
        cc = data.get('_cc', None)
        next = next_url(referrer, data.get('_next'))
        spam = data.get('_gotcha', None)

        # 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 }

        # 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
        if self.get_monthly_counter(basedate=request_date) > settings.MONTHLY_SUBMISSIONS_LIMIT:
            if not self.owner or not self.owner.upgraded:
                overlimit = True

        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)
            html = render_template('email/form.html', data=data, host=self.host, keys=keys, now=now)
        else:
            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]:
            return{ 'code': Form.STATUS_EMAIL_FAILED }

        return { 'code': Form.STATUS_EMAIL_SENT, 'next': next }
Example #34
0
    def check_over_submission_limit(self, unconfirm_url):
        # check if the forms are over the counter and the user has unlimited submissions
        monthly_counter = self.form.update_counters()
        monthly_limit = (
            settings.MONTHLY_SUBMISSIONS_LIMIT
            if self.form.id > settings.FORM_LIMIT_DECREASE_ACTIVATION_SEQUENCE
            else settings.GRANDFATHER_MONTHLY_LIMIT
        )
        overlimit = monthly_counter > monthly_limit and not self.form.has_feature(
            "unlimited"
        )

        if overlimit:
            g.log.info("Form over limit.", monthly_counter=monthly_counter)

        # send overlimit or approaching limit emails

        if monthly_counter == int(monthly_limit * 0.9) and not self.form.has_feature(
            "unlimited"
        ):
            # send email notification
            send_email(
                to=self.form.email,
                subject=self.SUBJECT_APPROACHING_LIMIT,
                text=render_template(
                    "email/90-percent-warning.txt",
                    unconfirm_url=unconfirm_url,
                    limit=monthly_limit,
                ),
                html=render_email(
                    "90-percent-warning.html",
                    unconfirm_url=unconfirm_url,
                    limit=monthly_limit,
                ),
                sender=settings.DEFAULT_SENDER,
            )

        # send an overlimit notification for the first x overlimit emails
        # after that, return an error so the user can know the website owner is not
        # going to read his message.
        if (
            overlimit
            and monthly_counter
            <= monthly_limit + settings.OVERLIMIT_NOTIFICATION_QUANTITY
        ):
            send_email(
                to=self.form.email,
                subject=self.SUBJECT_OVER_LIMIT,
                text=render_template(
                    "email/overlimit-notification.txt",
                    host=self.get_host_path(),
                    unconfirm_url=unconfirm_url,
                    limit=monthly_limit,
                ),
                html=render_email(
                    "overlimit-notification.html",
                    host=self.get_host_path(),
                    unconfirm_url=unconfirm_url,
                    limit=monthly_limit,
                ),
                sender=settings.DEFAULT_SENDER,
            )

        return overlimit
Example #35
0
    def send(self, data, keys, referrer):
        '''
        Sends form to user's email.
        Assumes sender's email has been verified.
        '''

        subject = data.get('_subject') or \
            'New submission from %s' % referrer_to_path(referrer)
        reply_to = (data.get(
            '_replyto',
            data.get('email', data.get('Email'))
        ) or '').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:
            g.log.info('Submission rejected.', gotcha=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):
            g.log.info('Submission rejected. Reply-To is invalid.',
                       reply_to=reply_to)
            return {
                'code': Form.STATUS_REPLYTO_ERROR,
                'address': reply_to,
                'referrer': referrer
            }

        # 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

        # if submission storage is disabled and form is upgraded, don't store submission
        if self.disable_storage and self.upgraded:
            pass
        else:
            DB.session.add(self)

            # archive the form contents
            sub = Submission(self.id)
            sub.data = {key: data[key] for key in data if key not in KEYS_NOT_STORED}
            DB.session.add(sub)

            # commit changes
            DB.session.commit()

        # sometimes we'll delete all archived submissions over the limit
        if random.random() < settings.EXPENSIVELY_WIPE_SUBMISSIONS_FREQUENCY:
            records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT
            total_records = DB.session.query(func.count(Submission.id)) \
                .filter_by(form_id=self.id) \
                .scalar()

            if total_records > records_to_keep:
                newest = self.submissions.with_entities(Submission.id).limit(records_to_keep)
                DB.engine.execute(
                  delete(table('submissions')). \
                  where(Submission.form_id == self.id). \
                  where(~Submission.id.in_(newest))
                )

        # url to request_unconfirm_form page
        unconfirm = url_for('request_unconfirm_form', form_id=self.id, _external=True)

        # check if the forms are over the counter and the user is not upgraded
        overlimit = False
        monthly_counter = self.get_monthly_counter()
        monthly_limit = settings.MONTHLY_SUBMISSIONS_LIMIT \
                if self.id > settings.FORM_LIMIT_DECREASE_ACTIVATION_SEQUENCE \
                else settings.GRANDFATHER_MONTHLY_LIMIT

        if monthly_counter > monthly_limit and not self.upgraded:
            overlimit = True

        if monthly_counter == int(monthly_limit * 0.9) and not self.upgraded:
            # send email notification
            send_email(
                to=self.email,
                subject="Formspree Notice: Approaching submission limit.",
                text=render_template('email/90-percent-warning.txt',
                    unconfirm_url=unconfirm, limit=monthly_limit
                ),
                html=render_template_string(
                    TEMPLATES.get('90-percent-warning.html'),
                    unconfirm_url=unconfirm, limit=monthly_limit
                ),
                sender=settings.DEFAULT_SENDER
            )

        now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y')

        if not overlimit:
            g.log.info('Submitted.')
            text = render_template('email/form.txt',
                data=data, host=self.host, keys=keys, now=now,
                unconfirm_url=unconfirm)
            # 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,
                    unconfirm_url=unconfirm)
            else:
                html = render_template_string(TEMPLATES.get('form.html'),
                    data=data, host=self.host, keys=keys, now=now,
                    unconfirm_url=unconfirm)
        else:
            g.log.info('Submission rejected. Form over quota.',
                monthly_counter=monthly_counter)
            # send an overlimit notification for the first x overlimit emails
            # after that, return an error so the user can know the website owner is not
            # going to read his message.
            if monthly_counter <= monthly_limit + settings.OVERLIMIT_NOTIFICATION_QUANTITY:
                subject = 'Formspree Notice: Your submission limit has been reached.'
                text = render_template('email/overlimit-notification.txt',
                    host=self.host, unconfirm_url=unconfirm, limit=monthly_limit)
                html = render_template_string(TEMPLATES.get('overlimit-notification.html'),
                    host=self.host, unconfirm_url=unconfirm, limit=monthly_limit)
            else:
                return {'code': Form.STATUS_OVERLIMIT}

        # if emails are disabled and form is upgraded, don't send email notification
        if self.disable_email and self.upgraded:
            return {'code': Form.STATUS_NO_EMAIL, 'next': next}
        else:
            result = send_email(
                to=self.email,
                subject=subject,
                text=text,
                html=html,
                sender=settings.DEFAULT_SENDER,
                reply_to=reply_to,
                cc=cc,
                headers={
                    'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
                    'List-Unsubscribe': '<' + url_for(
                        'unconfirm_form',
                        form_id=self.id,
                        digest=self.unconfirm_digest(),
                        _external=True
                    ) + '>'
                }
            )

            if not result[0]:
                g.log.warning('Failed to send email.',
                              reason=result[1], code=result[2])
                if result[1].startswith('Invalid replyto email address'):
                    return {
                        'code': Form.STATUS_REPLYTO_ERROR,
                        'address': reply_to,
                        'referrer': referrer
                    }

                return {
                    'code': Form.STATUS_EMAIL_FAILED,
                    'mailer-code': result[2],
                    'error-message': result[1]
                }

            return {'code': Form.STATUS_EMAIL_SENT, 'next': next}
Example #36
0
    def send_confirmation(self, store_data=None):
        '''
        Helper that actually creates confirmation nonce
        and sends the email to associated email. Renders
        different templates depending on the result
        '''

        g.log = g.log.new(form=self.id, to=self.email, host=self.host)
        g.log.debug('Confirmation.')
        if self.confirm_sent:
            g.log.debug('Previously sent.')
            return {'code': Form.STATUS_CONFIRMATION_DUPLICATED}

        # the nonce for email confirmation will be the hash when it exists
        # (whenever the form was created from a simple submission) or
        # a concatenation of HASH(email, id) + ':' + hashid
        # (whenever the form was created from the dashboard)
        id = str(self.id)
        nonce = self.hash or '%s:%s' % (HASH(self.email, id), self.hashid)
        link = url_for('confirm_email', nonce=nonce, _external=True)

        def render_content(ext):
            data, keys = None, None
            if store_data:
                if type(store_data) in (
                        ImmutableMultiDict, ImmutableOrderedMultiDict):
                    data, _ = http_form_to_dict(store_data)
                    store_first_submission(nonce, data)
                else:
                    store_first_submission(nonce, store_data)

            params = dict(
                email=self.email,
                host=self.host,
                nonce_link=link,
                keys=keys
            )
            if ext == 'html':
                return render_template_string(TEMPLATES.get('confirm.html'), **params)
            elif ext == 'txt':
                return render_template('email/confirm.txt', **params)

        DB.session.add(self)
        DB.session.flush()

        result = send_email(
            to=self.email,
            subject='Confirm email for {} on {}' \
                .format(settings.SERVICE_NAME, self.host),
            text=render_content('txt'),
            html=render_content('html'),
            sender=settings.DEFAULT_SENDER,
            headers={
                'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
                'List-Unsubscribe': '<' + url_for(
                    'unconfirm_form',
                    form_id=self.id,
                    digest=self.unconfirm_digest(),
                    _external=True
                ) + '>'
            }
        )
        g.log.debug('Confirmation email queued.')

        if not result[0]:
            return {'code': Form.STATUS_CONFIRMATION_FAILED}

        self.confirm_sent = True
        DB.session.add(self)
        DB.session.commit()

        return {'code': Form.STATUS_CONFIRMATION_SENT}