From 321b7ad5aea6cccd41a4b88e0ff5096dd6b6f7fa Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Wed, 17 Oct 2018 00:01:48 -0400 Subject: [PATCH] Add custom validator for multiple emails in a single text field. Change the widget of the email fields in the mailer app to avoid browser side email validation. Closes GitLab issue #530. Thanks to Mark Maglana @relaxdiego for the report. Signed-off-by: Roberto Rosario --- HISTORY.rst | 4 + mayan/apps/mailer/forms.py | 12 +- mayan/apps/mailer/tests/literals.py | 12 +- mayan/apps/mailer/tests/mixins.py | 16 +++ mayan/apps/mailer/tests/test_models.py | 26 ++-- mayan/apps/mailer/tests/test_views.py | 168 ++++++++++++++++++++++--- mayan/apps/mailer/validators.py | 18 +++ 7 files changed, 217 insertions(+), 39 deletions(-) create mode 100644 mayan/apps/mailer/tests/mixins.py create mode 100644 mayan/apps/mailer/validators.py diff --git a/HISTORY.rst b/HISTORY.rst index 0fa3d4e6db..015dde8fc4 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -8,6 +8,10 @@ * Add improvements to the metadata URL encoding and decoding to support ampersand characters as part of the metadata value. GitLab issue #529. Thanks to Mark Maglana @relaxdiego for the report. +* Add custom validator for multiple emails in a single text field. + Change the widget of the email fields in the mailer app to avoid + browser side email validation. Closes GitLab issue #530. + Thanks to Mark Maglana @relaxdiego for the report. 3.1.7 (2018-10-14) ================== diff --git a/mayan/apps/mailer/forms.py b/mayan/apps/mailer/forms.py index eed1bb51d5..2372a7a91e 100644 --- a/mayan/apps/mailer/forms.py +++ b/mayan/apps/mailer/forms.py @@ -18,6 +18,7 @@ from .settings import ( setting_document_body_template, setting_document_subject_template, setting_link_body_template, setting_link_subject_template ) +from .validators import validate_email_multiple class DocumentMailForm(forms.Form): @@ -56,11 +57,11 @@ class DocumentMailForm(forms.Form): except UserMailer.DoesNotExist: pass - email = forms.EmailField( + email = forms.CharField( help_text=_( 'Email address of the recipient. Can be multiple addresses ' 'separated by comma or semicolon.' - ), label=_('Email address') + ), label=_('Email address'), validators=[validate_email_multiple] ) subject = forms.CharField(label=_('Subject'), required=False) body = forms.CharField( @@ -117,4 +118,9 @@ class UserMailerDynamicForm(DynamicModelForm): class UserMailerTestForm(forms.Form): - email = forms.EmailField(label=_('Email address')) + email = forms.CharField( + help_text=_( + 'Email address of the recipient. Can be multiple addresses ' + 'separated by comma or semicolon.' + ), label=_('Email address'), validators=[validate_email_multiple] + ) diff --git a/mayan/apps/mailer/tests/literals.py b/mayan/apps/mailer/tests/literals.py index 4801e5365c..b61fc6103a 100644 --- a/mayan/apps/mailer/tests/literals.py +++ b/mayan/apps/mailer/tests/literals.py @@ -3,10 +3,16 @@ from __future__ import unicode_literals TEST_BODY_HTML = 'test body' TEST_EMAIL_ADDRESS = 'test@example.com' TEST_RECIPIENTS_MULTIPLE_COMMA = 'test@example.com,test2@example.com' +TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT = [ + 'test@example.com', 'test2@example.com' +] TEST_RECIPIENTS_MULTIPLE_SEMICOLON = 'test@example.com;test2@example.com' +TEST_RECIPIENTS_MULTIPLE_SEMICOLON_RESULT = [ + 'test@example.com', 'test2@example.com' +] TEST_RECIPIENTS_MULTIPLE_MIXED = 'test@example.com,test2@example.com;test2@example.com' -TEST_RECIPIENTS_MULTIPLE_MIXED_LIST = ( - 'test@example.com', 'test2@example.com', 'test2@example.com', -) +TEST_RECIPIENTS_MULTIPLE_MIXED_RESULT = [ + 'test@example.com', 'test2@example.com', 'test2@example.com' +] TEST_USER_MAILER_BACKEND_PATH = 'mailer.tests.mailers.TestBackend' TEST_USER_MAILER_LABEL = 'test user mailer label' diff --git a/mayan/apps/mailer/tests/mixins.py b/mayan/apps/mailer/tests/mixins.py new file mode 100644 index 0000000000..c6cd6bc7b3 --- /dev/null +++ b/mayan/apps/mailer/tests/mixins.py @@ -0,0 +1,16 @@ +from __future__ import unicode_literals + +from ..models import UserMailer + +from .literals import TEST_USER_MAILER_BACKEND_PATH, TEST_USER_MAILER_LABEL + + +class MailerTestMixin(object): + def _create_user_mailer(self): + self.user_mailer = UserMailer.objects.create( + default=True, + enabled=True, + label=TEST_USER_MAILER_LABEL, + backend_path=TEST_USER_MAILER_BACKEND_PATH, + backend_data='{}' + ) diff --git a/mayan/apps/mailer/tests/test_models.py b/mayan/apps/mailer/tests/test_models.py index 56b952a937..56fa8ef9f6 100644 --- a/mayan/apps/mailer/tests/test_models.py +++ b/mayan/apps/mailer/tests/test_models.py @@ -4,26 +4,16 @@ from django.core import mail from documents.tests.test_models import GenericDocumentTestCase -from ..models import UserMailer - from .literals import ( TEST_BODY_HTML, TEST_EMAIL_ADDRESS, TEST_RECIPIENTS_MULTIPLE_COMMA, - TEST_RECIPIENTS_MULTIPLE_SEMICOLON, TEST_RECIPIENTS_MULTIPLE_MIXED, - TEST_RECIPIENTS_MULTIPLE_MIXED_LIST, TEST_USER_MAILER_LABEL, - TEST_USER_MAILER_BACKEND_PATH + TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT, TEST_RECIPIENTS_MULTIPLE_SEMICOLON, + TEST_RECIPIENTS_MULTIPLE_SEMICOLON_RESULT, TEST_RECIPIENTS_MULTIPLE_MIXED, + TEST_RECIPIENTS_MULTIPLE_MIXED_RESULT, ) +from .mixins import MailerTestMixin -class ModelTestCase(GenericDocumentTestCase): - def _create_user_mailer(self): - self.user_mailer = UserMailer.objects.create( - default=True, - enabled=True, - label=TEST_USER_MAILER_LABEL, - backend_path=TEST_USER_MAILER_BACKEND_PATH, - backend_data='{}' - ) - +class ModelTestCase(MailerTestMixin, GenericDocumentTestCase): def test_send_simple(self): self._create_user_mailer() self.user_mailer.send(to=TEST_EMAIL_ADDRESS) @@ -61,7 +51,7 @@ class ModelTestCase(GenericDocumentTestCase): self.assertEqual(len(mail.outbox), 1) self.assertEqual( - mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_COMMA.split(',') + mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT ) def test_send_multiple_recipients_semicolon(self): @@ -70,7 +60,7 @@ class ModelTestCase(GenericDocumentTestCase): self.assertEqual(len(mail.outbox), 1) self.assertEqual( - mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_SEMICOLON.split(';') + mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_SEMICOLON_RESULT ) def test_send_multiple_recipient_mixed(self): @@ -79,5 +69,5 @@ class ModelTestCase(GenericDocumentTestCase): self.assertEqual(len(mail.outbox), 1) self.assertEqual( - list(mail.outbox[0].to), list(TEST_RECIPIENTS_MULTIPLE_MIXED_LIST) + mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_MIXED_RESULT ) diff --git a/mayan/apps/mailer/tests/test_views.py b/mayan/apps/mailer/tests/test_views.py index 870a22faa5..9bd4dd8ce8 100644 --- a/mayan/apps/mailer/tests/test_views.py +++ b/mayan/apps/mailer/tests/test_views.py @@ -12,20 +12,14 @@ from ..permissions import ( ) from .literals import ( - TEST_EMAIL_ADDRESS, TEST_USER_MAILER_BACKEND_PATH, TEST_USER_MAILER_LABEL + TEST_EMAIL_ADDRESS, TEST_USER_MAILER_BACKEND_PATH, TEST_USER_MAILER_LABEL, + TEST_RECIPIENTS_MULTIPLE_COMMA, TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT, + TEST_RECIPIENTS_MULTIPLE_MIXED, TEST_RECIPIENTS_MULTIPLE_MIXED_RESULT, + TEST_RECIPIENTS_MULTIPLE_SEMICOLON, + TEST_RECIPIENTS_MULTIPLE_SEMICOLON_RESULT ) from .mailers import TestBackend - - -class MailerTestMixin(object): - def _create_user_mailer(self): - self.user_mailer = UserMailer.objects.create( - default=True, - enabled=True, - label=TEST_USER_MAILER_LABEL, - backend_path=TEST_USER_MAILER_BACKEND_PATH, - backend_data='{}' - ) +from .mixins import MailerTestMixin class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): @@ -33,7 +27,9 @@ class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): return self.post( 'mailer:send_document_link', args=(self.document.pk,), data={ - 'email': TEST_EMAIL_ADDRESS, + 'email': getattr( + self, 'test_email_address', TEST_EMAIL_ADDRESS + ), 'user_mailer': self.user_mailer.pk }, ) @@ -42,7 +38,9 @@ class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): return self.post( 'mailer:send_document', args=(self.document.pk,), data={ - 'email': TEST_EMAIL_ADDRESS, + 'email': getattr( + self, 'test_email_address', TEST_EMAIL_ADDRESS + ), 'user_mailer': self.user_mailer.pk }, ) @@ -66,7 +64,9 @@ class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): def _request_user_mailer_test(self): return self.post( 'mailer:user_mailer_test', args=(self.user_mailer.pk,), data={ - 'email': TEST_EMAIL_ADDRESS + 'email': getattr( + self, 'test_email_address', TEST_EMAIL_ADDRESS + ) }, follow=True ) @@ -207,3 +207,141 @@ class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS]) + + def test_send_multiple_recipients_comma(self): + self._create_user_mailer() + self.login_user() + + self.grant_access( + obj=self.user_mailer, permission=permission_user_mailer_use + ) + + self.test_email_address = TEST_RECIPIENTS_MULTIPLE_COMMA + response = self._request_user_mailer_test() + self.assertEqual(response.status_code, 200) + self.assertEqual(len(mail.outbox), 1) + self.assertEqual( + mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT + ) + + def test_send_multiple_recipients_mixed(self): + self._create_user_mailer() + self.login_user() + + self.grant_access( + obj=self.user_mailer, permission=permission_user_mailer_use + ) + + self.test_email_address = TEST_RECIPIENTS_MULTIPLE_MIXED + response = self._request_user_mailer_test() + self.assertEqual(response.status_code, 200) + self.assertEqual(len(mail.outbox), 1) + self.assertEqual( + mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_MIXED_RESULT + ) + + def test_send_multiple_recipients_semicolon(self): + self._create_user_mailer() + self.login_user() + + self.grant_access( + obj=self.user_mailer, permission=permission_user_mailer_use + ) + + self.test_email_address = TEST_RECIPIENTS_MULTIPLE_SEMICOLON + response = self._request_user_mailer_test() + self.assertEqual(response.status_code, 200) + self.assertEqual(len(mail.outbox), 1) + self.assertEqual( + mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_SEMICOLON_RESULT + ) + + def test_mail_link_view_recipients_comma(self): + self._create_user_mailer() + self.login_user() + + self.grant_permission(permission=permission_mailing_link) + self.grant_permission(permission=permission_user_mailer_use) + + self.test_email_address = TEST_RECIPIENTS_MULTIPLE_COMMA + self._request_document_link_send() + + self.assertEqual(len(mail.outbox), 1) + self.assertEqual( + mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT + ) + + def test_mail_link_view_recipients_mixed(self): + self._create_user_mailer() + self.login_user() + + self.grant_permission(permission=permission_mailing_link) + self.grant_permission(permission=permission_user_mailer_use) + + self.test_email_address = TEST_RECIPIENTS_MULTIPLE_MIXED + self._request_document_link_send() + + self.assertEqual(len(mail.outbox), 1) + self.assertEqual( + mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_MIXED_RESULT + ) + + def test_mail_link_view_recipients_semicolon(self): + self._create_user_mailer() + self.login_user() + + self.grant_permission(permission=permission_mailing_link) + self.grant_permission(permission=permission_user_mailer_use) + + self.test_email_address = TEST_RECIPIENTS_MULTIPLE_SEMICOLON + self._request_document_link_send() + + self.assertEqual(len(mail.outbox), 1) + self.assertEqual( + mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_SEMICOLON_RESULT + ) + + def test_mail_document_view_recipients_comma(self): + self._create_user_mailer() + self.login_user() + + self.grant_permission(permission=permission_mailing_send_document) + self.grant_permission(permission=permission_user_mailer_use) + + self.test_email_address = TEST_RECIPIENTS_MULTIPLE_COMMA + self._request_document_send() + + self.assertEqual(len(mail.outbox), 1) + self.assertEqual( + mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT + ) + + def test_mail_document_view_recipients_mixed(self): + self._create_user_mailer() + self.login_user() + + self.grant_permission(permission=permission_mailing_send_document) + self.grant_permission(permission=permission_user_mailer_use) + + self.test_email_address = TEST_RECIPIENTS_MULTIPLE_MIXED + self._request_document_send() + + self.assertEqual(len(mail.outbox), 1) + self.assertEqual( + mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_MIXED_RESULT + ) + + def test_mail_document_view_recipients_semicolon(self): + self._create_user_mailer() + self.login_user() + + self.grant_permission(permission=permission_mailing_send_document) + self.grant_permission(permission=permission_user_mailer_use) + + self.test_email_address = TEST_RECIPIENTS_MULTIPLE_SEMICOLON + self._request_document_send() + + self.assertEqual(len(mail.outbox), 1) + self.assertEqual( + mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_SEMICOLON_RESULT + ) diff --git a/mayan/apps/mailer/validators.py b/mayan/apps/mailer/validators.py new file mode 100644 index 0000000000..49c79d80d3 --- /dev/null +++ b/mayan/apps/mailer/validators.py @@ -0,0 +1,18 @@ +from __future__ import unicode_literals + +from django.core import validators +from django.utils.translation import ugettext_lazy as _ + +from .utils import split_recipient_list + + +def validate_email_multiple(value): + recipient_list = split_recipient_list(recipients=[value]) + + for recipient in recipient_list: + validate_email = validators.EmailValidator( + message=_('%(email)s is not a valid email address.') % { + 'email': recipient + } + ) + validate_email(value=recipient)