import json import logging import os from unittest import mock from flask_mail import Message import features from _init import ROOT_DIR from app import app, get_app_url, mail from util.fips import login_fips_safe from util.html import html2text from util.jinjautil import get_template_env logger = logging.getLogger(__name__) template_env = get_template_env(os.path.join(ROOT_DIR, "emails")) class CannotSendEmailException(Exception): pass class GmailAction(object): """ Represents an action that can be taken in Gmail in response to the email. """ def __init__(self, metadata): self.metadata = metadata @staticmethod def confirm(name, url, description): return GmailAction( { "@context": "http://schema.org", "@type": "EmailMessage", "action": { "@type": "ConfirmAction", "name": name, "handler": {"@type": "HttpActionHandler", "url": get_app_url() + "/" + url}, }, "description": description, } ) @staticmethod def view(name, url, description): return GmailAction( { "@context": "http://schema.org", "@type": "EmailMessage", "action": {"@type": "ViewAction", "name": name, "url": get_app_url() + "/" + url}, "description": description, } ) def send_email(recipient, subject, template_file, parameters, action=None): app_title = app.config["REGISTRY_TITLE_SHORT"] app_url = get_app_url() html, plain = render_email( app_title, app_url, recipient, subject, template_file, parameters, action=None ) msg = Message("[%s] %s" % (app_title, subject), recipients=[recipient]) msg.html = html msg.body = plain try: if features.FIPS: assert app.config[ "MAIL_USE_TLS" ], "MAIL_USE_TLS must be enabled to use SMTP in FIPS mode." with mock.patch("smtplib.SMTP.login", login_fips_safe): mail.send(msg) else: mail.send(msg) if app.config["TESTING"]: logger.debug("Quay is configured for testing. Email not sent: '%s'", msg.subject) else: logger.debug("Sent email: '%s'", msg.subject) except Exception as ex: logger.exception("Error while trying to send email to %s", recipient) raise CannotSendEmailException(str(ex)) def render_email(app_title, app_url, recipient, subject, template_file, parameters, action=None): def app_link_handler(url=None): return app_url + "/" + url if url else app_url app_logo = app.config.get("ENTERPRISE_LOGO_URL", "https://quay.io/static/img/quay-logo.png") parameters.update( { "subject": subject, "app_logo": app_logo, "app_url": app_url, "app_title": app_title, "hosted": features.BILLING, "app_link": app_link_handler, "action_metadata": json.dumps(action.metadata) if action else None, "with_base_template": True, } ) rendered_html = template_env.get_template(template_file + ".html").render(parameters) parameters.update( { "with_base_template": False, } ) rendered_for_plain = template_env.get_template(template_file + ".html").render(parameters) return rendered_html, html2text(rendered_for_plain) def send_password_changed(username, email): send_email(email, "Account password changed", "passwordchanged", {"username": username}) def send_email_changed(username, old_email, new_email): send_email( old_email, "Account e-mail address changed", "emailchanged", {"username": username, "new_email": new_email}, ) def send_change_email(username, email, token): send_email( email, "E-mail address change requested", "changeemail", {"username": username, "token": token}, ) def send_confirmation_email(username, email, token): action = GmailAction.confirm( "Confirm E-mail", "confirm?code=" + token, "Verification of e-mail address" ) send_email( email, "Please confirm your e-mail address", "confirmemail", {"username": username, "token": token}, action=action, ) def send_repo_authorization_email(namespace, repository, email, token): action = GmailAction.confirm( "Verify E-mail", "authrepoemail?code=" + token, "Verification of e-mail address" ) subject = "Please verify your e-mail address for repository %s/%s" % (namespace, repository) send_email( email, subject, "repoauthorizeemail", {"namespace": namespace, "repository": repository, "token": token}, action=action, ) def send_org_recovery_email(org, admin_users): subject = "Organization %s recovery" % (org.username) send_email( org.email, subject, "orgrecovery", { "organization": org.username, "admin_usernames": [user.username for user in admin_users], }, ) def send_recovery_email(email, token): action = GmailAction.view("Recover Account", "recovery?code=" + token, "Recovery of an account") subject = "Account recovery" send_email(email, subject, "recovery", {"email": email, "token": token}, action=action) def send_payment_failed(email, username): send_email(email, "Subscription Payment Failure", "paymentfailure", {"username": username}) def send_org_invite_email(member_name, member_email, orgname, team, adder, code): action = GmailAction.view( "Join %s" % team, "confirminvite?code=" + code, "Invitation to join a team" ) send_email( member_email, "Invitation to join team", "teaminvite", {"inviter": adder, "token": code, "organization": orgname, "teamname": team}, action=action, ) def send_invoice_email(email, contents): # Note: This completely generates the contents of the email, so we don't use the # normal template here. msg = Message("Quay payment received - Thank you!", recipients=[email]) msg.html = contents if features.FIPS: assert app.config["MAIL_USE_TLS"], "MAIL_USE_TLS must be enabled to use SMTP in FIPS mode." with mock.patch("smtplib.SMTP.login", login_fips_safe): mail.send(msg) else: mail.send(msg) def send_logs_exported_email( email, export_id, status, exported_data_url=None, exported_data_expiration=None ): send_email( email, "Export Action Logs Complete", "logsexported", { "status": status, "export_id": export_id, "exported_data_url": exported_data_url, "exported_data_expiration": exported_data_expiration, }, ) # INTERNAL EMAILS BELOW def send_subscription_change(change_description, customer_id, customer_email, quay_username): SUBSCRIPTION_CHANGE_TITLE = "Subscription Change - {0} {1}" SUBSCRIPTION_CHANGE = """ Change: {0}
Customer id: {1}
Customer email: {2}
Quay user or org name: {3}
""" title = SUBSCRIPTION_CHANGE_TITLE.format(quay_username, change_description) msg = Message(title, recipients=["stripe@quay.io"]) msg.html = SUBSCRIPTION_CHANGE.format( change_description, customer_id, customer_email, quay_username ) if features.FIPS: assert app.config["MAIL_USE_TLS"], "MAIL_USE_TLS must be enabled to use SMTP in FIPS mode." with mock.patch("smtplib.SMTP.login", login_fips_safe): mail.send(msg) else: mail.send(msg)