mail2redmine/mail2redmine.py

165 lines
5.1 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- encoding: utf-8 -*-
import json
import os
import sys
import email
import hashlib
import tempfile
import uuid
import smtplib
import subprocess
import traceback
import redminelib
from redminelib import Redmine
REDMINE_URL = os.environ.get('REDMINE_URL', 'https://dev.entrouvert.org')
REDMINE_KEY = os.environ.get('REDMINE_KEY')
TRACKER_ID = os.environ.get('REDMINE_TRACKER', '3')
PROJECT_ID = os.environ.get('PROJECT')
TECH_MANAGER_ROLE_ID = os.environ.get('TECH_MANAGER_ROLE_ID', 10)
FALLBACK_EMAIL = os.environ.get('FALLBACK_EMAIL', 'support@entrouvert.com')
ATTACHMENTS_BLACKLIST = os.environ.get('ATTACHMENTS_BLACKLIST', None)
class UnknownUser(Exception):
pass
def parse_header(header_text):
if not header_text:
return ''
default_charset = 'ascii'
try:
headers = email.header.decode_header(header_text)
except email.errors.HeaderParseError:
return header_text.encode(default_charset, 'replace').decode(default_charset)
else:
res = ''
for i, (text, charset) in enumerate(headers):
if isinstance(text, str):
res += text
else:
res += text.decode(charset or default_charset, errors='replace')
return res
return ''
def parse_attachment(data, blacklist=None):
if data.get('Content-type', '').startswith('multipart/'):
return None
if data.get('Content-type', '').startswith('text/plain') and not (
data.get('Content-Disposition', '').startswith('attachment')):
return None
file_data = data.get_payload(decode=True)
if not file_data:
return None
if blacklist and hashlib.sha1(file_data).hexdigest() in blacklist.get('sha1sums'):
return None
with tempfile.NamedTemporaryFile(delete=False) as attachment:
attachment.write(file_data)
attachment.flush()
attachment = {'path': attachment.name,
'content_type': data.get_content_type(),
'filename': parse_header(data.get_filename())}
return attachment
def send_mail(to, subject, message):
s = smtplib.SMTP('localhost')
from_mail = 'noreply@entrouvert.com'
msg = email.mime.Text.MIMEText(message, 'plain', 'utf-8')
msg['From'] = from_mail
msg['To'] = to
msg['Subject'] = subject
s.sendmail(from_mail, to, msg.as_string())
s.quit()
def create_ticket(mail):
mail = email.message_from_string(mail)
r = Redmine(REDMINE_URL, key=REDMINE_KEY)
from_name, from_email = email.utils.parseaddr(mail['From'])
users = r.user.filter(name=from_email)
if not users:
raise UnknownUser
blacklist = {'sha1sums': []}
if ATTACHMENTS_BLACKLIST:
try:
blacklist = json.load(open(ATTACHMENTS_BLACKLIST))
except (IOError, ValueError):
pass
r = Redmine(REDMINE_URL, key=REDMINE_KEY, impersonate=users[0].login)
attachments = []
body = u''
for data in mail.walk():
attachment = parse_attachment(data, blacklist)
if attachment:
attachments.append(attachment)
elif data.get_content_type() == "text/plain":
body = data.get_payload()
# get project tech manager
tech_manager = None
for membership in r.project_membership.filter(project_id=PROJECT_ID):
try:
if membership.roles.get(TECH_MANAGER_ROLE_ID):
tech_manager = membership.user
break
except redminelib.resources.ResourceAttrError:
continue
issue_data = {'project_id': PROJECT_ID,
'subject': parse_header(mail['Subject']),
'tracker_id': TRACKER_ID,
'description': body,
'uploads': attachments}
if tech_manager:
issue_data['assigned_to_id'] = tech_manager.id
issue = r.issue.create(**issue_data)
message = u"""[ Ce courriel est envoyé par un automate. Merci de ne pas y répondre, votre message ne serait pas pris en compte. ]
Bonjour,
Votre demande a bien été prise en compte et enregistrée dans notre système sous
le numéro %s.
Vous pouvez suivre son évolution à l'adresse : %s .
Cordialement,
L'équipe Entr'ouvert"""
message = message % (issue.id, issue.url)
send_mail(mail['From'], u'Votre demande a été bien prise en compte', message)
# cleanup temporary attachment files
for attachment in attachments:
if os.path.exists(attachment['path']):
os.unlink(attachment['path'])
if __name__ == '__main__':
mail = sys.stdin.read()
mail_dumps_dir = '/var/tmp'
if not os.path.exists(mail_dumps_dir):
os.mkdir(mail_dumps_dir)
file_id = uuid.uuid4()
filename = os.path.join(mail_dumps_dir, '%s.mail' % file_id)
with open(filename, 'w') as mail_dump:
mail_dump.write(mail)
try:
create_ticket(mail)
except Exception as e:
exim = subprocess.Popen(['/usr/sbin/exim', FALLBACK_EMAIL], stdin=file(filename))
trace = traceback.format_exc()
filename = os.path.join(mail_dumps_dir, '%s.trace' % file_id)
with open(filename, 'w') as mail_dump:
mail_dump.write(trace)