Merge pull request #3246 from jtschladen/send-single-email
Send a single email to multiple recipients instead of multiple emails
This commit is contained in:
commit
33e34d76b2
|
@ -103,8 +103,9 @@ def send_plugin_notification(event_type, data, recipients, notification):
|
||||||
function = f"{__name__}.{sys._getframe().f_code.co_name}"
|
function = f"{__name__}.{sys._getframe().f_code.co_name}"
|
||||||
log_data = {
|
log_data = {
|
||||||
"function": function,
|
"function": function,
|
||||||
"message": f"Sending expiration notification for to recipients {recipients}",
|
"message": f"Sending {event_type} notification for to recipients {recipients}",
|
||||||
"notification_type": "expiration",
|
"notification_type": event_type,
|
||||||
|
"notification_plugin": notification.plugin.slug,
|
||||||
"certificate_targets": recipients,
|
"certificate_targets": recipients,
|
||||||
}
|
}
|
||||||
status = FAILURE_METRIC_STATUS
|
status = FAILURE_METRIC_STATUS
|
||||||
|
@ -121,7 +122,7 @@ def send_plugin_notification(event_type, data, recipients, notification):
|
||||||
"notification",
|
"notification",
|
||||||
"counter",
|
"counter",
|
||||||
1,
|
1,
|
||||||
metric_tags={"status": status, "event_type": event_type},
|
metric_tags={"status": status, "event_type": event_type, "plugin": notification.plugin.slug},
|
||||||
)
|
)
|
||||||
|
|
||||||
if status == SUCCESS_METRIC_STATUS:
|
if status == SUCCESS_METRIC_STATUS:
|
||||||
|
@ -142,7 +143,6 @@ def send_expiration_notifications(exclude):
|
||||||
|
|
||||||
for notification_label, certificates in notification_group.items():
|
for notification_label, certificates in notification_group.items():
|
||||||
notification_data = []
|
notification_data = []
|
||||||
security_data = []
|
|
||||||
|
|
||||||
notification = certificates[0][0]
|
notification = certificates[0][0]
|
||||||
|
|
||||||
|
@ -152,33 +152,26 @@ def send_expiration_notifications(exclude):
|
||||||
certificate
|
certificate
|
||||||
).data
|
).data
|
||||||
notification_data.append(cert_data)
|
notification_data.append(cert_data)
|
||||||
security_data.append(cert_data)
|
|
||||||
|
|
||||||
if send_default_notification(
|
|
||||||
"expiration", notification_data, [owner], notification.options
|
|
||||||
):
|
|
||||||
success += 1
|
|
||||||
else:
|
|
||||||
failure += 1
|
|
||||||
|
|
||||||
recipients = notification.plugin.filter_recipients(notification.options, security_email + [owner])
|
|
||||||
|
|
||||||
|
email_recipients = notification.plugin.get_recipients(notification.options, security_email + [owner])
|
||||||
|
# Plugin will ONLY use the provided recipients if it's email; any other notification plugin ignores them
|
||||||
if send_plugin_notification(
|
if send_plugin_notification(
|
||||||
"expiration",
|
"expiration", notification_data, email_recipients, notification
|
||||||
notification_data,
|
|
||||||
recipients,
|
|
||||||
notification,
|
|
||||||
):
|
):
|
||||||
success += 1
|
success += len(email_recipients)
|
||||||
else:
|
else:
|
||||||
failure += 1
|
failure += len(email_recipients)
|
||||||
|
# If we're using an email plugin, we're done,
|
||||||
if send_default_notification(
|
# since "security_email + [owner]" were added as email_recipients.
|
||||||
"expiration", security_data, security_email, notification.options
|
# If we're not using an email plugin, we also need to send an email to the security team and owner,
|
||||||
):
|
# since the plugin notification didn't send anything to them.
|
||||||
success += 1
|
if notification.plugin.slug != "email-notification":
|
||||||
else:
|
if send_default_notification(
|
||||||
failure += 1
|
"expiration", notification_data, email_recipients, notification.options
|
||||||
|
):
|
||||||
|
success = 1 + len(email_recipients)
|
||||||
|
else:
|
||||||
|
failure = 1 + len(email_recipients)
|
||||||
|
|
||||||
return success, failure
|
return success, failure
|
||||||
|
|
||||||
|
@ -195,15 +188,16 @@ def send_default_notification(notification_type, data, targets, notification_opt
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
function = f"{__name__}.{sys._getframe().f_code.co_name}"
|
function = f"{__name__}.{sys._getframe().f_code.co_name}"
|
||||||
log_data = {
|
|
||||||
"function": function,
|
|
||||||
"message": f"Sending notification for certificate data {data}",
|
|
||||||
"notification_type": notification_type,
|
|
||||||
}
|
|
||||||
status = FAILURE_METRIC_STATUS
|
status = FAILURE_METRIC_STATUS
|
||||||
notification_plugin = plugins.get(
|
notification_plugin = plugins.get(
|
||||||
current_app.config.get("LEMUR_DEFAULT_NOTIFICATION_PLUGIN", "email-notification")
|
current_app.config.get("LEMUR_DEFAULT_NOTIFICATION_PLUGIN", "email-notification")
|
||||||
)
|
)
|
||||||
|
log_data = {
|
||||||
|
"function": function,
|
||||||
|
"message": f"Sending {notification_type} notification for certificate data {data} to targets {targets}",
|
||||||
|
"notification_type": notification_type,
|
||||||
|
"notification_plugin": notification_plugin.slug,
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
current_app.logger.debug(log_data)
|
current_app.logger.debug(log_data)
|
||||||
|
@ -212,7 +206,7 @@ def send_default_notification(notification_type, data, targets, notification_opt
|
||||||
status = SUCCESS_METRIC_STATUS
|
status = SUCCESS_METRIC_STATUS
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log_data["message"] = f"Unable to send {notification_type} notification for certificate data {data} " \
|
log_data["message"] = f"Unable to send {notification_type} notification for certificate data {data} " \
|
||||||
f"to target {targets}"
|
f"to targets {targets}"
|
||||||
current_app.logger.error(log_data, exc_info=True)
|
current_app.logger.error(log_data, exc_info=True)
|
||||||
sentry.captureException()
|
sentry.captureException()
|
||||||
|
|
||||||
|
@ -220,7 +214,7 @@ def send_default_notification(notification_type, data, targets, notification_opt
|
||||||
"notification",
|
"notification",
|
||||||
"counter",
|
"counter",
|
||||||
1,
|
1,
|
||||||
metric_tags={"status": status, "event_type": notification_type},
|
metric_tags={"status": status, "event_type": notification_type, "plugin": notification_plugin.slug},
|
||||||
)
|
)
|
||||||
|
|
||||||
if status == SUCCESS_METRIC_STATUS:
|
if status == SUCCESS_METRIC_STATUS:
|
||||||
|
@ -247,15 +241,14 @@ def send_pending_failure_notification(
|
||||||
data = pending_certificate_output_schema.dump(pending_cert).data
|
data = pending_certificate_output_schema.dump(pending_cert).data
|
||||||
data["security_email"] = current_app.config.get("LEMUR_SECURITY_TEAM_EMAIL")
|
data["security_email"] = current_app.config.get("LEMUR_SECURITY_TEAM_EMAIL")
|
||||||
|
|
||||||
notify_owner_success = False
|
email_recipients = []
|
||||||
if notify_owner:
|
if notify_owner:
|
||||||
notify_owner_success = send_default_notification("failed", data, [data["owner"]], pending_cert)
|
email_recipients = email_recipients + [data["owner"]]
|
||||||
|
|
||||||
notify_security_success = False
|
|
||||||
if notify_security:
|
if notify_security:
|
||||||
notify_security_success = send_default_notification("failed", data, data["security_email"], pending_cert)
|
email_recipients = email_recipients + data["security_email"]
|
||||||
|
|
||||||
return notify_owner_success or notify_security_success
|
return send_default_notification("failed", data, email_recipients, pending_cert)
|
||||||
|
|
||||||
|
|
||||||
def needs_notification(certificate):
|
def needs_notification(certificate):
|
||||||
|
|
|
@ -20,14 +20,14 @@ class NotificationPlugin(Plugin):
|
||||||
def send(self, notification_type, message, targets, options, **kwargs):
|
def send(self, notification_type, message, targets, options, **kwargs):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def filter_recipients(self, options, excluded_recipients):
|
def get_recipients(self, options, additional_recipients):
|
||||||
"""
|
"""
|
||||||
Given a set of options (which should include configured recipient info), filters out recipients that
|
Given a set of options (which should include configured recipient info), returns the parsed list of recipients
|
||||||
we do NOT want to notify.
|
from those options plus the additional recipients specified. The returned value has no duplicates.
|
||||||
|
|
||||||
For any notification types where recipients can't be dynamically modified, this returns an empty list.
|
For any notification types where recipients can't be dynamically modified, this returns only the additional recipients.
|
||||||
"""
|
"""
|
||||||
return []
|
return additional_recipients
|
||||||
|
|
||||||
|
|
||||||
class ExpirationNotificationPlugin(NotificationPlugin):
|
class ExpirationNotificationPlugin(NotificationPlugin):
|
||||||
|
|
|
@ -105,6 +105,8 @@ class EmailNotificationPlugin(ExpirationNotificationPlugin):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def send(notification_type, message, targets, options, **kwargs):
|
def send(notification_type, message, targets, options, **kwargs):
|
||||||
|
if not targets:
|
||||||
|
return
|
||||||
|
|
||||||
subject = "Lemur: {0} Notification".format(notification_type.capitalize())
|
subject = "Lemur: {0} Notification".format(notification_type.capitalize())
|
||||||
|
|
||||||
|
@ -119,11 +121,9 @@ class EmailNotificationPlugin(ExpirationNotificationPlugin):
|
||||||
send_via_smtp(subject, body, targets)
|
send_via_smtp(subject, body, targets)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def filter_recipients(options, excluded_recipients, **kwargs):
|
def get_recipients(options, additional_recipients, **kwargs):
|
||||||
notification_recipients = get_plugin_option("recipients", options)
|
notification_recipients = get_plugin_option("recipients", options)
|
||||||
if notification_recipients:
|
if notification_recipients:
|
||||||
notification_recipients = notification_recipients.split(",")
|
notification_recipients = notification_recipients.split(",")
|
||||||
# removing owner and security_email from notification_recipient
|
|
||||||
notification_recipients = [i for i in notification_recipients if i not in excluded_recipients]
|
|
||||||
|
|
||||||
return notification_recipients
|
return list(set(notification_recipients + additional_recipients))
|
||||||
|
|
|
@ -21,7 +21,6 @@ def get_options():
|
||||||
|
|
||||||
|
|
||||||
def test_render_expiration(certificate, endpoint):
|
def test_render_expiration(certificate, endpoint):
|
||||||
|
|
||||||
new_cert = CertificateFactory()
|
new_cert = CertificateFactory()
|
||||||
new_cert.replaces.append(certificate)
|
new_cert.replaces.append(certificate)
|
||||||
|
|
||||||
|
@ -54,7 +53,7 @@ def test_send_expiration_notification():
|
||||||
certificate.notifications[0].options = get_options()
|
certificate.notifications[0].options = get_options()
|
||||||
|
|
||||||
verify_sender_email()
|
verify_sender_email()
|
||||||
assert send_expiration_notifications([]) == (3, 0) # owner, recipients (only counted as 1), and security
|
assert send_expiration_notifications([]) == (4, 0) # owner (1), recipients (2), and security (1)
|
||||||
|
|
||||||
|
|
||||||
@mock_ses
|
@mock_ses
|
||||||
|
@ -76,15 +75,20 @@ def test_send_pending_failure_notification(user, pending_certificate, async_issu
|
||||||
|
|
||||||
verify_sender_email()
|
verify_sender_email()
|
||||||
assert send_pending_failure_notification(pending_certificate)
|
assert send_pending_failure_notification(pending_certificate)
|
||||||
|
assert send_pending_failure_notification(pending_certificate, True, True)
|
||||||
|
assert send_pending_failure_notification(pending_certificate, True, False)
|
||||||
|
assert send_pending_failure_notification(pending_certificate, False, True)
|
||||||
|
assert send_pending_failure_notification(pending_certificate, False, False)
|
||||||
|
|
||||||
|
|
||||||
def test_filter_recipients(certificate, endpoint):
|
def test_get_recipients(certificate, endpoint):
|
||||||
from lemur.plugins.lemur_email.plugin import EmailNotificationPlugin
|
from lemur.plugins.lemur_email.plugin import EmailNotificationPlugin
|
||||||
|
|
||||||
options = [{"name": "recipients", "value": "security@example.com,bob@example.com,joe@example.com"}]
|
options = [{"name": "recipients", "value": "security@example.com,joe@example.com"}]
|
||||||
assert EmailNotificationPlugin.filter_recipients(options, []) == ["security@example.com", "bob@example.com",
|
two_emails = sorted(["security@example.com", "joe@example.com"])
|
||||||
"joe@example.com"]
|
assert sorted(EmailNotificationPlugin.get_recipients(options, [])) == two_emails
|
||||||
assert EmailNotificationPlugin.filter_recipients(options, ["security@example.com"]) == ["bob@example.com",
|
assert sorted(EmailNotificationPlugin.get_recipients(options, ["security@example.com"])) == two_emails
|
||||||
"joe@example.com"]
|
three_emails = sorted(["security@example.com", "bob@example.com", "joe@example.com"])
|
||||||
assert EmailNotificationPlugin.filter_recipients(options, ["security@example.com", "bob@example.com",
|
assert sorted(EmailNotificationPlugin.get_recipients(options, ["bob@example.com"])) == three_emails
|
||||||
"joe@example.com"]) == []
|
assert sorted(EmailNotificationPlugin.get_recipients(options, ["security@example.com", "bob@example.com",
|
||||||
|
"joe@example.com"])) == three_emails
|
||||||
|
|
Loading…
Reference in New Issue