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
|
2017-04-02 16:01:21 +02:00
|
|
|
|
import hashlib
|
2016-03-29 14:16:00 +02:00
|
|
|
|
import tempfile
|
|
|
|
|
import uuid
|
|
|
|
|
import smtplib
|
2016-04-09 15:49:16 +02:00
|
|
|
|
import subprocess
|
2016-07-16 09:00:02 +02:00
|
|
|
|
import traceback
|
2016-03-29 14:16:00 +02:00
|
|
|
|
|
2017-04-17 16:22:55 +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')
|
2016-06-02 11:12:57 +02:00
|
|
|
|
TECH_MANAGER_ROLE_ID = os.environ.get('TECH_MANAGER_ROLE_ID', 10)
|
2016-04-09 15:49:16 +02:00
|
|
|
|
FALLBACK_EMAIL = os.environ.get('FALLBACK_EMAIL', 'support@entrouvert.com')
|
2017-04-02 16:01:21 +02:00
|
|
|
|
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):
|
2022-01-17 15:18:22 +01:00
|
|
|
|
if not header_text:
|
|
|
|
|
return ''
|
2016-03-29 14:16:00 +02:00
|
|
|
|
default_charset = 'ascii'
|
|
|
|
|
try:
|
2022-01-17 15:18:22 +01:00
|
|
|
|
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:
|
2022-01-17 15:18:22 +01:00
|
|
|
|
res = ''
|
2016-03-29 14:16:00 +02:00
|
|
|
|
for i, (text, charset) in enumerate(headers):
|
2022-01-17 15:18:22 +01:00
|
|
|
|
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
|
|
|
|
|
2017-04-02 16:01:21 +02:00
|
|
|
|
def parse_attachment(data, blacklist=None):
|
2016-09-02 11:07:18 +02:00
|
|
|
|
if data.get('Content-type', '').startswith('multipart/'):
|
2016-09-02 09:57:50 +02:00
|
|
|
|
return None
|
2016-09-02 11:07:18 +02:00
|
|
|
|
if data.get('Content-type', '').startswith('text/plain') and not (
|
2016-09-02 09:57:50 +02:00
|
|
|
|
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
|
2017-04-02 16:01:21 +02:00
|
|
|
|
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()
|
2022-01-17 15:18:22 +01:00
|
|
|
|
|
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')
|
2016-06-02 12:25:20 +02:00
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
|
2016-04-14 17:25:10 +02:00
|
|
|
|
def create_ticket(mail):
|
|
|
|
|
mail = email.message_from_string(mail)
|
2016-04-09 15:49:16 +02:00
|
|
|
|
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
|
|
|
|
|
2017-04-02 16:01:21 +02:00
|
|
|
|
blacklist = {'sha1sums': []}
|
|
|
|
|
if ATTACHMENTS_BLACKLIST:
|
|
|
|
|
try:
|
|
|
|
|
blacklist = json.load(open(ATTACHMENTS_BLACKLIST))
|
|
|
|
|
except (IOError, ValueError):
|
|
|
|
|
pass
|
|
|
|
|
|
2016-04-09 15:49:16 +02:00
|
|
|
|
r = Redmine(REDMINE_URL, key=REDMINE_KEY, impersonate=users[0].login)
|
2016-03-29 14:16:00 +02:00
|
|
|
|
|
2016-04-09 15:49:16 +02:00
|
|
|
|
attachments = []
|
2016-03-29 14:16:00 +02:00
|
|
|
|
|
2016-09-02 09:40:04 +02:00
|
|
|
|
body = u''
|
2016-04-09 15:49:16 +02:00
|
|
|
|
for data in mail.walk():
|
2017-04-02 16:01:21 +02:00
|
|
|
|
attachment = parse_attachment(data, blacklist)
|
2016-04-09 15:49:16 +02:00
|
|
|
|
if attachment:
|
|
|
|
|
attachments.append(attachment)
|
|
|
|
|
elif data.get_content_type() == "text/plain":
|
2022-01-17 15:18:22 +01:00
|
|
|
|
body = data.get_payload()
|
2016-03-29 14:16:00 +02:00
|
|
|
|
|
2016-06-02 11:12:57 +02:00
|
|
|
|
# get project tech manager
|
|
|
|
|
tech_manager = None
|
|
|
|
|
for membership in r.project_membership.filter(project_id=PROJECT_ID):
|
|
|
|
|
try:
|
2016-06-06 11:11:50 +02:00
|
|
|
|
if membership.roles.get(TECH_MANAGER_ROLE_ID):
|
|
|
|
|
tech_manager = membership.user
|
2016-06-02 11:12:57 +02:00
|
|
|
|
break
|
2017-04-17 16:22:55 +02:00
|
|
|
|
except redminelib.resources.ResourceAttrError:
|
2016-06-02 11:12:57 +02:00
|
|
|
|
continue
|
|
|
|
|
|
2016-06-10 15:59:20 +02:00
|
|
|
|
issue_data = {'project_id': PROJECT_ID,
|
|
|
|
|
'subject': parse_header(mail['Subject']),
|
|
|
|
|
'tracker_id': TRACKER_ID,
|
|
|
|
|
'description': body,
|
|
|
|
|
'uploads': attachments}
|
2016-06-02 11:12:57 +02:00
|
|
|
|
if tech_manager:
|
2016-06-21 14:13:25 +02:00
|
|
|
|
issue_data['assigned_to_id'] = tech_manager.id
|
2016-06-10 15:59:20 +02:00
|
|
|
|
|
|
|
|
|
issue = r.issue.create(**issue_data)
|
2016-06-02 11:12:57 +02:00
|
|
|
|
|
2016-06-02 12:25:20 +02:00
|
|
|
|
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
|
|
|
|
|
2016-04-09 15:49:16 +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
|
|
|
|
|
2016-04-09 15:49:16 +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"""
|
2016-04-09 15:49:16 +02:00
|
|
|
|
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__':
|
2016-05-04 01:00:28 +02:00
|
|
|
|
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)
|
2016-08-24 16:39:07 +02:00
|
|
|
|
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:
|
2016-05-04 01:00:28 +02:00
|
|
|
|
mail_dump.write(mail)
|
2016-04-09 15:49:16 +02:00
|
|
|
|
try:
|
2016-05-04 01:00:28 +02:00
|
|
|
|
create_ticket(mail)
|
2016-07-16 09:00:02 +02:00
|
|
|
|
except Exception as e:
|
2016-04-09 15:49:16 +02:00
|
|
|
|
exim = subprocess.Popen(['/usr/sbin/exim', FALLBACK_EMAIL], stdin=file(filename))
|
2016-07-16 09:00:02 +02:00
|
|
|
|
trace = traceback.format_exc()
|
2016-08-24 16:39:07 +02:00
|
|
|
|
filename = os.path.join(mail_dumps_dir, '%s.trace' % file_id)
|
2016-07-16 09:00:02 +02:00
|
|
|
|
with open(filename, 'w') as mail_dump:
|
|
|
|
|
mail_dump.write(trace)
|