Show More
@@ -39,9 +39,14 b' Recipients will see these emails origina' | |||||
39 | ``app_email_from`` setting in the configuration file. This setting can either |
|
39 | ``app_email_from`` setting in the configuration file. This setting can either | |
40 | contain only an email address, like `kallithea-noreply@example.com`, or both |
|
40 | contain only an email address, like `kallithea-noreply@example.com`, or both | |
41 | a name and an address in the following format: `Kallithea |
|
41 | a name and an address in the following format: `Kallithea | |
42 |
<kallithea-noreply@example.com>`. |
|
42 | <kallithea-noreply@example.com>`. However, if the email is sent due to an | |
43 | optionally be prefixed with the value of ``email_prefix`` in the configuration |
|
43 | action of a particular user, for example when a comment is given or a pull | |
44 | file. |
|
44 | request created, the name of that user will be combined with the email address | |
|
45 | specified in ``app_email_from`` to form the sender (and any name part in that | |||
|
46 | configuration setting disregarded). | |||
|
47 | ||||
|
48 | The subject of these emails can optionally be prefixed with the value of | |||
|
49 | ``email_prefix`` in the configuration file. | |||
45 |
|
50 | |||
46 |
|
51 | |||
47 | Error emails |
|
52 | Error emails |
@@ -31,6 +31,7 b' from celery.decorators import task' | |||||
31 | import os |
|
31 | import os | |
32 | import traceback |
|
32 | import traceback | |
33 | import logging |
|
33 | import logging | |
|
34 | import rfc822 | |||
34 | from os.path import join as jn |
|
35 | from os.path import join as jn | |
35 |
|
36 | |||
36 | from time import mktime |
|
37 | from time import mktime | |
@@ -45,6 +46,7 b' from kallithea.lib.celerylib import run_' | |||||
45 | from kallithea.lib.helpers import person |
|
46 | from kallithea.lib.helpers import person | |
46 | from kallithea.lib.rcmail.smtp_mailer import SmtpMailer |
|
47 | from kallithea.lib.rcmail.smtp_mailer import SmtpMailer | |
47 | from kallithea.lib.utils import add_cache, action_logger |
|
48 | from kallithea.lib.utils import add_cache, action_logger | |
|
49 | from kallithea.lib.vcs.utils import author_email | |||
48 | from kallithea.lib.compat import json, OrderedDict |
|
50 | from kallithea.lib.compat import json, OrderedDict | |
49 | from kallithea.lib.hooks import log_create_repository |
|
51 | from kallithea.lib.hooks import log_create_repository | |
50 |
|
52 | |||
@@ -247,7 +249,7 b' def get_commits_stats(repo_name, ts_min_' | |||||
247 |
|
249 | |||
248 | @task(ignore_result=True) |
|
250 | @task(ignore_result=True) | |
249 | @dbsession |
|
251 | @dbsession | |
250 | def send_email(recipients, subject, body='', html_body='', headers=None): |
|
252 | def send_email(recipients, subject, body='', html_body='', headers=None, author=None): | |
251 | """ |
|
253 | """ | |
252 | Sends an email with defined parameters from the .ini files. |
|
254 | Sends an email with defined parameters from the .ini files. | |
253 |
|
255 | |||
@@ -256,9 +258,16 b' def send_email(recipients, subject, body' | |||||
256 | :param subject: subject of the mail |
|
258 | :param subject: subject of the mail | |
257 | :param body: body of the mail |
|
259 | :param body: body of the mail | |
258 | :param html_body: html version of body |
|
260 | :param html_body: html version of body | |
|
261 | :param headers: dictionary of prepopulated e-mail headers | |||
|
262 | :param author: User object of the author of this mail, if known and relevant | |||
259 | """ |
|
263 | """ | |
260 | log = get_logger(send_email) |
|
264 | log = get_logger(send_email) | |
261 | assert isinstance(recipients, list), recipients |
|
265 | assert isinstance(recipients, list), recipients | |
|
266 | if headers is None: | |||
|
267 | headers = {} | |||
|
268 | else: | |||
|
269 | # do not modify the original headers object passed by the caller | |||
|
270 | headers = headers.copy() | |||
262 |
|
271 | |||
263 | email_config = config |
|
272 | email_config = config | |
264 | email_prefix = email_config.get('email_prefix', '') |
|
273 | email_prefix = email_config.get('email_prefix', '') | |
@@ -280,7 +289,18 b' def send_email(recipients, subject, body' | |||||
280 |
|
289 | |||
281 | log.warning("No recipients specified for '%s' - sending to admins %s", subject, ' '.join(recipients)) |
|
290 | log.warning("No recipients specified for '%s' - sending to admins %s", subject, ' '.join(recipients)) | |
282 |
|
291 | |||
283 | mail_from = email_config.get('app_email_from', 'Kallithea') |
|
292 | # SMTP sender | |
|
293 | envelope_from = email_config.get('app_email_from', 'Kallithea') | |||
|
294 | # 'From' header | |||
|
295 | if author is not None: | |||
|
296 | # set From header based on author but with a generic e-mail address | |||
|
297 | # In case app_email_from is in "Some Name <e-mail>" format, we first | |||
|
298 | # extract the e-mail address. | |||
|
299 | envelope_addr = author_email(envelope_from) | |||
|
300 | headers['From'] = '"%s" <%s>' % ( | |||
|
301 | rfc822.quote('%s (no-reply)' % author.full_name_or_username), | |||
|
302 | envelope_addr) | |||
|
303 | ||||
284 | user = email_config.get('smtp_username') |
|
304 | user = email_config.get('smtp_username') | |
285 | passwd = email_config.get('smtp_password') |
|
305 | passwd = email_config.get('smtp_password') | |
286 | mail_server = email_config.get('smtp_server') |
|
306 | mail_server = email_config.get('smtp_server') | |
@@ -306,7 +326,7 b' def send_email(recipients, subject, body' | |||||
306 | return False |
|
326 | return False | |
307 |
|
327 | |||
308 | try: |
|
328 | try: | |
309 |
m = SmtpMailer( |
|
329 | m = SmtpMailer(envelope_from, user, passwd, mail_server, smtp_auth, | |
310 | mail_port, ssl, tls, debug=debug) |
|
330 | mail_port, ssl, tls, debug=debug) | |
311 | m.send(recipients, subject, body, html_body, headers=headers) |
|
331 | m.send(recipients, subject, body, html_body, headers=headers) | |
312 | except: |
|
332 | except: |
@@ -145,7 +145,7 b' class NotificationModel(BaseModel):' | |||||
145 | .get_email_tmpl(type_, 'html', **html_kwargs) |
|
145 | .get_email_tmpl(type_, 'html', **html_kwargs) | |
146 |
|
146 | |||
147 | run_task(tasks.send_email, [rec.email], email_subject, email_txt_body, |
|
147 | run_task(tasks.send_email, [rec.email], email_subject, email_txt_body, | |
148 | email_html_body, headers) |
|
148 | email_html_body, headers, author=created_by_obj) | |
149 |
|
149 | |||
150 | return notif |
|
150 | return notif | |
151 |
|
151 |
@@ -2,6 +2,7 b' import mock' | |||||
2 |
|
2 | |||
3 | import kallithea |
|
3 | import kallithea | |
4 | from kallithea.tests import * |
|
4 | from kallithea.tests import * | |
|
5 | from kallithea.model.db import User | |||
5 |
|
6 | |||
6 | class smtplib_mock(object): |
|
7 | class smtplib_mock(object): | |
7 |
|
8 | |||
@@ -89,3 +90,78 b' class TestMail(BaseTestCase):' | |||||
89 | self.assertIn('Subject: %s' % subject, smtplib_mock.lastmsg) |
|
90 | self.assertIn('Subject: %s' % subject, smtplib_mock.lastmsg) | |
90 | self.assertIn(body, smtplib_mock.lastmsg) |
|
91 | self.assertIn(body, smtplib_mock.lastmsg) | |
91 | self.assertIn(html_body, smtplib_mock.lastmsg) |
|
92 | self.assertIn(html_body, smtplib_mock.lastmsg) | |
|
93 | ||||
|
94 | def test_send_mail_with_author(self): | |||
|
95 | mailserver = 'smtp.mailserver.org' | |||
|
96 | recipients = ['rcpt1', 'rcpt2'] | |||
|
97 | envelope_from = 'noreply@mailserver.org' | |||
|
98 | subject = 'subject' | |||
|
99 | body = 'body' | |||
|
100 | html_body = 'html_body' | |||
|
101 | author = User.get_by_username(TEST_USER_REGULAR_LOGIN) | |||
|
102 | ||||
|
103 | config_mock = { | |||
|
104 | 'smtp_server': mailserver, | |||
|
105 | 'app_email_from': envelope_from, | |||
|
106 | } | |||
|
107 | with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock): | |||
|
108 | kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body, author=author) | |||
|
109 | ||||
|
110 | self.assertSetEqual(smtplib_mock.lastdest, set(recipients)) | |||
|
111 | self.assertEqual(smtplib_mock.lastsender, envelope_from) | |||
|
112 | self.assertIn('From: "Kallithea Admin (no-reply)" <%s>' % envelope_from, smtplib_mock.lastmsg) | |||
|
113 | self.assertIn('Subject: %s' % subject, smtplib_mock.lastmsg) | |||
|
114 | self.assertIn(body, smtplib_mock.lastmsg) | |||
|
115 | self.assertIn(html_body, smtplib_mock.lastmsg) | |||
|
116 | ||||
|
117 | def test_send_mail_with_author_full_mail_from(self): | |||
|
118 | mailserver = 'smtp.mailserver.org' | |||
|
119 | recipients = ['rcpt1', 'rcpt2'] | |||
|
120 | envelope_addr = 'noreply@mailserver.org' | |||
|
121 | envelope_from = 'Some Name <%s>' % envelope_addr | |||
|
122 | subject = 'subject' | |||
|
123 | body = 'body' | |||
|
124 | html_body = 'html_body' | |||
|
125 | author = User.get_by_username(TEST_USER_REGULAR_LOGIN) | |||
|
126 | ||||
|
127 | config_mock = { | |||
|
128 | 'smtp_server': mailserver, | |||
|
129 | 'app_email_from': envelope_from, | |||
|
130 | } | |||
|
131 | with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock): | |||
|
132 | kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body, author=author) | |||
|
133 | ||||
|
134 | self.assertSetEqual(smtplib_mock.lastdest, set(recipients)) | |||
|
135 | self.assertEqual(smtplib_mock.lastsender, envelope_from) | |||
|
136 | self.assertIn('From: "Kallithea Admin (no-reply)" <%s>' % envelope_addr, smtplib_mock.lastmsg) | |||
|
137 | self.assertIn('Subject: %s' % subject, smtplib_mock.lastmsg) | |||
|
138 | self.assertIn(body, smtplib_mock.lastmsg) | |||
|
139 | self.assertIn(html_body, smtplib_mock.lastmsg) | |||
|
140 | ||||
|
141 | def test_send_mail_extra_headers(self): | |||
|
142 | mailserver = 'smtp.mailserver.org' | |||
|
143 | recipients = ['rcpt1', 'rcpt2'] | |||
|
144 | envelope_from = 'noreply@mailserver.org' | |||
|
145 | subject = 'subject' | |||
|
146 | body = 'body' | |||
|
147 | html_body = 'html_body' | |||
|
148 | author = User(name='foo', lastname='(fubar) "baz"') | |||
|
149 | headers = {'extra': 'yes'} | |||
|
150 | ||||
|
151 | config_mock = { | |||
|
152 | 'smtp_server': mailserver, | |||
|
153 | 'app_email_from': envelope_from, | |||
|
154 | } | |||
|
155 | with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock): | |||
|
156 | kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body, | |||
|
157 | author=author, headers=headers) | |||
|
158 | ||||
|
159 | self.assertSetEqual(smtplib_mock.lastdest, set(recipients)) | |||
|
160 | self.assertEqual(smtplib_mock.lastsender, envelope_from) | |||
|
161 | self.assertIn(r'From: "foo (fubar) \"baz\" (no-reply)" <%s>' % envelope_from, smtplib_mock.lastmsg) | |||
|
162 | self.assertIn('Subject: %s' % subject, smtplib_mock.lastmsg) | |||
|
163 | self.assertIn(body, smtplib_mock.lastmsg) | |||
|
164 | self.assertIn(html_body, smtplib_mock.lastmsg) | |||
|
165 | self.assertIn('Extra: yes', smtplib_mock.lastmsg) | |||
|
166 | # verify that headers dict hasn't mutated by send_email | |||
|
167 | self.assertDictEqual(headers, {'extra': 'yes'}) |
General Comments 0
You need to be logged in to leave comments.
Login now