Resout le probleme de firewall SMTP corporate qui bloque outbound 25/465/587 depuis
les postes de travail. Outlook utilise HTTPS (EWS/Graph) vers O365 -> aucun firewall.
Service mail_outlook_com.py:
- send_via_outlook_com(to, subject, html, cc, display_only): pilote Outlook via COM
pywin32. Mail apparait dans Sent Items du user, traçable.
- Mode display_only=True ouvre la fenetre de composition Outlook au lieu d'envoyer
automatiquement (relecture manuelle).
- pythoncom.CoInitialize() pour fonctionner dans un thread uvicorn.
Service mail_service.py:
- send_html_mail dispatche selon Setting 'mail_backend' (smtp / outlook_com).
- Defaut = smtp.
Settings:
- 'mail_backend' = 'smtp' | 'outlook_com'
- 'mail_outlook_display_only' = 'true' | 'false'
- UI: 2 selects en haut de la card SMTP avec hints + le SMTP existant en dessous
Pre-requis Windows: pip install pywin32 + Outlook lance + connecte au compte O365.
- Endpoint POST /settings/smtp/test (Form 'recipient', defaut kalid.moutaouakil@gmail.com)
envoie un mail HTML pro confirmant que SMTP fonctionne (header bleu degrade,
metadonnees envoyeur/date/destinataire en card)
- UI Settings > SMTP: champ destinataire test pre-rempli + bouton 'Envoyer test'
AJAX sans reload, status vert/rouge en dessous
- Reuse send_html_mail du mail_service
- Service mail_service.py: send_html_mail via SMTP standard (host/port/user/pass/from/use_tls
depuis Settings > SMTP). Gere SSL_465 et STARTTLS_587. Mode dry_run pour preview.
- Settings: nouvelle section 'smtp' avec smtp_host/port/user/pass/from/use_tls/pct_recipient
(a configurer pour O365 SMTP submission)
- Router planning_import.py:
* _build_pct_email(): construit subject + HTML pro/colore (header bleu degrade SANEF,
cards avec border-left bleu/orange, tableau serveurs, footer)
* Subject: 'Intervention sur <app>' si app uniforme, sinon liste des serveurs
* Plage horaire = 20 min × N serveurs (formattee Hh MM)
* 'Moyen d'exploitation prevu : Rollback en cas de probleme' ajoute en bas
* _fetch_pct_cc_emails(): query distinct contacts depuis responsable_domaine_contact_id
+ referent_technique_contact_id + server_additional_referents
* Endpoint POST /patching/import/pct-prevenance/preview retourne {subject, html, to, cc,
smtp_configured, row_count} sans envoyer
* Endpoint POST /patching/import/pct-prevenance/send envoie reellement, audit log,
update pct_mail_sent_at sur les rows
- Template patching_import.html:
* Bouton 'Prevenance PCT' (violet) a cote des autres actions
* Modal preview avec iframe sandboxe pour le rendu HTML mail
* Affiche destinataires, CC, objet, count serveurs
* Warning rouge si SMTP non configure (envoi desactive, preview seulement)
* 2 boutons: Annuler / Envoyer (avec confirmation)
- Migration v2: ajoute teams_channel_rules.match_is_database_server (NULL=any/TRUE=DB only/FALSE=non-DB only),
servers.is_database_server boolean (default false), table server_additional_referents pour multi-referents
- Service teams_service.py: resolve_channel_for_server -> resolve_channels_for_server (pluriel, retourne LIST)
* msg_type=reboot: 1 seul canal (canal flagge is_reboot_channel)
* server.teams_channel_id override: 1 seul canal
* sinon FAN-OUT: TOUTES les regles actives qui matchent contribuent un canal
* dynamic_to_email calcule selon nature de la regle (responsable / DBA pool / referents)
- send_notification: boucle sur les canaux resultants, ecrit 1 fichier SP par destination
- UI settings.html: nouveau filtre 'Match serveur DB' dans formulaire regle, badge dans tableau
- Compat: resolve_channel_for_server (singulier) conserve comme wrapper qui retourne le 1er canal
Permet le scenario: serveur DB avec responsable=Delcour -> notif a la fois dans conv Delcour
ET dans conv DBA (Nadine+Cedric), via 2 regles qui matchent en parallele.
- Migration: ajoute sp_route/mode/is_reboot_channel/is_dynamic_dm sur teams_channels,
cree table teams_channel_rules (match resp/domain/env/msg_type/hostname pattern)
- Service teams_service.py: format texte plat compatible workflows existants,
write_sharepoint_notification (ecrit fichier .txt dans <sp_base>/<sp_route>/),
resolve_channel_for_server rules-based avec priorite reboot,
send_notification orchestre resolution + envoi
- Settings UI: CRUD canaux etendu (mode SP/webhook + flags reboot/dyn_dm),
CRUD regles avec match conditions, sharepoint_notif_path en secret app,
bouton Test ecrit fichier .txt en mode SP
- Mode is_dynamic_dm: prefixe le contenu par 'TO: <email>' pour permettre
un workflow PA unique qui dispatch dynamiquement aux responsables
- Pas d'OAuth requis: PatchCenter ecrit fichiers, Workflows PA cote SharePoint
(deja en place pour le .exe) declenchent et postent sur Teams
Mode webhook conserve mais inactif tant qu'OAuth Entra ID pas mis en place chez SANEF
- Settings ldap_required_group (DN groupe autorise) + ldap_default_role
- ldap_authenticate verifie memberOf vs required_group avant bind
- auth.py: si user inconnu + LDAP + groupe OK -> auto-create user, role default,
zero permission (admin doit assigner via /users)
- auth: verification is_active au login (compte desactive = bloque)
- settings: enforcement backend can_edit(settings) + role/section
- servers: can_view/can_edit(servers) sur toutes les routes
- planning: can_view/can_edit(planning) sur toutes les routes
- specifics: can_view/can_edit(specifics) sur toutes les routes
- contacts: rattache au module servers (can_view/can_edit)
- campaigns: can_view/can_edit(campaigns) sur toutes les routes manquantes
- audit/audit_full: can_view/can_edit(audit) sur toutes les routes
- qualys: can_view/can_edit(qualys) sur toutes les routes
- safe_patching: perm checks + authentification sur SSE stream
- quickwin: can_view/can_edit(campaigns|quickwin) sur toutes les routes
97 points d'injection securises, 0 route sans controle
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Nginx: headers HSTS/X-Frame/nosniff/CSP, rate limit login 5r/m
- CSP: self only, unsafe-inline (Tailwind JIT), object-src none, pas de CDN externe
- Assets locaux: Tailwind/HTMX/Alpine.js téléchargés dans /static/js/
- ACL réseau: table allowed_networks administrable depuis Settings
- Fichier /etc/nginx/patchcenter_acl.conf régénéré auto depuis la base
- PostgreSQL: logs connexion/déconnexion, requêtes lentes >1s, max 50 conn
- REVOKE CREATE pour user patchcenter, role readonly créé
- SSH: clé only, 3 tentatives, pas de TCP forwarding
- Backup toutes les 30min, rétention 3 jours
- Application 100% hors ligne (aucune dépendance internet côté navigateur)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>