mirror of
https://github.com/aljazceru/CTFd.git
synced 2026-02-23 07:04:27 +01:00
Change sendmail functions into classes that can be overriden from a plugin (#2221)
* Change sendmail functions into classes that can be overriden from a plugin * Deprecate `CTFd.utils.email.mailgun.sendmail` * Deprecate `CTFd.utils.email.smtp.sendmail`
This commit is contained in:
@@ -102,6 +102,12 @@ MAILGUN_API_KEY =
|
||||
# Installations using the Mailgun API should migrate over to SMTP settings.
|
||||
MAILGUN_BASE_URL =
|
||||
|
||||
# MAIL_PROVIDER
|
||||
# Specifies the email provider that CTFd will use to send email.
|
||||
# By default CTFd will automatically detect the correct email provider based on the other settings
|
||||
# specified here or in the configuration panel. This setting can be used to force a specific provider.
|
||||
MAIL_PROVIDER =
|
||||
|
||||
[uploads]
|
||||
# UPLOAD_PROVIDER
|
||||
# Specifies the service that CTFd should use to store files.
|
||||
|
||||
@@ -154,6 +154,8 @@ class ServerConfig(object):
|
||||
|
||||
MAILGUN_BASE_URL: str = empty_str_cast(config_ini["email"]["MAILGUN_API_KEY"])
|
||||
|
||||
MAIL_PROVIDER: str = empty_str_cast(config_ini["email"].get("MAIL_PROVIDER"))
|
||||
|
||||
# === LOGS ===
|
||||
LOG_FOLDER: str = empty_str_cast(config_ini["logs"]["LOG_FOLDER"]) \
|
||||
or os.path.join(os.path.dirname(os.path.abspath(__file__)), "logs")
|
||||
|
||||
@@ -60,6 +60,9 @@ def can_send_mail():
|
||||
|
||||
|
||||
def get_mail_provider():
|
||||
mail_provider = app.config.get("MAIL_PROVIDER")
|
||||
if mail_provider:
|
||||
return mail_provider
|
||||
if get_config("mail_server") and get_config("mail_port"):
|
||||
return "smtp"
|
||||
if get_config("mailgun_api_key") and get_config("mailgun_base_url"):
|
||||
|
||||
@@ -2,10 +2,13 @@ from flask import url_for
|
||||
|
||||
from CTFd.utils import get_config
|
||||
from CTFd.utils.config import get_mail_provider
|
||||
from CTFd.utils.email import mailgun, smtp
|
||||
from CTFd.utils.email.providers.mailgun import MailgunEmailProvider
|
||||
from CTFd.utils.email.providers.smtp import SMTPEmailProvider
|
||||
from CTFd.utils.formatters import safe_format
|
||||
from CTFd.utils.security.signing import serialize
|
||||
|
||||
PROVIDERS = {"smtp": SMTPEmailProvider, "mailgun": MailgunEmailProvider}
|
||||
|
||||
DEFAULT_VERIFICATION_EMAIL_SUBJECT = "Confirm your account for {ctf_name}"
|
||||
DEFAULT_VERIFICATION_EMAIL_BODY = (
|
||||
"Welcome to {ctf_name}!\n\n"
|
||||
@@ -42,11 +45,10 @@ DEFAULT_PASSWORD_CHANGE_ALERT_BODY = (
|
||||
def sendmail(addr, text, subject="Message from {ctf_name}"):
|
||||
subject = safe_format(subject, ctf_name=get_config("ctf_name"))
|
||||
provider = get_mail_provider()
|
||||
if provider == "smtp":
|
||||
return smtp.sendmail(addr, text, subject)
|
||||
if provider == "mailgun":
|
||||
return mailgun.sendmail(addr, text, subject)
|
||||
return False, "No mail settings configured"
|
||||
EmailProvider = PROVIDERS.get(provider)
|
||||
if EmailProvider is None:
|
||||
return False, "No mail settings configured"
|
||||
return EmailProvider.sendmail(addr, text, subject)
|
||||
|
||||
|
||||
def password_change_alert(email):
|
||||
|
||||
@@ -1,40 +1,8 @@
|
||||
from email.utils import formataddr
|
||||
|
||||
import requests
|
||||
|
||||
from CTFd.utils import get_app_config, get_config
|
||||
from CTFd.utils.email.providers.mailgun import MailgunEmailProvider
|
||||
|
||||
|
||||
def sendmail(addr, text, subject):
|
||||
ctf_name = get_config("ctf_name")
|
||||
mailfrom_addr = get_config("mailfrom_addr") or get_app_config("MAILFROM_ADDR")
|
||||
mailfrom_addr = formataddr((ctf_name, mailfrom_addr))
|
||||
|
||||
mailgun_base_url = get_config("mailgun_base_url") or get_app_config(
|
||||
"MAILGUN_BASE_URL"
|
||||
print(
|
||||
"CTFd.utils.email.mailgun.sendmail will raise an exception in a future minor release of CTFd and then be removed in CTFd v4.0"
|
||||
)
|
||||
mailgun_api_key = get_config("mailgun_api_key") or get_app_config("MAILGUN_API_KEY")
|
||||
try:
|
||||
r = requests.post(
|
||||
mailgun_base_url + "/messages",
|
||||
auth=("api", mailgun_api_key),
|
||||
data={
|
||||
"from": mailfrom_addr,
|
||||
"to": [addr],
|
||||
"subject": subject,
|
||||
"text": text,
|
||||
},
|
||||
timeout=1.0,
|
||||
)
|
||||
except requests.RequestException as e:
|
||||
return (
|
||||
False,
|
||||
"{error} exception occured while handling your request".format(
|
||||
error=type(e).__name__
|
||||
),
|
||||
)
|
||||
|
||||
if r.status_code == 200:
|
||||
return True, "Email sent"
|
||||
else:
|
||||
return False, "Mailgun settings are incorrect"
|
||||
return MailgunEmailProvider.sendmail(addr, text, subject)
|
||||
|
||||
4
CTFd/utils/email/providers/__init__.py
Normal file
4
CTFd/utils/email/providers/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
class EmailProvider:
|
||||
@staticmethod
|
||||
def sendmail(addr, text, subject):
|
||||
raise NotImplementedError
|
||||
45
CTFd/utils/email/providers/mailgun.py
Normal file
45
CTFd/utils/email/providers/mailgun.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from email.utils import formataddr
|
||||
|
||||
import requests
|
||||
|
||||
from CTFd.utils import get_app_config, get_config
|
||||
from CTFd.utils.email.providers import EmailProvider
|
||||
|
||||
|
||||
class MailgunEmailProvider(EmailProvider):
|
||||
@staticmethod
|
||||
def sendmail(addr, text, subject):
|
||||
ctf_name = get_config("ctf_name")
|
||||
mailfrom_addr = get_config("mailfrom_addr") or get_app_config("MAILFROM_ADDR")
|
||||
mailfrom_addr = formataddr((ctf_name, mailfrom_addr))
|
||||
|
||||
mailgun_base_url = get_config("mailgun_base_url") or get_app_config(
|
||||
"MAILGUN_BASE_URL"
|
||||
)
|
||||
mailgun_api_key = get_config("mailgun_api_key") or get_app_config(
|
||||
"MAILGUN_API_KEY"
|
||||
)
|
||||
try:
|
||||
r = requests.post(
|
||||
mailgun_base_url + "/messages",
|
||||
auth=("api", mailgun_api_key),
|
||||
data={
|
||||
"from": mailfrom_addr,
|
||||
"to": [addr],
|
||||
"subject": subject,
|
||||
"text": text,
|
||||
},
|
||||
timeout=1.0,
|
||||
)
|
||||
except requests.RequestException as e:
|
||||
return (
|
||||
False,
|
||||
"{error} exception occured while handling your request".format(
|
||||
error=type(e).__name__
|
||||
),
|
||||
)
|
||||
|
||||
if r.status_code == 200:
|
||||
return True, "Email sent"
|
||||
else:
|
||||
return False, "Mailgun settings are incorrect"
|
||||
79
CTFd/utils/email/providers/smtp.py
Normal file
79
CTFd/utils/email/providers/smtp.py
Normal file
@@ -0,0 +1,79 @@
|
||||
import smtplib
|
||||
from email.message import EmailMessage
|
||||
from email.utils import formataddr
|
||||
from socket import timeout
|
||||
|
||||
from CTFd.utils import get_app_config, get_config
|
||||
from CTFd.utils.email.providers import EmailProvider
|
||||
|
||||
|
||||
class SMTPEmailProvider(EmailProvider):
|
||||
@staticmethod
|
||||
def sendmail(addr, text, subject):
|
||||
ctf_name = get_config("ctf_name")
|
||||
mailfrom_addr = get_config("mailfrom_addr") or get_app_config("MAILFROM_ADDR")
|
||||
mailfrom_addr = formataddr((ctf_name, mailfrom_addr))
|
||||
|
||||
data = {
|
||||
"host": get_config("mail_server") or get_app_config("MAIL_SERVER"),
|
||||
"port": int(get_config("mail_port") or get_app_config("MAIL_PORT")),
|
||||
}
|
||||
username = get_config("mail_username") or get_app_config("MAIL_USERNAME")
|
||||
password = get_config("mail_password") or get_app_config("MAIL_PASSWORD")
|
||||
TLS = get_config("mail_tls") or get_app_config("MAIL_TLS")
|
||||
SSL = get_config("mail_ssl") or get_app_config("MAIL_SSL")
|
||||
auth = get_config("mail_useauth") or get_app_config("MAIL_USEAUTH")
|
||||
|
||||
if username:
|
||||
data["username"] = username
|
||||
if password:
|
||||
data["password"] = password
|
||||
if TLS:
|
||||
data["TLS"] = TLS
|
||||
if SSL:
|
||||
data["SSL"] = SSL
|
||||
if auth:
|
||||
data["auth"] = auth
|
||||
|
||||
try:
|
||||
smtp = get_smtp(**data)
|
||||
|
||||
msg = EmailMessage()
|
||||
msg.set_content(text)
|
||||
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = mailfrom_addr
|
||||
msg["To"] = addr
|
||||
|
||||
# Check whether we are using an admin-defined SMTP server
|
||||
custom_smtp = bool(get_config("mail_server"))
|
||||
|
||||
# We should only consider the MAILSENDER_ADDR value on servers defined in config
|
||||
if custom_smtp:
|
||||
smtp.send_message(msg)
|
||||
else:
|
||||
mailsender_addr = get_app_config("MAILSENDER_ADDR")
|
||||
smtp.send_message(msg, from_addr=mailsender_addr)
|
||||
|
||||
smtp.quit()
|
||||
return True, "Email sent"
|
||||
except smtplib.SMTPException as e:
|
||||
return False, str(e)
|
||||
except timeout:
|
||||
return False, "SMTP server connection timed out"
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
|
||||
def get_smtp(host, port, username=None, password=None, TLS=None, SSL=None, auth=None):
|
||||
if SSL is None:
|
||||
smtp = smtplib.SMTP(host, port, timeout=3)
|
||||
else:
|
||||
smtp = smtplib.SMTP_SSL(host, port, timeout=3)
|
||||
|
||||
if TLS:
|
||||
smtp.starttls()
|
||||
|
||||
if auth:
|
||||
smtp.login(username, password)
|
||||
return smtp
|
||||
@@ -1,76 +1,8 @@
|
||||
import smtplib
|
||||
from email.message import EmailMessage
|
||||
from email.utils import formataddr
|
||||
from socket import timeout
|
||||
|
||||
from CTFd.utils import get_app_config, get_config
|
||||
|
||||
|
||||
def get_smtp(host, port, username=None, password=None, TLS=None, SSL=None, auth=None):
|
||||
if SSL is None:
|
||||
smtp = smtplib.SMTP(host, port, timeout=3)
|
||||
else:
|
||||
smtp = smtplib.SMTP_SSL(host, port, timeout=3)
|
||||
|
||||
if TLS:
|
||||
smtp.starttls()
|
||||
|
||||
if auth:
|
||||
smtp.login(username, password)
|
||||
return smtp
|
||||
from CTFd.utils.email.providers.smtp import SMTPEmailProvider
|
||||
|
||||
|
||||
def sendmail(addr, text, subject):
|
||||
ctf_name = get_config("ctf_name")
|
||||
mailfrom_addr = get_config("mailfrom_addr") or get_app_config("MAILFROM_ADDR")
|
||||
mailfrom_addr = formataddr((ctf_name, mailfrom_addr))
|
||||
|
||||
data = {
|
||||
"host": get_config("mail_server") or get_app_config("MAIL_SERVER"),
|
||||
"port": int(get_config("mail_port") or get_app_config("MAIL_PORT")),
|
||||
}
|
||||
username = get_config("mail_username") or get_app_config("MAIL_USERNAME")
|
||||
password = get_config("mail_password") or get_app_config("MAIL_PASSWORD")
|
||||
TLS = get_config("mail_tls") or get_app_config("MAIL_TLS")
|
||||
SSL = get_config("mail_ssl") or get_app_config("MAIL_SSL")
|
||||
auth = get_config("mail_useauth") or get_app_config("MAIL_USEAUTH")
|
||||
|
||||
if username:
|
||||
data["username"] = username
|
||||
if password:
|
||||
data["password"] = password
|
||||
if TLS:
|
||||
data["TLS"] = TLS
|
||||
if SSL:
|
||||
data["SSL"] = SSL
|
||||
if auth:
|
||||
data["auth"] = auth
|
||||
|
||||
try:
|
||||
smtp = get_smtp(**data)
|
||||
|
||||
msg = EmailMessage()
|
||||
msg.set_content(text)
|
||||
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = mailfrom_addr
|
||||
msg["To"] = addr
|
||||
|
||||
# Check whether we are using an admin-defined SMTP server
|
||||
custom_smtp = bool(get_config("mail_server"))
|
||||
|
||||
# We should only consider the MAILSENDER_ADDR value on servers defined in config
|
||||
if custom_smtp:
|
||||
smtp.send_message(msg)
|
||||
else:
|
||||
mailsender_addr = get_app_config("MAILSENDER_ADDR")
|
||||
smtp.send_message(msg, from_addr=mailsender_addr)
|
||||
|
||||
smtp.quit()
|
||||
return True, "Email sent"
|
||||
except smtplib.SMTPException as e:
|
||||
return False, str(e)
|
||||
except timeout:
|
||||
return False, "SMTP server connection timed out"
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
print(
|
||||
"CTFd.utils.email.smtp.sendmail will raise an exception in a future minor release of CTFd and then be removed in CTFd v4.0"
|
||||
)
|
||||
return SMTPEmailProvider.sendmail(addr, text, subject)
|
||||
|
||||
Reference in New Issue
Block a user