Пример #1
0
def _get_response_content(response_id, diagnostic_info):
    """Gets the response for the given response_id. diagnostic_info will be
    used to determine language and some content, but may be None.

    Returns a dict of the form:
        {
            subject: <subject text>,
            body_text: <body text>,
            body_html: <rich html body>,
            attachments: <attachments list, may be None>
        }

    Returns None if no response content can be derived.
    """

    logger.debug_log('_get_response_content: enter')

    sponsor_name = utils.coalesce(diagnostic_info,
                                  ['DiagnosticInfo', 'SystemInformation', 'PsiphonInfo', 'SPONSOR_ID'],
                                  required_types=utils.string_types)

    prop_channel_name = utils.coalesce(diagnostic_info,
                                       ['DiagnosticInfo', 'SystemInformation', 'PsiphonInfo', 'PROPAGATION_CHANNEL_ID'],
                                       required_types=utils.string_types)

    # Use default values if we couldn't get good user-specific values
    sponsor_name = sponsor_name or config['defaultSponsorName']
    prop_channel_name = prop_channel_name or config['defaultPropagationChannelName']

    lang_id = _get_lang_id_from_diagnostic_info(diagnostic_info)
    # lang_id may be None, if the language could not be determined

    # Read in all translations HTML
    response_translations = []
    for root, _, files in os.walk(_RESPONSES_DIR):
        for name in files:
            lang, ext = os.path.splitext(name)
            if ext != '.html':
                continue

            if lang == 'master':
                lang = 'en'

            with open(os.path.join(root, name)) as translation_file:
                translation = translation_file.read().decode('utf-8')

            # Strip leading and trailing whitespace so that we don't get extra
            # text elements in our BeautifulSoup
            translation = translation.strip()

            response_translations.append((lang, translation.strip()))

    # Reorder the array according to the detected language and _TOP_LANGS
    def lang_sorter(item):
        lang, _ = item
        rank = 999
        try:
            if lang == lang_id:
                rank = -1
            else:
                rank = _TOP_LANGS.index(lang)
        except ValueError:
            pass
        return rank

    response_translations.sort(key=lang_sorter)

    # Gather the info we'll need for formatting the email
    bucketname, email_address = psi_ops_helpers.get_bucket_name_and_email_address(sponsor_name, prop_channel_name)

    # Use default values if we couldn't get good user-specific values
    if not bucketname or not email_address:
        default_bucketname, default_email_address = \
            psi_ops_helpers.get_bucket_name_and_email_address(config['defaultSponsorName'],
                                                              config['defaultPropagationChannelName'])
        bucketname = bucketname or default_bucketname
        email_address = email_address or default_email_address

    # If, despite our best efforts, we still don't have a bucketname and
    # email address, just bail.
    if not bucketname or not email_address:
        logger.debug_log('_get_response_content: exiting due to no bucketname or address')
        return None

    # Collect the translations of the specific response we're sending

    subject = None
    bodies = []
    for lang_id, html in response_translations:
        soup = BeautifulSoup(html)
        if not subject:
            subject = soup.find(id='default_response_subject')
            if subject:
                # Strip outer element
                subject = u''.join(unicode(elem) for elem in subject.contents).strip()
        body = soup.find(id=response_id)
        if body:
            # Strip outer element
            body = u''.join(unicode(elem) for elem in body.contents).strip()

            # The user might be using a language for which there isn't a
            # download page. Fall back to English if that's the case.
            home_page_url = psi_ops_helpers.get_s3_bucket_home_page_url(
                bucketname,
                lang_id if lang_id in psi_ops_helpers.WEBSITE_LANGS else 'en')
            download_page_url = psi_ops_helpers.get_s3_bucket_download_page_url(
                bucketname,
                lang_id if lang_id in psi_ops_helpers.WEBSITE_LANGS else 'en')
            faq_page_url = psi_ops_helpers.get_s3_bucket_faq_url(
                bucketname,
                lang_id if lang_id in psi_ops_helpers.WEBSITE_LANGS else 'en')

            # We're using numbers rather than more readable names here because
            # they're less likely to be accidentally modified by translators
            # (we think).
            format_dict = {
                '0': email_address,
                '1': download_page_url,
                '2': home_page_url,
                '3': faq_page_url
            }
            body = unicode(body) % format_dict
            bodies.append(body)

    # Render the email body from the Mako template
    body_html = _render_email({
        'lang_id': lang_id,
        'response_id': response_id,
        'responses': bodies
    })

    # Get attachments.
    # This depends on which response we're returning.
    attachments = None
    if response_id == 'download_new_version_links':
        pass
    elif response_id == 'download_new_version_attachments':
        fp_windows = aws_helpers.get_s3_attachment('attachments',
                                                   bucketname,
                                                   psi_ops_helpers.DOWNLOAD_SITE_WINDOWS_BUILD_FILENAME)
        fp_android = aws_helpers.get_s3_attachment('attachments',
                                                   bucketname,
                                                   psi_ops_helpers.DOWNLOAD_SITE_ANDROID_BUILD_FILENAME)
        attachments = [(fp_windows, psi_ops_helpers.EMAIL_RESPONDER_WINDOWS_ATTACHMENT_FILENAME),
                       (fp_android, psi_ops_helpers.EMAIL_RESPONDER_ANDROID_ATTACHMENT_FILENAME)]
    else:
        pass

    logger.debug_log('_get_response_content: exit')

    return {
        'subject': subject,
        'body_text': _html_to_text(body_html),
        'body_html': body_html,
        'attachments': attachments
    }
