2020-04-19 21:35:51 +00:00
|
|
|
import datetime
|
|
|
|
import pytz
|
|
|
|
import requests
|
|
|
|
import smtplib
|
|
|
|
import ssl
|
|
|
|
from email.mime.text import MIMEText
|
|
|
|
from email.mime.multipart import MIMEMultipart
|
|
|
|
from pathlib import Path
|
|
|
|
from dateutil import parser as date_parser
|
|
|
|
from jinja2 import Environment, FileSystemLoader
|
|
|
|
from __main__ import config
|
|
|
|
from __main__ import email_log as log
|
|
|
|
|
|
|
|
|
|
|
|
class Email():
|
|
|
|
def __init__(self, address):
|
|
|
|
self.address = address
|
|
|
|
log.debug(f'{self.address}: Creating email')
|
|
|
|
self.content = {}
|
|
|
|
self.from_address = config['email']['address']
|
|
|
|
self.from_name = config['email']['from']
|
|
|
|
log.debug((
|
|
|
|
f'{self.address}: Sending from {self.from_address} ' +
|
|
|
|
f'({self.from_name})'))
|
|
|
|
|
|
|
|
def pretty_time(self, expiry):
|
|
|
|
current_time = datetime.datetime.now()
|
|
|
|
date = expiry.strftime(config['email']['date_format'])
|
|
|
|
if config.getboolean('email', 'use_24h'):
|
|
|
|
log.debug(f'{self.address}: Using 24h time')
|
|
|
|
time = expiry.strftime('%H:%M')
|
|
|
|
else:
|
|
|
|
log.debug(f'{self.address}: Using 12h time')
|
|
|
|
time = expiry.strftime('%-I:%M %p')
|
|
|
|
expiry_delta = (expiry - current_time).seconds
|
|
|
|
expires_in = {'hours': expiry_delta//3600,
|
|
|
|
'minutes': (expiry_delta//60) % 60}
|
|
|
|
if expires_in['hours'] == 0:
|
|
|
|
expires_in = f'{str(expires_in["minutes"])}m'
|
|
|
|
else:
|
|
|
|
expires_in = (f'{str(expires_in["hours"])}h ' +
|
|
|
|
f'{str(expires_in["minutes"])}m')
|
|
|
|
log.debug(f'{self.address}: Expires in {expires_in}')
|
|
|
|
return {'date': date, 'time': time, 'expires_in': expires_in}
|
|
|
|
|
|
|
|
def construct_invite(self, invite):
|
|
|
|
self.subject = config['invite_emails']['subject']
|
|
|
|
log.debug(f'{self.address}: Using subject {self.subject}')
|
|
|
|
log.debug(f'{self.address}: Constructing email content')
|
|
|
|
expiry = invite['expiry']
|
|
|
|
expiry.replace(tzinfo=None)
|
|
|
|
pretty = self.pretty_time(expiry)
|
|
|
|
email_message = config['email']['message']
|
|
|
|
invite_link = config['invite_emails']['url_base']
|
|
|
|
invite_link += '/' + invite['code']
|
|
|
|
for key in ['text', 'html']:
|
|
|
|
sp = Path(config['invite_emails']['email_' + key]) / '..'
|
|
|
|
sp = str(sp.resolve()) + '/'
|
|
|
|
template_loader = FileSystemLoader(searchpath=sp)
|
|
|
|
template_env = Environment(loader=template_loader)
|
|
|
|
fname = Path(config['invite_emails']['email_' + key]).name
|
|
|
|
template = template_env.get_template(fname)
|
|
|
|
c = template.render(expiry_date=pretty['date'],
|
|
|
|
expiry_time=pretty['time'],
|
|
|
|
expires_in=pretty['expires_in'],
|
|
|
|
invite_link=invite_link,
|
|
|
|
message=email_message)
|
|
|
|
self.content[key] = c
|
|
|
|
log.info(f'{self.address}: {key} constructed')
|
|
|
|
|
|
|
|
def construct_reset(self, reset):
|
|
|
|
self.subject = config['password_resets']['subject']
|
|
|
|
log.debug(f'{self.address}: Using subject {self.subject}')
|
|
|
|
log.debug(f'{self.address}: Constructing email content')
|
|
|
|
try:
|
|
|
|
expiry = date_parser.parse(reset['ExpirationDate'])
|
|
|
|
expiry = expiry.replace(tzinfo=None)
|
|
|
|
except:
|
|
|
|
log.error(f"{self.address}: Couldn't parse expiry time")
|
|
|
|
return False
|
|
|
|
current_time = datetime.datetime.now()
|
|
|
|
if expiry >= current_time:
|
|
|
|
log.debug(f'{self.address}: Invite valid')
|
|
|
|
pretty = self.pretty_time(expiry)
|
|
|
|
email_message = config['email']['message']
|
|
|
|
for key in ['text', 'html']:
|
|
|
|
sp = Path(config['password_resets']['email_' + key]) / '..'
|
|
|
|
sp = str(sp.resolve()) + '/'
|
|
|
|
template_loader = FileSystemLoader(searchpath=sp)
|
|
|
|
template_env = Environment(loader=template_loader)
|
|
|
|
fname = Path(config['password_resets']['email_' + key]).name
|
|
|
|
template = template_env.get_template(fname)
|
|
|
|
c = template.render(username=reset['UserName'],
|
|
|
|
expiry_date=pretty['date'],
|
|
|
|
expiry_time=pretty['time'],
|
|
|
|
expires_in=pretty['expires_in'],
|
|
|
|
pin=reset['Pin'],
|
|
|
|
message=email_message)
|
|
|
|
self.content[key] = c
|
|
|
|
log.info(f'{self.address}: {key} constructed')
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
err = ((f"{self.address}: " +
|
|
|
|
"Reset has reportedly already expired. " +
|
|
|
|
"Ensure timezones are correctly configured."))
|
|
|
|
log.error(err)
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
class Mailgun(Email):
|
|
|
|
def __init__(self, address):
|
|
|
|
super().__init__(address)
|
|
|
|
self.api_url = config['mailgun']['api_url']
|
|
|
|
self.api_key = config['mailgun']['api_key']
|
|
|
|
self.from_mg = f'{self.from_name} <{self.from_address}>'
|
|
|
|
|
|
|
|
def send(self):
|
|
|
|
response = requests.post(self.api_url,
|
|
|
|
auth=("api", self.api_key),
|
|
|
|
data={"from": self.from_mg,
|
|
|
|
"to": [self.address],
|
|
|
|
"subject": self.subject,
|
|
|
|
"text": self.content['text'],
|
|
|
|
"html": self.content['html']})
|
|
|
|
if response.ok:
|
|
|
|
log.info(f'{self.address}: Sent via mailgun.')
|
|
|
|
return True
|
|
|
|
log.debug(f'{self.address}: Mailgun: {response.status_code}')
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
class Smtp(Email):
|
|
|
|
def __init__(self, address):
|
|
|
|
super().__init__(address)
|
|
|
|
self.server = config['smtp']['server']
|
|
|
|
self.password = config['smtp']['password']
|
|
|
|
try:
|
|
|
|
self.port = int(config['smtp']['port'])
|
|
|
|
except ValueError:
|
|
|
|
self.port = 465
|
|
|
|
log.debug(f'{self.address}: Defaulting to port {self.port}')
|
|
|
|
|
|
|
|
def send(self):
|
|
|
|
message = MIMEMultipart("alternative")
|
|
|
|
message["Subject"] = self.subject
|
|
|
|
message["From"] = self.from_address
|
|
|
|
message["To"] = self.address
|
|
|
|
text = MIMEText(self.content['text'], 'plain')
|
|
|
|
html = MIMEText(self.content['html'], 'html')
|
|
|
|
message.attach(text)
|
|
|
|
message.attach(html)
|
|
|
|
try:
|
2020-04-26 20:28:55 +00:00
|
|
|
if config['smtp']['encryption'] == 'ssl_tls':
|
|
|
|
self.context = ssl.create_default_context()
|
|
|
|
with smtplib.SMTP_SSL(self.server,
|
|
|
|
self.port,
|
|
|
|
context=self.context) as server:
|
|
|
|
server.ehlo()
|
|
|
|
server.login(self.from_address, self.password)
|
|
|
|
server.sendmail(self.from_address,
|
|
|
|
self.address,
|
|
|
|
message.as_string())
|
|
|
|
log.info(f'{self.address}: Sent via smtp (ssl/tls)')
|
|
|
|
return True
|
|
|
|
elif config['smtp']['encryption'] == 'starttls':
|
|
|
|
with smtplib.SMTP(self.server,
|
|
|
|
self.port) as server:
|
|
|
|
server.ehlo()
|
|
|
|
server.starttls()
|
|
|
|
server.login(self.from_address, self.password)
|
|
|
|
server.sendmail(self.from_address,
|
|
|
|
self.address,
|
|
|
|
message.as_string())
|
|
|
|
log.info(f'{self.address}: Sent via smtp (starttls)')
|
|
|
|
return True
|
2020-04-19 21:35:51 +00:00
|
|
|
except Exception as e:
|
|
|
|
err = f'{self.address}: Failed to send via smtp: '
|
|
|
|
err += type(e).__name__
|
|
|
|
log.error(err)
|
2020-05-02 17:32:58 +00:00
|
|
|
try:
|
|
|
|
log.error(e.smtp_error)
|
|
|
|
except:
|
|
|
|
pass
|