def test_govuk_banner(show_banner): email = HTMLEmailTemplate({'content': 'hello world', 'subject': ''}) email.govuk_banner = show_banner if show_banner: assert "GOV.UK" in str(email) else: assert "GOV.UK" not in str(email)
def test_complete_html(complete_html, branding_should_be_present, brand_logo, brand_name, brand_colour, content): email = str( HTMLEmailTemplate( { 'content': 'hello world', 'subject': '' }, complete_html=complete_html, brand_logo=brand_logo, brand_name=brand_name, brand_colour=brand_colour, )) if complete_html: assert content in email else: assert content not in email if branding_should_be_present: assert brand_logo in email assert brand_name in email if brand_colour: assert brand_colour in email assert '##' not in email
def test_URLs_get_escaped(url, expected_html, expected_html_in_template): assert notify_email_markdown(url) == ( '<p style="Margin: 0 0 20px 0; font-size: 19px; line-height: 25px; color: #0B0C0C;">' '{}' '</p>' ).format(expected_html) assert expected_html_in_template in str(HTMLEmailTemplate({'content': url, 'subject': ''}))
def test_urls_get_escaped(url, expected_html, expected_html_in_template): assert notify_email_markdown(url) == (PARAGRAPH_TEXT).format(expected_html) assert expected_html_in_template in str( HTMLEmailTemplate({ 'content': url, 'subject': '' }))
def send_email_to_provider(notification): service = notification.service if not service.active: technical_failure(notification=notification) return if notification.status == 'created': provider = provider_to_use(EMAIL_TYPE, notification.id) current_app.logger.debug( "Starting sending EMAIL {} to provider at {}".format(notification.id, datetime.utcnow()) ) template_dict = dao_get_template_by_id(notification.template_id, notification.template_version).__dict__ html_email = HTMLEmailTemplate( template_dict, values=notification.personalisation, **get_html_email_options(service) ) plain_text_email = PlainTextEmailTemplate( template_dict, values=notification.personalisation ) if service.research_mode or notification.key_type == KEY_TYPE_TEST: reference = str(create_uuid()) notification.billable_units = 0 notification.reference = reference update_notification(notification, provider) send_email_response(reference, notification.to) else: from_address = '"{}" <{}@{}>'.format(service.name, service.email_from, current_app.config['NOTIFY_EMAIL_DOMAIN']) email_reply_to = notification.reply_to_text reference, status = provider.send_email( from_address, validate_and_format_email_address(notification.to), plain_text_email.subject, body=str(plain_text_email), html_body=str(html_email), reply_to_address=validate_and_format_email_address(email_reply_to) if email_reply_to else None, ) notification.reference = reference update_notification(notification, provider, status=status) current_app.logger.debug("SENT_MAIL: {} -- {}".format( validate_and_format_email_address(notification.to), str(plain_text_email)) ) current_app.logger.debug( "Email {} sent to provider at {}".format(notification.id, notification.sent_at) ) delta_milliseconds = (datetime.utcnow() - notification.created_at).total_seconds() * 1000 statsd_client.timing("email.total-time", delta_milliseconds)
def send_email_to_provider(notification): service = notification.service if not service.active: technical_failure(notification=notification) return if notification.status == 'created': provider = provider_to_use(EMAIL_TYPE) template_dict = dao_get_template_by_id( notification.template_id, notification.template_version).__dict__ html_email = HTMLEmailTemplate(template_dict, values=notification.personalisation, **get_html_email_options(service)) plain_text_email = PlainTextEmailTemplate( template_dict, values=notification.personalisation) if service.research_mode or notification.key_type == KEY_TYPE_TEST: notification.reference = str(create_uuid()) update_notification_to_sending(notification, provider) send_email_response(notification.reference, notification.to) else: from_address = '"{}" <{}@{}>'.format( service.name, service.email_from, current_app.config['NOTIFY_EMAIL_DOMAIN']) email_reply_to = notification.reply_to_text reference = provider.send_email( from_address, validate_and_format_email_address(notification.to), plain_text_email.subject, body=str(plain_text_email), html_body=str(html_email), reply_to_address=validate_and_format_email_address( email_reply_to) if email_reply_to else None, ) notification.reference = reference update_notification_to_sending(notification, provider) delta_seconds = (datetime.utcnow() - notification.created_at).total_seconds() if notification.key_type == KEY_TYPE_TEST: statsd_client.timing("email.test-key.total-time", delta_seconds) else: statsd_client.timing("email.live-key.total-time", delta_seconds) if str(service.id) in current_app.config.get( 'HIGH_VOLUME_SERVICE'): statsd_client.timing("email.live-key.high-volume.total-time", delta_seconds) else: statsd_client.timing( "email.live-key.not-high-volume.total-time", delta_seconds)
def test_escaping_govuk_in_email_templates(): template_content = "GOV.UK" expected = "GOV.\u200BUK" assert unlink_govuk_escaped(template_content) == expected template_json = { 'content': template_content, 'subject': '', 'template_type': 'email' } assert expected in str(PlainTextEmailTemplate(template_json)) assert expected in str(HTMLEmailTemplate(template_json))
def test_HTML_template_has_URLs_replaced_with_links(): assert ( '<a style="word-wrap: break-word;" href="https://service.example.com/accept_invite/a1b2c3d4">' 'https://service.example.com/accept_invite/a1b2c3d4' '</a>' ) in str(HTMLEmailTemplate({'content': ( 'You’ve been invited to a service. Click this link:\n' 'https://service.example.com/accept_invite/a1b2c3d4\n' '\n' 'Thanks\n' ), 'subject': ''}))
def test_brand_banner_shows(): email = str(HTMLEmailTemplate( {'content': 'hello world', 'subject': ''}, brand_banner=True, govuk_banner=False )) assert ( '<td width="10" height="10" valign="middle"></td>' ) not in email assert ( 'role="presentation" width="100%" style="min-width: 100%;width: 100% !important;"' ) in email
def test_escaping_govuk_in_email_templates(template_content, expected): assert unlink_govuk_escaped(template_content) == expected assert expected in str( PlainTextEmailTemplate({ 'content': template_content, 'subject': '' })) assert expected in str( HTMLEmailTemplate({ 'content': template_content, 'subject': '' }))
def test_brand_data_shows(brand_logo, brand_name, brand_colour): email = str(HTMLEmailTemplate( {'content': 'hello world', 'subject': ''}, brand_banner=True, govuk_banner=False, brand_logo=brand_logo, brand_name=brand_name, brand_colour=brand_colour )) assert 'GOV.UK' not in email if brand_logo: assert brand_logo in email if brand_name: assert brand_name in email if brand_colour: assert 'bgcolor="{}"'.format(brand_colour) in email
def email_template(): return str( HTMLEmailTemplate( { 'subject': 'foo', 'content': ('Lorem Ipsum is simply dummy text of the printing and typesetting ' 'industry.\n\nLorem Ipsum has been the industry’s standard dummy ' 'text ever since the 1500s, when an unknown printer took a galley ' 'of type and scrambled it to make a type specimen book. ' '\n\n' '# History' '\n\n' 'It has ' 'survived not only' '\n\n' '* five centuries' '\n' '* but also the leap into electronic typesetting' '\n\n' 'It was ' 'popularised in the 1960s with the release of Letraset sheets ' 'containing Lorem Ipsum passages, and more recently with desktop ' 'publishing software like Aldus PageMaker including versions of ' 'Lorem Ipsum.' '\n\n' '^ It is a long established fact that a reader will be distracted ' 'by the readable content of a page when looking at its layout.' '\n\n' 'The point of using Lorem Ipsum is that it has a more-or-less ' 'normal distribution of letters, as opposed to using ‘Content ' 'here, content here’, making it look like readable English.' '\n\n\n' '1. One' '\n' '2. Two' '\n' '10. Three' '\n\n' 'This is an example of an email sent using GOV.UK Notify.' '\n\n' 'https://www.notifications.service.gov.uk') }, govuk_banner=convert_to_boolean( request.args.get('govuk_banner', True))))
def send_email_to_provider(notification): service = notification.service if not service.active: technical_failure(notification=notification) return # TODO: no else - replace with if statement raising error / logging when not 'created' if notification.status == 'created': provider = provider_to_use(notification) # TODO: remove that code or extract attachment handling to separate method # Extract any file objects from the personalization file_keys = [ k for k, v in (notification.personalisation or {}).items() if isinstance(v, dict) and 'document' in v ] attachments = [] personalisation_data = notification.personalisation.copy() for key in file_keys: # Check if a MLWR sid exists if (current_app.config["MLWR_HOST"] and 'mlwr_sid' in personalisation_data[key]['document'] and personalisation_data[key]['document']['mlwr_sid'] != "false"): mlwr_result = check_mlwr( personalisation_data[key]['document']['mlwr_sid']) if "state" in mlwr_result and mlwr_result[ "state"] == "completed": # Update notification that it contains malware if "submission" in mlwr_result and mlwr_result[ "submission"]['max_score'] >= 500: malware_failure(notification=notification) return else: # Throw error so celery will retry in sixty seconds raise MalwarePendingException try: response = requests.get( personalisation_data[key]['document']['direct_file_url']) if response.headers['Content-Type'] == 'application/pdf': attachments.append({ "name": "{}.pdf".format(key), "data": response.content }) except Exception: current_app.logger.error( "Could not download and attach {}".format( personalisation_data[key]['document'] ['direct_file_url'])) personalisation_data[key] = personalisation_data[key]['document'][ 'url'] template_dict = dao_get_template_by_id( notification.template_id, notification.template_version).__dict__ html_email = HTMLEmailTemplate(template_dict, values=personalisation_data, **get_html_email_options( notification, provider)) plain_text_email = PlainTextEmailTemplate(template_dict, values=personalisation_data) if current_app.config["SCAN_FOR_PII"]: contains_pii(notification, str(plain_text_email)) if service.research_mode or notification.key_type == KEY_TYPE_TEST: notification.reference = str(create_uuid()) update_notification_to_sending(notification, provider) send_email_response(notification.reference, notification.to) else: email_reply_to = notification.reply_to_text reference = provider.send_email( source=compute_source_email_address(service, provider), to_addresses=validate_and_format_email_address( notification.to), subject=plain_text_email.subject, body=str(plain_text_email), html_body=str(html_email), reply_to_address=validate_and_format_email_address( email_reply_to) if email_reply_to else None, attachments=attachments) notification.reference = reference update_notification_to_sending(notification, provider) current_app.logger.info( f"Saved provider reference: {reference} for notification id: {notification.id}" ) delta_milliseconds = (datetime.utcnow() - notification.created_at).total_seconds() * 1000 statsd_client.timing("email.total-time", delta_milliseconds)
def email_template(): branding_type = "fip_english" branding_style = request.args.get("branding_style", None) if ( branding_style == FieldWithLanguageOptions.ENGLISH_OPTION_VALUE or branding_style == FieldWithLanguageOptions.FRENCH_OPTION_VALUE ): if branding_style == FieldWithLanguageOptions.FRENCH_OPTION_VALUE: branding_type = "fip_french" branding_style = None if branding_style is not None: email_branding = email_branding_client.get_email_branding(branding_style)["email_branding"] branding_type = email_branding["brand_type"] if branding_type == "fip_english": brand_text = None brand_colour = None brand_logo = None fip_banner_english = True fip_banner_french = False logo_with_background_colour = False brand_name = None elif branding_type == "fip_french": brand_text = None brand_colour = None brand_logo = None fip_banner_english = False fip_banner_french = True logo_with_background_colour = False brand_name = None else: colour = email_branding["colour"] brand_text = email_branding["text"] brand_colour = colour brand_logo = "https://{}/{}".format(get_logo_cdn_domain(), email_branding["logo"]) if email_branding["logo"] else None fip_banner_english = branding_type in ["fip_english", "both_english"] fip_banner_french = branding_type in ["fip_french", "both_french"] logo_with_background_colour = branding_type == "custom_logo_with_background_colour" brand_name = email_branding["name"] template = { "subject": "foo", "content": ( "Lorem Ipsum is simply dummy text of the printing and typesetting " "industry.\n\nLorem Ipsum has been the industry’s standard dummy " "text ever since the 1500s, when an unknown printer took a galley " "of type and scrambled it to make a type specimen book. " "\n\n" "# History" "\n\n" "It has " "survived not only" "\n\n" "* five centuries" "\n" "* but also the leap into electronic typesetting" "\n\n" "It was " "popularised in the 1960s with the release of Letraset sheets " "containing Lorem Ipsum passages, and more recently with desktop " "publishing software like Aldus PageMaker including versions of " "Lorem Ipsum." "\n\n" "^ It is a long established fact that a reader will be distracted " "by the readable content of a page when looking at its layout." "\n\n" "The point of using Lorem Ipsum is that it has a more-or-less " "normal distribution of letters, as opposed to using ‘Content " "here, content here’, making it look like readable English." "\n\n\n" "1. One" "\n" "2. Two" "\n" "10. Three" "\n\n" "This is an example of an email sent using Notification." "\n\n" "https://www.notifications.service.gov.uk" ), } if not bool(request.args): resp = make_response(str(HTMLEmailTemplate(template))) else: resp = make_response( str( HTMLEmailTemplate( template, fip_banner_english=fip_banner_english, fip_banner_french=fip_banner_french, brand_text=brand_text, brand_colour=brand_colour, brand_logo=brand_logo, logo_with_background_colour=logo_with_background_colour, brand_name=brand_name, ) ) ) resp.headers["X-Frame-Options"] = "SAMEORIGIN" return resp
def test_subject_is_cleaned_up(subject, expected): assert expected == HTMLEmailTemplate({'content': '', 'subject': subject}).subject
def email_template(): branding_type = 'govuk' branding_style = request.args.get('branding_style', None) if branding_style == FieldWithNoneOption.NONE_OPTION_VALUE: branding_style = None if branding_style is not None: email_branding = email_branding_client.get_email_branding( branding_style)['email_branding'] branding_type = email_branding['brand_type'] if branding_type == 'govuk': brand_text = None brand_colour = None brand_logo = None govuk_banner = True brand_banner = False brand_name = None else: colour = email_branding['colour'] brand_text = email_branding['text'] brand_colour = colour brand_logo = ('https://{}/{}'.format(get_logo_cdn_domain(), email_branding['logo']) if email_branding['logo'] else None) govuk_banner = branding_type in ['govuk', 'both'] brand_banner = branding_type == 'org_banner' brand_name = email_branding['name'] template = { 'template_type': 'email', 'subject': 'foo', 'content': ('Lorem Ipsum is simply dummy text of the printing and typesetting ' 'industry.\n\nLorem Ipsum has been the industry’s standard dummy ' 'text ever since the 1500s, when an unknown printer took a galley ' 'of type and scrambled it to make a type specimen book. ' '\n\n' '# History' '\n\n' 'It has ' 'survived not only' '\n\n' '* five centuries' '\n' '* but also the leap into electronic typesetting' '\n\n' 'It was ' 'popularised in the 1960s with the release of Letraset sheets ' 'containing Lorem Ipsum passages, and more recently with desktop ' 'publishing software like Aldus PageMaker including versions of ' 'Lorem Ipsum.' '\n\n' '^ It is a long established fact that a reader will be distracted ' 'by the readable content of a page when looking at its layout.' '\n\n' 'The point of using Lorem Ipsum is that it has a more-or-less ' 'normal distribution of letters, as opposed to using ‘Content ' 'here, content here’, making it look like readable English.' '\n\n\n' '1. One' '\n' '2. Two' '\n' '10. Three' '\n\n' 'This is an example of an email sent using GOV.UK Notify.' '\n\n' 'https://www.notifications.service.gov.uk') } if not bool(request.args): resp = make_response(str(HTMLEmailTemplate(template))) else: resp = make_response( str( HTMLEmailTemplate( template, govuk_banner=govuk_banner, brand_text=brand_text, brand_colour=brand_colour, brand_logo=brand_logo, brand_banner=brand_banner, brand_name=brand_name, ))) resp.headers['X-Frame-Options'] = 'SAMEORIGIN' return resp
def send_email_to_provider(notification): service = notification.service if not service.active: technical_failure(notification=notification) return if notification.status == 'created': provider = provider_to_use(EMAIL_TYPE, notification.id) # Extract any file objects from the personalization file_keys = [ k for k, v in (notification.personalisation or {}).items() if isinstance(v, dict) and 'document' in v ] attachments = [] personalisation_data = notification.personalisation.copy() for key in file_keys: # Check if a MLWR sid exists if (current_app.config["MLWR_HOST"] and 'mlwr_sid' in personalisation_data[key]['document'] and personalisation_data[key]['document']['mlwr_sid'] != "false"): mlwr_result = check_mlwr(personalisation_data[key]['document']['mlwr_sid']) if "state" in mlwr_result and mlwr_result["state"] == "completed": # Update notification that it contains malware if "submission" in mlwr_result and mlwr_result["submission"]['max_score'] >= 500: malware_failure(notification=notification) return else: # Throw error so celery will retry in sixty seconds raise MalwarePendingException try: req = urllib.request.Request(personalisation_data[key]['document']['direct_file_url']) with urllib.request.urlopen(req) as response: buffer = response.read() mime_type = magic.from_buffer(buffer, mime=True) if mime_type == 'application/pdf': attachments.append({"name": "{}.pdf".format(key), "data": buffer}) except Exception: current_app.logger.error( "Could not download and attach {}".format(personalisation_data[key]['document']['direct_file_url']) ) personalisation_data[key] = personalisation_data[key]['document']['url'] template_dict = dao_get_template_by_id(notification.template_id, notification.template_version).__dict__ html_email = HTMLEmailTemplate( template_dict, values=personalisation_data, **get_html_email_options(service) ) plain_text_email = PlainTextEmailTemplate( template_dict, values=personalisation_data ) if current_app.config["SCAN_FOR_PII"]: contains_pii(notification, str(plain_text_email)) if service.research_mode or notification.key_type == KEY_TYPE_TEST: notification.reference = str(create_uuid()) update_notification_to_sending(notification, provider) send_email_response(notification.reference, notification.to) else: if service.sending_domain is None or service.sending_domain.strip() == "": sending_domain = current_app.config['NOTIFY_EMAIL_DOMAIN'] else: sending_domain = service.sending_domain from_address = '"{}" <{}@{}>'.format(service.name, service.email_from, sending_domain) email_reply_to = notification.reply_to_text reference = provider.send_email( from_address, validate_and_format_email_address(notification.to), plain_text_email.subject, body=str(plain_text_email), html_body=str(html_email), reply_to_address=validate_and_format_email_address(email_reply_to) if email_reply_to else None, attachments=attachments ) notification.reference = reference update_notification_to_sending(notification, provider) delta_milliseconds = (datetime.utcnow() - notification.created_at).total_seconds() * 1000 statsd_client.timing("email.total-time", delta_milliseconds)
def send_email_to_provider(notification): service = notification.service if not service.active: technical_failure(notification=notification) return if notification.status == "created": provider = provider_to_use(EMAIL_TYPE, notification.id) # Extract any file objects from the personalization file_keys = [ k for k, v in (notification.personalisation or {}).items() if isinstance(v, dict) and "document" in v ] attachments = [] personalisation_data = notification.personalisation.copy() for key in file_keys: sending_method = personalisation_data[key]["document"].get( "sending_method") # Check if a MLWR sid exists if (current_app.config["MLWR_HOST"] and "mlwr_sid" in personalisation_data[key]["document"] and personalisation_data[key]["document"]["mlwr_sid"] != "false"): mlwr_result = check_mlwr( personalisation_data[key]["document"]["mlwr_sid"]) if "state" in mlwr_result and mlwr_result[ "state"] == "completed": # Update notification that it contains malware if "submission" in mlwr_result and mlwr_result[ "submission"]["max_score"] >= 500: malware_failure(notification=notification) return else: # Throw error so celery will retry in sixty seconds raise MalwarePendingException if sending_method == "attach": try: req = urllib.request.Request( personalisation_data[key]["document"] ["direct_file_url"]) with urllib.request.urlopen(req) as response: buffer = response.read() filename = personalisation_data[key]["document"].get( "filename") mime_type = personalisation_data[key]["document"].get( "mime_type") attachments.append({ "name": filename, "data": buffer, "mime_type": mime_type, }) except Exception: current_app.logger.error( "Could not download and attach {}".format( personalisation_data[key]["document"] ["direct_file_url"])) del personalisation_data[key] else: personalisation_data[key] = personalisation_data[key][ "document"]["url"] template_dict = dao_get_template_by_id( notification.template_id, notification.template_version).__dict__ # Local Jinja support - Add USE_LOCAL_JINJA_TEMPLATES=True to .env # Add a folder to the project root called 'jinja_templates' # with a copy of 'email_template.jinja2' from notification-utils repo debug_template_path = ( os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if os.environ.get("USE_LOCAL_JINJA_TEMPLATES") == "True" else None) html_email = HTMLEmailTemplate( template_dict, values=personalisation_data, jinja_path=debug_template_path, **get_html_email_options(service), ) plain_text_email = PlainTextEmailTemplate(template_dict, values=personalisation_data) if current_app.config["SCAN_FOR_PII"]: contains_pii(notification, str(plain_text_email)) if service.research_mode or notification.key_type == KEY_TYPE_TEST: notification.reference = send_email_response(notification.to) update_notification_to_sending(notification, provider) else: if service.sending_domain is None or service.sending_domain.strip( ) == "": sending_domain = current_app.config["NOTIFY_EMAIL_DOMAIN"] else: sending_domain = service.sending_domain from_address = '"{}" <{}@{}>'.format(service.name, service.email_from, sending_domain) email_reply_to = notification.reply_to_text reference = provider.send_email( from_address, validate_and_format_email_address(notification.to), plain_text_email.subject, body=str(plain_text_email), html_body=str(html_email), reply_to_address=validate_and_format_email_address( email_reply_to) if email_reply_to else None, attachments=attachments, ) notification.reference = reference update_notification_to_sending(notification, provider) # Record StatsD stats to compute SLOs statsd_client.timing_with_dates("email.total-time", notification.sent_at, notification.created_at) attachments_category = "with-attachments" if attachments else "no-attachments" statsd_key = f"email.{attachments_category}.process_type-{template_dict['process_type']}" statsd_client.timing_with_dates(statsd_key, notification.sent_at, notification.created_at) statsd_client.incr(statsd_key)
), ( SMSPreviewTemplate( {"content": "((content))", "subject": "((subject))"}, ), ['content'], ), ( PlainTextEmailTemplate( {"content": "((content))", "subject": "((subject))"}, ), ['content', 'subject'], ), ( HTMLEmailTemplate( {"content": "((content))", "subject": "((subject))"}, ), ['content', 'subject'], ), ( EmailPreviewTemplate( {"content": "((content))", "subject": "((subject))"}, ), ['content', 'subject'], ), ( LetterPreviewTemplate( {"content": "((content))", "subject": "((subject))"}, contact_block='((contact_block))', ), ['content', 'subject', 'contact_block'],
def test_makes_links_out_of_URLs(url, url_with_entities_replaced): link = '<a style="word-wrap: break-word;" href="{}">{}</a>'.format( url_with_entities_replaced, url_with_entities_replaced ) assert link in str(HTMLEmailTemplate({'content': url, 'subject': ''})) assert link in str(EmailPreviewTemplate({'content': url, 'subject': ''}))
def test_default_template(content): assert content in str(HTMLEmailTemplate({'content': 'hello world', 'subject': ''}))
def test_html_email_inserts_body(): assert 'the <em>quick</em> brown fox' in str(HTMLEmailTemplate( {'content': 'the <em>quick</em> brown fox', 'subject': ''} ))