Files

181 lines
6.3 KiB
Python

"""
notify.py — Email notifications for NetAdmin
"""
import datetime
import email.utils
import logging
import quopri
import socket
from email.header import Header
from email.message import Message
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from typing import Optional
from config import cfg
log = logging.getLogger(__name__)
def _make_qp_part(content: str, subtype: str) -> Message:
"""Build a text/plain or text/html MIME part using quoted-printable encoding.
MIMEText with charset='utf-8' defaults to base64.
We encode manually as QP so mail clients display it as plain text.
"""
encoded = quopri.encodestring(content.encode("utf-8"), quotetabs=False)
part = Message()
part["Content-Type"] = f"text/{subtype}; charset=utf-8"
part["Content-Transfer-Encoding"] = "quoted-printable"
part.set_payload(encoded.decode("ascii"))
return part
def _send(to_addr: str, subject: str, body_text: str, body_html: Optional[str] = None):
"""Send an email with correct headers and QP encoding."""
s = cfg.smtp
if not s.enabled or not to_addr:
return
try:
msg = MIMEMultipart("alternative")
# RFC-required headers
msg["Message-ID"] = email.utils.make_msgid(domain=s.from_addr.split("@")[-1])
msg["Date"] = email.utils.formatdate(localtime=True)
msg["Subject"] = Header(subject, "utf-8").encode()
msg["From"] = email.utils.formataddr((s.from_name, s.from_addr))
msg["To"] = to_addr
msg["X-Mailer"] = "NetAdmin"
# Attach parts with quoted-printable (readable in all clients)
msg.attach(_make_qp_part(body_text, "plain"))
if body_html:
msg.attach(_make_qp_part(body_html, "html"))
if s.use_ssl:
server = __import__("smtplib").SMTP_SSL(s.host, s.port, timeout=10)
else:
server = __import__("smtplib").SMTP(s.host, s.port, timeout=10)
if s.use_tls:
server.starttls()
if s.username and s.password:
server.login(s.username, s.password)
server.sendmail(s.from_addr, [to_addr], msg.as_string())
server.quit()
log.info(f"[notify] Email sent to {to_addr}: {subject}")
except Exception as e:
log.warning(f"[notify] Failed to send email to {to_addr}: {e}")
def notify_login(username: str, email_addr: Optional[str], ip: str):
if not cfg.smtp.notify_login or not email_addr:
return
hostname = socket.gethostname()
now = datetime.datetime.now().strftime("%d/%m/%Y à %H:%M")
subject = "Connexion à votre compte NetAdmin"
text = (
f"Bonjour {username},\n\n"
f"Une connexion a été détectée sur votre compte NetAdmin.\n\n"
f" Date : {now}\n"
f" Adresse IP : {ip or 'inconnue'}\n"
f" Serveur : {hostname}\n\n"
f"Si vous n'êtes pas à l'origine de cette connexion, "
f"contactez immédiatement votre administrateur.\n\n"
f"-- NetAdmin"
)
html = f"""\
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body style="font-family:Arial,sans-serif;color:#333;max-width:520px;margin:0 auto">
<h2 style="color:#0d7bc4;border-bottom:2px solid #0d7bc4;padding-bottom:8px">
Connexion à votre compte NetAdmin
</h2>
<p>Bonjour <strong>{username}</strong>,</p>
<p>Une connexion a été détectée sur votre compte NetAdmin.</p>
<table style="border-collapse:collapse;margin:16px 0;width:100%">
<tr style="background:#f5f5f5">
<td style="padding:8px 12px;font-weight:bold;width:120px">Date</td>
<td style="padding:8px 12px">{now}</td>
</tr>
<tr>
<td style="padding:8px 12px;font-weight:bold">Adresse IP</td>
<td style="padding:8px 12px;font-family:monospace">{ip or 'inconnue'}</td>
</tr>
<tr style="background:#f5f5f5">
<td style="padding:8px 12px;font-weight:bold">Serveur</td>
<td style="padding:8px 12px">{hostname}</td>
</tr>
</table>
<p style="color:#c00;font-weight:bold">
Si vous n'êtes pas à l'origine de cette connexion,
contactez immédiatement votre administrateur.
</p>
<hr style="border:none;border-top:1px solid #ddd;margin:24px 0">
<p style="color:#999;font-size:11px">NetAdmin — notification automatique</p>
</body>
</html>"""
_send(email_addr, subject, text, html)
def notify_role_assigned(
target_username: str,
target_email: Optional[str],
role: str,
scopes: list,
assigned_by: str,
):
if not cfg.smtp.notify_role_added or not target_email:
return
scope_str = ", ".join(str(s) for s in scopes if s) if scopes and scopes != [None] else "global"
subject = f"NetAdmin — Nouveau rôle assigné : {role}"
text = (
f"Bonjour {target_username},\n\n"
f"Un nouveau rôle vous a été assigné sur NetAdmin.\n\n"
f" Rôle : {role}\n"
f" Périmètre : {scope_str}\n"
f" Assigné par : {assigned_by}\n\n"
f"Connectez-vous à NetAdmin pour consulter vos permissions.\n\n"
f"-- NetAdmin"
)
html = f"""\
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body style="font-family:Arial,sans-serif;color:#333;max-width:520px;margin:0 auto">
<h2 style="color:#0d7bc4;border-bottom:2px solid #0d7bc4;padding-bottom:8px">
Nouveau rôle NetAdmin
</h2>
<p>Bonjour <strong>{target_username}</strong>,</p>
<p>Un nouveau rôle vous a été assigné.</p>
<table style="border-collapse:collapse;margin:16px 0;width:100%">
<tr style="background:#f5f5f5">
<td style="padding:8px 12px;font-weight:bold;width:140px">Rôle</td>
<td style="padding:8px 12px;font-weight:bold;color:#0d7bc4">{role}</td>
</tr>
<tr>
<td style="padding:8px 12px;font-weight:bold">Périmètre</td>
<td style="padding:8px 12px;font-family:monospace">{scope_str}</td>
</tr>
<tr style="background:#f5f5f5">
<td style="padding:8px 12px;font-weight:bold">Assigné par</td>
<td style="padding:8px 12px">{assigned_by}</td>
</tr>
</table>
<hr style="border:none;border-top:1px solid #ddd;margin:24px 0">
<p style="color:#999;font-size:11px">NetAdmin — notification automatique</p>
</body>
</html>"""
_send(target_email, subject, text, html)