mail2redmine/mail2redmine.py

165 lines
5.1 KiB
Python
Raw Permalink Normal View History

2016-03-29 14:16:00 +02:00
# -*- encoding: utf-8 -*-
2017-04-03 09:44:24 +02:00
import json
2016-03-29 14:16:00 +02:00
import os
import sys
import email
import hashlib
2016-03-29 14:16:00 +02:00
import tempfile
import uuid
import smtplib
import subprocess
import traceback
2016-03-29 14:16:00 +02:00
import redminelib
from redminelib import Redmine
2016-03-29 14:16:00 +02:00
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)
2016-03-29 14:16:00 +02:00
class UnknownUser(Exception):
pass
def parse_header(header_text):
if not header_text:
return ''
2016-03-29 14:16:00 +02:00
default_charset = 'ascii'
try:
headers = email.header.decode_header(header_text)
except email.errors.HeaderParseError:
2016-03-29 14:16:00 +02:00
return header_text.encode(default_charset, 'replace').decode(default_charset)
else:
res = ''
2016-03-29 14:16:00 +02:00
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 ''
2016-03-29 14:16:00 +02:00
2016-08-09 22:08:32 +02:00
def parse_attachment(data, blacklist=None):
2016-09-02 11:07:18 +02:00
if data.get('Content-type', '').startswith('multipart/'):
return None
2016-09-02 11:07:18 +02:00
if data.get('Content-type', '').startswith('text/plain') and not (
data.get('Content-Disposition', '').startswith('attachment')):
return None
2016-08-09 22:08:32 +02:00
file_data = data.get_payload(decode=True)
2016-09-02 09:18:54 +02:00
if not file_data:
return None
if blacklist and hashlib.sha1(file_data).hexdigest() in blacklist.get('sha1sums'):
return None
2016-08-09 22:08:32 +02:00
with tempfile.NamedTemporaryFile(delete=False) as attachment:
attachment.write(file_data)
attachment.flush()
2016-08-09 22:08:32 +02:00
attachment = {'path': attachment.name,
'content_type': data.get_content_type(),
'filename': parse_header(data.get_filename())}
return attachment
2016-03-29 14:16:00 +02:00
def send_mail(to, subject, message):
s = smtplib.SMTP('localhost')
from_mail = 'noreply@entrouvert.com'
2016-05-04 01:04:48 +02:00
msg = email.mime.Text.MIMEText(message, 'plain', 'utf-8')
2016-03-29 14:16:00 +02:00
msg['From'] = from_mail
msg['To'] = to
msg['Subject'] = subject
2016-05-04 01:04:48 +02:00
s.sendmail(from_mail, to, msg.as_string())
2016-03-29 14:16:00 +02:00
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
2016-03-29 14:16:00 +02:00
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)
2016-03-29 14:16:00 +02:00
attachments = []
2016-03-29 14:16:00 +02:00
2016-09-02 09:40:04 +02:00
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()
2016-03-29 14:16:00 +02:00
# 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:
2016-06-21 14:13:25 +02:00
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,
2016-03-29 14:16:00 +02:00
Votre demande a bien été prise en compte et enregistrée dans notre système sous
le numéro %s.
2016-03-29 14:16:00 +02:00
Vous pouvez suivre son évolution à l'adresse : %s .
2016-03-29 14:16:00 +02:00
Cordialement,
2016-04-06 16:42:40 +02:00
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'])
2016-03-29 14:16:00 +02:00
if __name__ == '__main__':
mail = sys.stdin.read()
2016-03-29 14:44:18 +02:00
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)
2016-03-29 14:16:00 +02:00
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)