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