Пример #2
0
    def process_email(self, email_string):
        '''
        Processes the given email and sends a response.
        Returns True if successful, False or exception otherwise.
        '''

        self._email_string = email_string

        if not self._parse_email(email_string):
            return False

        # Look up all config entries matching the requested address.
        request_conf = [item for item in self._conf if item['email_addr'] == self.requested_addr]

        # If we didn't find anything for that address, exit.
        if not request_conf:
            logger.info('fail: invalid requested address: %s', self.requested_addr)
            return False

        # Check if the user is (or should be) blacklisted
        if not self._check_blacklist():
            logger.info('fail: blacklist')
            return False

        # Process each config entry found the for the requested address separately.
        # Don't fail out early, since the other email send method has a chance
        # to succeed even if one fails. (I.e., SMTP will succeed even if there's
        # a SES service problem.)
        full_success = True
        exception_to_raise = None
        for conf in request_conf:
            attachments = None
            if conf['attachments']:
                attachments = []
                for attachment_info in conf['attachments']:
                    bucketname, bucket_filename, attachment_filename = attachment_info
                    attachments.append((aws_helpers.get_s3_attachment(settings.ATTACHMENT_CACHE_DIR,
                                                                      bucketname,
                                                                      bucket_filename),
                                        attachment_filename))

            extra_headers = {'Reply-To': self.requested_addr}

            if self._requester_msgid:
                extra_headers['In-Reply-To'] = self._requester_msgid
                extra_headers['References'] = self._requester_msgid

            raw_response = sendmail.create_raw_email(self._requester_addr,
                                                     self._response_from_addr,
                                                     self._subject,
                                                     conf['body'],
                                                     attachments,
                                                     extra_headers)

            if not raw_response:
                full_success = False
                continue

            if conf.get('send_method', '').upper() == 'SES':
                # If sending via SES, we'll use its DKIM facility -- so don't do it here.
                try:
                    if not sendmail.send_raw_email_amazonses(raw_response,
                                                             self._response_from_addr):
                        return False
                except BotoServerError as ex:
                    if ex.error_message == 'Address blacklisted.':
                        logger.critical('fail: requester address blacklisted by SES')
                    else:
                        exception_to_raise = ex

                    full_success = False
                    continue
            else:
                raw_response = _dkim_sign_email(raw_response)

                if not sendmail.send_raw_email_smtp(raw_response,
                                                    settings.COMPLAINTS_ADDRESS,  # will be Return-Path
                                                    self._requester_addr):
                    full_success = False
                    continue

        if exception_to_raise:
            raise exception_to_raise
        return full_success
