backend/services/messaging/smtp_service.py (127 lines of code) (raw):
import smtplib
import urllib.parse
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from itsdangerous import URLSafeTimedSerializer
from flask import current_app
from backend.services.messaging.template_service import (
get_template,
format_username_link,
)
class SMTPService:
@staticmethod
def send_verification_email(to_address: str, username: str):
""" Sends a verification email with a unique token so we can verify user owns this email address """
# TODO these could be localised if needed, in the future
verification_url = SMTPService._generate_email_verification_url(
to_address, username
)
values = {
"USERNAME": username,
"VERIFICATION_LINK": verification_url,
}
html_template = get_template("email_verification_en.html", values)
subject = "Confirm your email address"
SMTPService._send_message(to_address, subject, html_template)
return True
@staticmethod
def send_contact_admin_email(data):
email_to = current_app.config["EMAIL_CONTACT_ADDRESS"]
if email_to is None:
raise ValueError("variable TM_EMAIL_CONTACT_ADDRESS not set")
message = """New contact from {name} - {email}.
<p>{content}</p>
""".format(
name=data.get("name"),
email=data.get("email"),
content=data.get("content"),
)
subject = "New contact from {name}".format(name=data.get("name"))
SMTPService._send_message(email_to, subject, message, message)
@staticmethod
def send_email_alert(
to_address: str,
username: str,
user_email_verified: bool,
message_id: int,
from_username: str,
project_id: int,
task_id: int,
subject: str,
content: str,
message_type: int,
):
"""Send an email to user to alert that they have a new message."""
if not user_email_verified:
return False
current_app.logger.debug(f"Test if email required {to_address}")
from_user_link = f"{current_app.config['APP_BASE_URL']}/users/{from_username}"
project_link = f"{current_app.config['APP_BASE_URL']}/projects/{project_id}"
task_link = f"{current_app.config['APP_BASE_URL']}/projects/{project_id}/tasks/?search={task_id}"
settings_url = "{}/settings#notifications".format(
current_app.config["APP_BASE_URL"]
)
if not to_address:
return False # Many users will not have supplied email address so return
message_path = ""
if message_id is not None:
message_path = f"/message/{message_id}"
inbox_url = f"{current_app.config['APP_BASE_URL']}/inbox{message_path}"
values = {
"FROM_USER_LINK": from_user_link,
"FROM_USERNAME": from_username,
"PROJECT_LINK": project_link,
"PROJECT_ID": str(project_id) if project_id is not None else None,
"TASK_LINK": task_link,
"TASK_ID": str(task_id) if task_id is not None else None,
"PROFILE_LINK": inbox_url,
"SETTINGS_LINK": settings_url,
"CONTENT": format_username_link(content),
"MESSAGE_TYPE": message_type,
}
html_template = get_template("message_alert_en.html", values)
SMTPService._send_message(to_address, subject, html_template)
return True
@staticmethod
def _send_message(
to_address: str, subject: str, html_message: str, text_message: str = None
):
""" Helper sends SMTP message """
from_address = current_app.config["EMAIL_FROM_ADDRESS"]
if from_address is None:
raise ValueError("Missing TM_EMAIL_FROM_ADDRESS environment variable")
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = "{} Tasking Manager <{}>".format(
current_app.config["ORG_CODE"], from_address
)
msg["To"] = to_address
# Record the MIME types of both parts - text/plain and text/html.
part2 = MIMEText(html_message, "html")
msg.attach(part2)
if text_message:
part1 = MIMEText(text_message, "plain")
msg.attach(part1)
current_app.logger.debug(f"Sending email via SMTP {to_address}")
if current_app.config["SMTP_SETTINGS"]["host"] is None:
current_app.logger.debug(msg.as_string())
else:
sender = SMTPService._init_smtp_client()
sender.sendmail(from_address, to_address, msg.as_string())
sender.quit()
current_app.logger.debug(f"Email sent {to_address}")
@staticmethod
def _init_smtp_client():
""" Initialise SMTP client from app settings """
smtp_settings = current_app.config["SMTP_SETTINGS"]
sender = smtplib.SMTP(smtp_settings["host"], port=smtp_settings["smtp_port"])
if current_app.config["LOG_LEVEL"] == "DEBUG":
sender.set_debuglevel(1)
sender.starttls()
if smtp_settings["smtp_user"] and smtp_settings["smtp_password"]:
sender.login(smtp_settings["smtp_user"], smtp_settings["smtp_password"])
return sender
@staticmethod
def _generate_email_verification_url(email_address: str, user_name: str):
""" Generate email verification url with unique token """
entropy = current_app.secret_key if current_app.secret_key else "un1testingmode"
serializer = URLSafeTimedSerializer(entropy)
token = serializer.dumps(email_address)
base_url = current_app.config["APP_BASE_URL"]
verification_params = {"token": token, "username": user_name}
verification_url = "{0}/verify-email/?{1}".format(
base_url, urllib.parse.urlencode(verification_params)
)
return verification_url