mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-23 15:40:01 -06:00
Add a jinja2 e-mail template for EPN
Add options for character set (default utf8) and message subtype (default plain). This will allow for more control for users to do either HTML mail or use ascii for the character set so the attachment is not base64-encoded to make it easier for all mail clients. Collect first and last name as well for each user in order to provide more options for the template engine. Make the From address configurable, defaulting to noreply@ipa_domain Make Subject configurable too. Don't rely on the MTA to set Message-Id: set it using the email module. Fixes: https://pagure.io/freeipa/issue/3687 Signed-off-by: Rob Crittenden <rcritten@redhat.com> Reviewed-By: Michal Polovka <mpolovka@redhat.com> Reviewed-By: Christian Heimes <cheimes@redhat.com>
This commit is contained in:
parent
3805eff417
commit
03caa7f965
@ -66,8 +66,19 @@ Specifies the number of seconds to wait for SMTP to respond. Default 60.
|
|||||||
.B smtp_security <security>
|
.B smtp_security <security>
|
||||||
Specifies the type of secure connection to make. Options are: none, starttls and ssl. The default is none.
|
Specifies the type of secure connection to make. Options are: none, starttls and ssl. The default is none.
|
||||||
.TP
|
.TP
|
||||||
|
.B mail_from <address>
|
||||||
|
Specifies the From: e-mal address value in the e-mails sent. The default is
|
||||||
|
noreply@ipadefaultemaildomain. This value can be found by running
|
||||||
|
.I ipa config-show
|
||||||
|
.TP
|
||||||
.B notify_ttls <list of days>
|
.B notify_ttls <list of days>
|
||||||
This is the list of days before a password expiration when ipa-epn shoould notify a user that their password will soon require a reset. If this value is not specified then the default list will be used: 28, 14, 7, 3, 1.
|
This is the list of days before a password expiration when ipa-epn should notify a user that their password will soon require a reset. If this value is not specified then the default list will be used: 28, 14, 7, 3, 1.
|
||||||
|
.TP
|
||||||
|
.B msg_charset <type>
|
||||||
|
Set the character set of the message. The default is utf8. This will result in he body of the message being base64-encoded.
|
||||||
|
.TP
|
||||||
|
.B msg_subtype <type>
|
||||||
|
Set the message's MIME sub-content type. The default is plain.
|
||||||
.SH "FILES"
|
.SH "FILES"
|
||||||
.TP
|
.TP
|
||||||
.I /etc/ipa/epn.conf
|
.I /etc/ipa/epn.conf
|
||||||
|
@ -52,6 +52,18 @@ The \fB\-\-dry\-run\fR CLI option is intented to test ipa\-epn's configuration.
|
|||||||
|
|
||||||
For instance, if notify_ttls is set to 21, 14, 3, \fB\-\-dry-run\fR would display the list of users whose passwords would expire in 21, 14, and 3 days in the future.
|
For instance, if notify_ttls is set to 21, 14, 3, \fB\-\-dry-run\fR would display the list of users whose passwords would expire in 21, 14, and 3 days in the future.
|
||||||
|
|
||||||
|
.SH "TEMPLATE"
|
||||||
|
The template for the e\-mail message is contained in /etc/ipa/epn/expire_msg.template. The following template variables are available.
|
||||||
|
.TP
|
||||||
|
User ID: uid
|
||||||
|
.TP
|
||||||
|
Full name: fullname
|
||||||
|
.TP
|
||||||
|
First name: first
|
||||||
|
.TP
|
||||||
|
Last name: Last
|
||||||
|
.TP
|
||||||
|
Password expiration date: expiration
|
||||||
|
|
||||||
.SH "EXAMPLES"
|
.SH "EXAMPLES"
|
||||||
.nf
|
.nf
|
||||||
|
@ -1363,6 +1363,7 @@ fi
|
|||||||
%{_mandir}/man5/epn.conf.5*
|
%{_mandir}/man5/epn.conf.5*
|
||||||
%attr(644,root,root) %{_unitdir}/ipa-epn.service
|
%attr(644,root,root) %{_unitdir}/ipa-epn.service
|
||||||
%attr(644,root,root) %{_unitdir}/ipa-epn.timer
|
%attr(644,root,root) %{_unitdir}/ipa-epn.timer
|
||||||
|
%attr(644,root,root) %{_sysconfdir}/ipa/epn/expire_msg.template
|
||||||
|
|
||||||
%files -n python3-ipaclient
|
%files -n python3-ipaclient
|
||||||
%doc README.md Contributors.txt
|
%doc README.md Contributors.txt
|
||||||
|
@ -106,3 +106,8 @@ dist_app_DATA = \
|
|||||||
kdcproxyconfdir = $(IPA_SYSCONF_DIR)/kdcproxy
|
kdcproxyconfdir = $(IPA_SYSCONF_DIR)/kdcproxy
|
||||||
dist_kdcproxyconf_DATA = \
|
dist_kdcproxyconf_DATA = \
|
||||||
kdcproxy.conf
|
kdcproxy.conf
|
||||||
|
|
||||||
|
epnconfdir = $(IPA_SYSCONF_DIR)/epn
|
||||||
|
dist_epnconf_DATA = \
|
||||||
|
expire_msg.template \
|
||||||
|
$(NULL)
|
||||||
|
5
install/share/expire_msg.template
Normal file
5
install/share/expire_msg.template
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Hi {{ fullname }},
|
||||||
|
|
||||||
|
Your password will expire on {{ expiration }}.
|
||||||
|
|
||||||
|
Please change it as soon as possible.
|
@ -36,6 +36,7 @@ from email.utils import formataddr, formatdate
|
|||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.header import Header
|
from email.header import Header
|
||||||
|
from email.utils import make_msgid
|
||||||
|
|
||||||
from ipaclient.install.client import is_ipa_client_installed
|
from ipaclient.install.client import is_ipa_client_installed
|
||||||
from ipaplatform.paths import paths
|
from ipaplatform.paths import paths
|
||||||
@ -44,6 +45,8 @@ from ipalib.install import sysrestore
|
|||||||
from ipapython import admintool, ipaldap
|
from ipapython import admintool, ipaldap
|
||||||
from ipapython.dn import DN
|
from ipapython.dn import DN
|
||||||
|
|
||||||
|
from jinja2 import Environment, FileSystemLoader, TemplateSyntaxError
|
||||||
|
|
||||||
|
|
||||||
EPN_CONF = "/etc/ipa/epn.conf"
|
EPN_CONF = "/etc/ipa/epn.conf"
|
||||||
EPN_CONFIG = {
|
EPN_CONFIG = {
|
||||||
@ -54,7 +57,11 @@ EPN_CONFIG = {
|
|||||||
"smtp_timeout": 60,
|
"smtp_timeout": 60,
|
||||||
"smtp_security": "none",
|
"smtp_security": "none",
|
||||||
"smtp_admin": "root@localhost",
|
"smtp_admin": "root@localhost",
|
||||||
|
"mail_from": None,
|
||||||
"notify_ttls": "28,14,7,3,1",
|
"notify_ttls": "28,14,7,3,1",
|
||||||
|
"msg_charset": "utf8",
|
||||||
|
"msg_subtype": "plain",
|
||||||
|
"msg_subject": "Your password will expire soon.",
|
||||||
}
|
}
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -116,7 +123,7 @@ class EPNUserList:
|
|||||||
|
|
||||||
def add(self, entry):
|
def add(self, entry):
|
||||||
"""Parses and appends an LDAP user entry with the uid, cn,
|
"""Parses and appends an LDAP user entry with the uid, cn,
|
||||||
krbpasswordexpiration and mail attributes.
|
givenname, sn, krbpasswordexpiration and mail attributes.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self._sorted = False
|
self._sorted = False
|
||||||
@ -124,6 +131,8 @@ class EPNUserList:
|
|||||||
dict(
|
dict(
|
||||||
uid=str(entry["uid"].pop(0)),
|
uid=str(entry["uid"].pop(0)),
|
||||||
cn=str(entry["cn"].pop(0)),
|
cn=str(entry["cn"].pop(0)),
|
||||||
|
givenname=str(entry.get("givenname", "")),
|
||||||
|
sn=str(entry["sn"].pop(0)),
|
||||||
krbpasswordexpiration=str(
|
krbpasswordexpiration=str(
|
||||||
entry["krbpasswordexpiration"].pop(0)
|
entry["krbpasswordexpiration"].pop(0)
|
||||||
),
|
),
|
||||||
@ -190,6 +199,8 @@ class EPN(admintool.AdminTool):
|
|||||||
self._ldap_data = []
|
self._ldap_data = []
|
||||||
self._date_ranges = []
|
self._date_ranges = []
|
||||||
self._mailer = None
|
self._mailer = None
|
||||||
|
self.env = None
|
||||||
|
self.default_email_domain = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_options(cls, parser):
|
def add_options(cls, parser):
|
||||||
@ -241,6 +252,7 @@ class EPN(admintool.AdminTool):
|
|||||||
self._validate_configuration()
|
self._validate_configuration()
|
||||||
self._parse_configuration()
|
self._parse_configuration()
|
||||||
self._get_connection()
|
self._get_connection()
|
||||||
|
self._read_ipa_configuration()
|
||||||
drop_privileges()
|
drop_privileges()
|
||||||
if self.options.to_nbdays:
|
if self.options.to_nbdays:
|
||||||
self._build_cli_date_ranges()
|
self._build_cli_date_ranges()
|
||||||
@ -258,6 +270,8 @@ class EPN(admintool.AdminTool):
|
|||||||
smtp_username=api.env.smtp_user,
|
smtp_username=api.env.smtp_user,
|
||||||
smtp_password=api.env.smtp_password,
|
smtp_password=api.env.smtp_password,
|
||||||
x_mailer=self.command_name,
|
x_mailer=self.command_name,
|
||||||
|
msg_subtype=api.env.msg_subtype,
|
||||||
|
msg_charset=api.env.msg_charset,
|
||||||
)
|
)
|
||||||
self._send_emails()
|
self._send_emails()
|
||||||
|
|
||||||
@ -352,6 +366,17 @@ class EPN(admintool.AdminTool):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
loader = FileSystemLoader(os.path.join(api.env.confdir, 'epn'))
|
||||||
|
self.env = Environment(loader=loader)
|
||||||
|
|
||||||
|
def _read_ipa_configuration(self):
|
||||||
|
"""Get the IPA configuration"""
|
||||||
|
api.Backend.rpcclient.connect()
|
||||||
|
result = api.Command.config_show()['result']
|
||||||
|
self.default_email_domain = result.get('ipadefaultemaildomain',
|
||||||
|
[None])[0]
|
||||||
|
api.Backend.rpcclient.disconnect()
|
||||||
|
|
||||||
def _get_connection(self):
|
def _get_connection(self):
|
||||||
"""Create a connection to LDAP and bind to it.
|
"""Create a connection to LDAP and bind to it.
|
||||||
"""
|
"""
|
||||||
@ -389,7 +414,8 @@ class EPN(admintool.AdminTool):
|
|||||||
)
|
)
|
||||||
|
|
||||||
search_base = DN(api.env.container_user, api.env.basedn)
|
search_base = DN(api.env.container_user, api.env.basedn)
|
||||||
attrs_list = ["uid", "krbpasswordexpiration", "mail", "cn"]
|
attrs_list = ["uid", "krbpasswordexpiration", "mail", "cn",
|
||||||
|
"gn", "surname"]
|
||||||
|
|
||||||
search_filter = (
|
search_filter = (
|
||||||
"(&(!(nsaccountlock=TRUE)) \
|
"(&(!(nsaccountlock=TRUE)) \
|
||||||
@ -439,18 +465,29 @@ class EPN(admintool.AdminTool):
|
|||||||
logger.error("IPA-EPN: mailer was not configured.")
|
logger.error("IPA-EPN: mailer was not configured.")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
|
template = self.env.get_template("expire_msg.template")
|
||||||
|
except TemplateSyntaxError as e:
|
||||||
|
raise RuntimeError("Parsing template %s failed: %s" %
|
||||||
|
(e.filename, e))
|
||||||
|
if api.env.mail_from:
|
||||||
|
mail_from = api.env.mail_from
|
||||||
|
else:
|
||||||
|
mail_from = "noreply@%s" % self.default_email_domain
|
||||||
while self._expiring_password_user_list:
|
while self._expiring_password_user_list:
|
||||||
entry = self._expiring_password_user_list.pop()
|
entry = self._expiring_password_user_list.pop()
|
||||||
|
body = template.render(
|
||||||
|
uid=entry["uid"],
|
||||||
|
first=entry["givenname"],
|
||||||
|
last=entry["sn"],
|
||||||
|
fullname=entry["cn"],
|
||||||
|
expiration=entry["krbpasswordexpiration"],
|
||||||
|
)
|
||||||
self._mailer.send_message(
|
self._mailer.send_message(
|
||||||
mail_subject="Your password is expiring.",
|
mail_subject=api.env.msg_subject,
|
||||||
mail_body=os.linesep.join(
|
mail_body=body,
|
||||||
[
|
|
||||||
"Hi %s, Your password will expire on %s."
|
|
||||||
% (entry["cn"], entry["krbpasswordexpiration"]),
|
|
||||||
"Please change it as soon as possible.",
|
|
||||||
]
|
|
||||||
),
|
|
||||||
subscribers=ast.literal_eval(entry["mail"]),
|
subscribers=ast.literal_eval(entry["mail"]),
|
||||||
|
mail_from=mail_from,
|
||||||
)
|
)
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
expdate = datetime.strptime(
|
expdate = datetime.strptime(
|
||||||
@ -524,6 +561,7 @@ class MTAClient:
|
|||||||
self._disconnect()
|
self._disconnect()
|
||||||
|
|
||||||
def send_message(self, message_str=None, subscribers=None):
|
def send_message(self, message_str=None, subscribers=None):
|
||||||
|
result = None
|
||||||
try:
|
try:
|
||||||
result = self._conn.sendmail(
|
result = self._conn.sendmail(
|
||||||
api.env.smtp_admin, subscribers, message_str,
|
api.env.smtp_admin, subscribers, message_str,
|
||||||
@ -640,18 +678,17 @@ class MailUserAgent:
|
|||||||
smtp_username=None,
|
smtp_username=None,
|
||||||
smtp_password=None,
|
smtp_password=None,
|
||||||
x_mailer=None,
|
x_mailer=None,
|
||||||
|
msg_subtype="plain",
|
||||||
|
msg_charset="utf8",
|
||||||
):
|
):
|
||||||
|
|
||||||
self._x_mailer = x_mailer
|
self._x_mailer = x_mailer
|
||||||
self._subject = None
|
self._subject = None
|
||||||
self._body = None
|
self._body = None
|
||||||
self._subscribers = None
|
self._subscribers = None
|
||||||
self._subtype = None
|
|
||||||
|
|
||||||
# Let it be plain or html?
|
self._subtype = msg_subtype
|
||||||
self._subtype = "plain"
|
self._charset = msg_charset
|
||||||
# UTF-8 only for now
|
|
||||||
self._charset = "utf-8"
|
|
||||||
|
|
||||||
self._msg = None
|
self._msg = None
|
||||||
self._message_str = None
|
self._message_str = None
|
||||||
@ -669,18 +706,20 @@ class MailUserAgent:
|
|||||||
self._mta_client.cleanup()
|
self._mta_client.cleanup()
|
||||||
|
|
||||||
def send_message(
|
def send_message(
|
||||||
self, mail_subject=None, mail_body=None, subscribers=None
|
self, mail_subject=None, mail_body=None, subscribers=None,
|
||||||
|
mail_from=None
|
||||||
):
|
):
|
||||||
"""Given mail_subject, mail_body, and subscribers, composes
|
"""Given mail_subject, mail_body, and subscribers, composes
|
||||||
the message and sends it.
|
the message and sends it.
|
||||||
"""
|
"""
|
||||||
if None in [mail_subject, mail_body, subscribers]:
|
if None in [mail_subject, mail_body, subscribers, mail_from]:
|
||||||
logger.error("IPA-EPN: Tried to send an empty message.")
|
logger.error("IPA-EPN: Tried to send an empty message.")
|
||||||
return False
|
return False
|
||||||
self._compose_message(
|
self._compose_message(
|
||||||
mail_subject=mail_subject,
|
mail_subject=mail_subject,
|
||||||
mail_body=mail_body,
|
mail_body=mail_body,
|
||||||
subscribers=subscribers,
|
subscribers=subscribers,
|
||||||
|
mail_from=mail_from,
|
||||||
)
|
)
|
||||||
self._mta_client.send_message(
|
self._mta_client.send_message(
|
||||||
message_str=self._message_str, subscribers=subscribers
|
message_str=self._message_str, subscribers=subscribers
|
||||||
@ -688,7 +727,7 @@ class MailUserAgent:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _compose_message(
|
def _compose_message(
|
||||||
self, mail_subject=None, mail_body=None, subscribers=None
|
self, mail_subject, mail_body, subscribers, mail_from
|
||||||
):
|
):
|
||||||
"""The composer creates a MIME multipart message.
|
"""The composer creates a MIME multipart message.
|
||||||
"""
|
"""
|
||||||
@ -698,12 +737,11 @@ class MailUserAgent:
|
|||||||
self._subscribers = subscribers
|
self._subscribers = subscribers
|
||||||
|
|
||||||
self._msg = MIMEMultipart(_charset=self._charset)
|
self._msg = MIMEMultipart(_charset=self._charset)
|
||||||
self._msg["From"] = formataddr(
|
self._msg["From"] = formataddr(("IPA-EPN", mail_from))
|
||||||
("IPA-EPN", "noreply@%s" % socket.getfqdn())
|
|
||||||
)
|
|
||||||
self._msg["To"] = ", ".join(self._subscribers)
|
self._msg["To"] = ", ".join(self._subscribers)
|
||||||
self._msg["Date"] = formatdate(localtime=True)
|
self._msg["Date"] = formatdate(localtime=True)
|
||||||
self._msg["Subject"] = Header(self._subject, self._charset)
|
self._msg["Subject"] = Header(self._subject, self._charset)
|
||||||
|
self._msg["Message-Id"] = make_msgid()
|
||||||
self._msg.preamble = "Multipart message"
|
self._msg.preamble = "Multipart message"
|
||||||
if "X-Mailer" not in self._msg and self._x_mailer:
|
if "X-Mailer" not in self._msg and self._x_mailer:
|
||||||
self._msg.add_header("X-Mailer", self._x_mailer)
|
self._msg.add_header("X-Mailer", self._x_mailer)
|
||||||
|
Loading…
Reference in New Issue
Block a user