Пример #3
0
    def process_email(self, email_string):
        '''
        Processes the given email and sends a response.
        Returns True if successful, False or exception otherwise.
        '''

        self._email_string = email_string

        if not self._parse_email(email_string):
            return False

        # Look up all config entries matching the requested address.
        request_conf = [
            item for item in self._conf
            if item['email_addr'] == self.requested_addr
        ]

        # If we didn't find anything for that address, exit.
        if not request_conf:
            logger.info('fail: invalid requested address: %s',
                        self.requested_addr)
            return False

        # Check if the user is (or should be) blacklisted
        if not self._check_blacklist():
            logger.info('fail: blacklist')
            return False

        # Process each config entry found the for the requested address separately.
        # Don't fail out early, since the other email send method has a chance
        # to succeed even if one fails. (I.e., SMTP will succeed even if there's
        # a SES service problem.)
        full_success = True
        exception_to_raise = None
        for conf in request_conf:
            attachments = None
            if conf['attachments']:
                attachments = []
                for attachment_info in conf['attachments']:
                    bucketname, bucket_filename, attachment_filename = attachment_info
                    attachments.append((aws_helpers.get_s3_attachment(
                        settings.ATTACHMENT_CACHE_DIR, bucketname,
                        bucket_filename), attachment_filename))

            extra_headers = {
                'Reply-To': self.requested_addr,
                'Auto-Submitted': 'auto-replied'
            }

            if self._requester_msgid:
                extra_headers['In-Reply-To'] = self._requester_msgid
                extra_headers['References'] = self._requester_msgid

            raw_response = sendmail.create_raw_email(self._requester_addr,
                                                     self._response_from_addr,
                                                     self._subject,
                                                     conf['body'], attachments,
                                                     extra_headers)

            if not raw_response:
                full_success = False
                continue

            if conf.get('send_method', '').upper() == 'SES':
                # If sending via SES, we'll use its DKIM facility -- so don't do it here.
                try:
                    if not sendmail.send_raw_email_amazonses(
                            raw_response, self._response_from_addr):
                        return False
                except BotoServerError as ex:
                    if ex.error_message == 'Address blacklisted.':
                        logger.critical(
                            'fail: requester address blacklisted by SES')
                    else:
                        exception_to_raise = ex

                    full_success = False
                    continue
            else:
                raw_response = _dkim_sign_email(raw_response)

                if not sendmail.send_raw_email_smtp(
                        raw_response,
                        settings.COMPLAINTS_ADDRESS,  # will be Return-Path
                        self._requester_addr):
                    full_success = False
                    continue

        if exception_to_raise:
            raise exception_to_raise
        return full_success
Пример #4
0
def _get_response_content(response_id, diagnostic_info):
    """Gets the response for the given response_id. diagnostic_info will be
    used to determine language and some content, but may be None.

    Returns a dict of the form:
        {
            subject: <subject text>,
            body_text: <body text>,
            body_html: <rich html body>,
            attachments: <attachments list, may be None>
        }

    Returns None if no response content can be derived.
    """

    logger.debug_log('_get_response_content: enter')

    sponsor_name = utils.coalesce(diagnostic_info,
                                  ['DiagnosticInfo', 'SystemInformation', 'PsiphonInfo', 'SPONSOR_ID'],
                                  required_types=utils.string_types)

    prop_channel_name = utils.coalesce(diagnostic_info,
                                       ['DiagnosticInfo', 'SystemInformation', 'PsiphonInfo', 'PROPAGATION_CHANNEL_ID'],
                                       required_types=utils.string_types)

    # Use default values if we couldn't get good user-specific values
    sponsor_name = sponsor_name or config['defaultSponsorName']
    prop_channel_name = prop_channel_name or config['defaultPropagationChannelName']

    lang_id = _get_lang_id_from_diagnostic_info(diagnostic_info)
    # lang_id may be None, if the language could not be determined

    # Read in all translations HTML
    response_translations = []
    for root, _, files in os.walk(_RESPONSES_DIR):
        for name in files:
            lang, ext = os.path.splitext(name)
            if ext != '.html':
                continue

            if lang == 'master':
                lang = 'en'

            with open(os.path.join(root, name)) as translation_file:
                translation = translation_file.read().decode('utf-8')

            # Strip leading and trailing whitespace so that we don't get extra
            # text elements in our BeautifulSoup
            translation = translation.strip()

            response_translations.append((lang, translation.strip()))

    # Reorder the array according to the detected language and _TOP_LANGS
    def lang_sorter(item):
        lang, _ = item
        rank = 999
        try:
            if lang == lang_id:
                rank = -1
            else:
                rank = _TOP_LANGS.index(lang)
        except ValueError:
            pass
        return rank

    response_translations.sort(key=lang_sorter)

    # Gather the info we'll need for formatting the email
    bucketname, email_address = psi_ops_helpers.get_bucket_name_and_email_address(sponsor_name, prop_channel_name)

    # Use default values if we couldn't get good user-specific values
    if not bucketname or not email_address:
        default_bucketname, default_email_address = \
            psi_ops_helpers.get_bucket_name_and_email_address(config['defaultSponsorName'],
                                                              config['defaultPropagationChannelName'])
        bucketname = bucketname or default_bucketname
        email_address = email_address or default_email_address

    # If, despite our best efforts, we still don't have a bucketname and
    # email address, just bail.
    if not bucketname or not email_address:
        logger.debug_log('_get_response_content: exiting due to no bucketname or address')
        return None

    # Collect the translations of the specific response we're sending

    subject = None
    bodies = []
    for lang_id, html in response_translations:
        soup = BeautifulSoup(html)
        if not subject:
            subject = soup.find(id='default_response_subject')
            if subject:
                # Strip outer element
                subject = u''.join(unicode(elem) for elem in subject.contents).strip()
        body = soup.find(id=response_id)
        if body:
            # Strip outer element
            body = u''.join(unicode(elem) for elem in body.contents).strip()

            # The user might be using a language for which there isn't a
            # download page. Fall back to English if that's the case.
            home_page_url = psi_ops_helpers.get_s3_bucket_home_page_url(
                bucketname,
                lang_id if lang_id in psi_ops_helpers.WEBSITE_LANGS else 'en')
            download_page_url = psi_ops_helpers.get_s3_bucket_download_page_url(
                bucketname,
                lang_id if lang_id in psi_ops_helpers.WEBSITE_LANGS else 'en')

            format_dict = {
                '0': email_address,
                '1': download_page_url,
                '2': home_page_url
            }
            body = unicode(body) % format_dict
            bodies.append(body)

    # Render the email body from the Mako template
    body_html = _render_email({
        'lang_id': lang_id,
        'response_id': response_id,
        'responses': bodies
    })

    # Get attachments.
    # This depends on which response we're returning.
    attachments = None
    if response_id == 'download_new_version_links':
        pass
    elif response_id == 'download_new_version_attachments':
        fp_windows = aws_helpers.get_s3_attachment('attachments',
                                                   bucketname,
                                                   psi_ops_helpers.DOWNLOAD_SITE_WINDOWS_BUILD_FILENAME)
        fp_android = aws_helpers.get_s3_attachment('attachments',
                                                   bucketname,
                                                   psi_ops_helpers.DOWNLOAD_SITE_ANDROID_BUILD_FILENAME)
        attachments = [(fp_windows, psi_ops_helpers.EMAIL_RESPONDER_WINDOWS_ATTACHMENT_FILENAME),
                       (fp_android, psi_ops_helpers.EMAIL_RESPONDER_ANDROID_ATTACHMENT_FILENAME)]
    else:
        pass

    logger.debug_log('_get_response_content: exit')

    return {
        'subject': subject,
        'body_text': _html_to_text(body_html),
        'body_html': body_html,
        'attachments': attachments
    }