From 103cb481bc46e9dc8c5ca047b460265b5d68ee32 Mon Sep 17 00:00:00 2001 From: Bruno Wagner Date: Fri, 5 Jun 2015 19:36:08 -0300 Subject: Added creation of input mail from python message That way we don't need extra logic for the welcome mail, we just have to read the file and send the contents to the input mail parser and that's it. Also moved the logic of adding a welcome mail to the mailboxes because it has knowledge of mailbox methods anyways. --- service/pixelated/adapter/model/mail.py | 44 +++++++++++++++----- .../pixelated/adapter/services/draft_service.py | 7 +--- service/pixelated/adapter/services/mail_service.py | 4 +- service/pixelated/adapter/services/mailboxes.py | 14 ++++++- service/pixelated/application.py | 2 +- service/pixelated/config/app_factory.py | 4 +- service/pixelated/config/initialize_leap.py | 47 --------------------- service/pixelated/config/leap.py | 48 ++++++++++++++++++++++ service/pixelated/config/welcome_mail.py | 42 ------------------- service/pixelated/maintenance.py | 2 +- service/pixelated/resources/mails_resource.py | 2 +- service/test/integration/test_contacts.py | 2 +- service/test/integration/test_tags.py | 2 +- service/test/integration/test_welcome_mail.py | 15 ++++--- .../test/support/integration/app_test_client.py | 2 +- service/test/unit/adapter/test_draft_service.py | 2 +- service/test/unit/adapter/test_mail_service.py | 6 +-- service/test/unit/adapter/test_mailboxes.py | 1 + service/test/unit/config/test_welcome_mail.py | 32 --------------- 19 files changed, 117 insertions(+), 161 deletions(-) delete mode 100644 service/pixelated/config/initialize_leap.py create mode 100644 service/pixelated/config/leap.py delete mode 100644 service/pixelated/config/welcome_mail.py delete mode 100644 service/test/unit/config/test_welcome_mail.py (limited to 'service') diff --git a/service/pixelated/adapter/model/mail.py b/service/pixelated/adapter/model/mail.py index 99ff0297..7c4223de 100644 --- a/service/pixelated/adapter/model/mail.py +++ b/service/pixelated/adapter/model/mail.py @@ -14,20 +14,21 @@ # You should have received a copy of the GNU Affero General Public License # along with Pixelated. If not, see . import json +import os +import re +import logging +import dateutil.parser as dateparser from uuid import uuid4 +from email import message_from_file from email.mime.text import MIMEText from email.header import decode_header - +from email.MIMEMultipart import MIMEMultipart +from pycryptopp.hash import sha256 from leap.mail.imap.fields import fields import leap.mail.walk as walk -import dateutil.parser as dateparser from pixelated.adapter.model.status import Status -import pixelated.support.date -from email.MIMEMultipart import MIMEMultipart -from pycryptopp.hash import sha256 -import re +from pixelated.support import date from pixelated.support.functional import compact -import logging logger = logging.getLogger(__name__) @@ -207,7 +208,7 @@ class InputMail(Mail): input_mail.headers = {key.capitalize(): value for key, value in mail_dict.get('header', {}).items()} # XXX this is overriding the property in PixelatedMail - input_mail.headers['Date'] = pixelated.support.date.iso_now() + input_mail.headers['Date'] = date.iso_now() # XXX this is overriding the property in PixelatedMail input_mail.body = mail_dict.get('body', '') @@ -218,6 +219,20 @@ class InputMail(Mail): input_mail._status = set(mail_dict.get('status', [])) return input_mail + @staticmethod + def from_python_mail(mail): + input_mail = InputMail() + input_mail.headers = {key.capitalize(): value for key, value in mail.items()} + input_mail.headers['Date'] = date.iso_now() + input_mail.headers['Subject'] = mail['Subject'] + input_mail.headers['To'] = InputMail.FROM_EMAIL_ADDRESS + input_mail._mime = MIMEMultipart() + for payload in mail.get_payload(): + input_mail._mime.attach(payload) + if payload.get_content_type() == 'text/plain': + input_mail.body = payload.as_string() + return input_mail + class PixelatedMail(Mail): @@ -305,7 +320,7 @@ class PixelatedMail(Mail): try: _headers['Date'] = self._get_date() except Exception: - _headers['Date'] = pixelated.support.date.iso_now() + _headers['Date'] = date.iso_now() if self.parts and len(self.parts['alternatives']) > 1: _headers['content_type'] = 'multipart/alternative; boundary="%s"' % self.boundary @@ -341,10 +356,10 @@ class PixelatedMail(Mail): else: # we can't get a date for this mail, so lets just use now logger.warning('Encountered a mail with missing date and received header fields. ID %s' % self.fdoc.content.get('uid', None)) - date = pixelated.support.date.iso_now() + date = date.iso_now() return dateparser.parse(date).isoformat() except (ValueError, TypeError): - date = pixelated.support.date.iso_now() + date = date.iso_now() return dateparser.parse(date).isoformat() @property @@ -487,3 +502,10 @@ class PixelatedMail(Mail): dict_mail['replying']['all']['to-field'] = recipients dict_mail['replying']['all']['cc-field'] = ccs return dict_mail + + +def welcome_mail(): + current_path = os.path.dirname(os.path.abspath(__file__)) + with open(os.path.join(current_path, '..', '..', 'assets', 'welcome.mail')) as mail_template_file: + mail_template = message_from_file(mail_template_file) + return InputMail.from_python_mail(mail_template) diff --git a/service/pixelated/adapter/services/draft_service.py b/service/pixelated/adapter/services/draft_service.py index df295eec..c8df0a05 100644 --- a/service/pixelated/adapter/services/draft_service.py +++ b/service/pixelated/adapter/services/draft_service.py @@ -22,13 +22,10 @@ class DraftService(object): self._mailboxes = mailboxes def create_draft(self, input_mail): - pixelated_mail = self._drafts().add(input_mail) + pixelated_mail = self._mailboxes.drafts.add(input_mail) return pixelated_mail def update_draft(self, ident, input_mail): pixelated_mail = self.create_draft(input_mail) - self._drafts().remove(ident) + self._mailboxes.drafts.remove(ident) return pixelated_mail - - def _drafts(self): - return self._mailboxes.drafts() diff --git a/service/pixelated/adapter/services/mail_service.py b/service/pixelated/adapter/services/mail_service.py index e392bc44..4e6b6aa8 100644 --- a/service/pixelated/adapter/services/mail_service.py +++ b/service/pixelated/adapter/services/mail_service.py @@ -76,8 +76,8 @@ class MailService(object): def move_to_sent(self, last_draft_ident, mail): if last_draft_ident: - self.mailboxes.drafts().remove(last_draft_ident) - return self.mailboxes.sent().add(mail) + self.mailboxes.drafts.remove(last_draft_ident) + return self.mailboxes.sent.add(mail) def mark_as_read(self, mail_id): mail = self.mail(mail_id) diff --git a/service/pixelated/adapter/services/mailboxes.py b/service/pixelated/adapter/services/mailboxes.py index e9fe6ce5..c2b61ca8 100644 --- a/service/pixelated/adapter/services/mailboxes.py +++ b/service/pixelated/adapter/services/mailboxes.py @@ -15,6 +15,7 @@ # along with Pixelated. If not, see . from pixelated.adapter.services.mailbox import Mailbox from pixelated.adapter.listeners.mailbox_indexer_listener import MailboxIndexerListener +from pixelated.adapter.model.mail import welcome_mail class Mailboxes(object): @@ -33,15 +34,19 @@ class Mailboxes(object): MailboxIndexerListener.listen(self.account, mailbox_name, self.querier) return Mailbox.create(mailbox_name, self.querier, self.search_engine) + @property def inbox(self): return self._create_or_get('INBOX') + @property def drafts(self): return self._create_or_get('DRAFTS') + @property def trash(self): return self._create_or_get('TRASH') + @property def sent(self): return self._create_or_get('SENT') @@ -49,10 +54,10 @@ class Mailboxes(object): return [self._create_or_get(leap_mailbox_name) for leap_mailbox_name in self.account.mailboxes] def move_to_trash(self, mail_id): - return self._move_to(mail_id, self.trash()) + return self._move_to(mail_id, self.trash) def move_to_inbox(self, mail_id): - return self._move_to(mail_id, self.inbox()) + return self._move_to(mail_id, self.inbox) def _move_to(self, mail_id, mailbox): mail = self.querier.mail(mail_id) @@ -62,3 +67,8 @@ class Mailboxes(object): def mail(self, mail_id): return self.querier.mail(mail_id) + + def add_welcome_mail_for_fresh_user(self): + if self.inbox.fresh: + mail = welcome_mail() + self.inbox.add(mail) diff --git a/service/pixelated/application.py b/service/pixelated/application.py index b63e10fb..55946a5e 100644 --- a/service/pixelated/application.py +++ b/service/pixelated/application.py @@ -26,7 +26,7 @@ from OpenSSL import crypto from pixelated.config import arguments from pixelated.resources import loading_page -from pixelated.config.initialize_leap import initialize_leap +from pixelated.config.leap import initialize_leap from pixelated.config import logger, app_factory diff --git a/service/pixelated/config/app_factory.py b/service/pixelated/config/app_factory.py index 477317e1..717c2bb9 100644 --- a/service/pixelated/config/app_factory.py +++ b/service/pixelated/config/app_factory.py @@ -23,7 +23,6 @@ from pixelated.adapter.soledad.soledad_querier import SoledadQuerier from pixelated.adapter.search import SearchEngine from pixelated.adapter.services.draft_service import DraftService from pixelated.adapter.listeners.mailbox_indexer_listener import MailboxIndexerListener -from .welcome_mail import check_welcome_mail def init_app(leap_home, leap_session): @@ -37,7 +36,8 @@ def init_app(leap_home, leap_session): lambda: leap_session.smtp.ensure_running()) pixelated_mailboxes = Mailboxes(leap_session.account, soledad_querier, search_engine) - check_welcome_mail(pixelated_mailboxes.inbox()) + + pixelated_mailboxes.add_welcome_mail_for_fresh_user() draft_service = DraftService(pixelated_mailboxes) mail_service = MailService(pixelated_mailboxes, pixelated_mail_sender, soledad_querier, search_engine) diff --git a/service/pixelated/config/initialize_leap.py b/service/pixelated/config/initialize_leap.py deleted file mode 100644 index 1f8c5655..00000000 --- a/service/pixelated/config/initialize_leap.py +++ /dev/null @@ -1,47 +0,0 @@ -from pixelated.config import credentials -from leap.common.events import server as events_server -import pixelated.bitmask_libraries.certs as certs -from pixelated.bitmask_libraries.session import open_leap_session - - -def initialize_leap(leap_provider_cert, - leap_provider_cert_fingerprint, - credentials_file, - organization_mode, - leap_home): - init_monkeypatches() - provider, user, password = credentials.read(organization_mode, credentials_file) - init_leap_cert(leap_provider_cert, leap_provider_cert_fingerprint) - events_server.ensure_server(port=8090) - leap_session = create_leap_session(provider, user, password, leap_home) - return leap_session - - -def create_leap_session(provider, username, password, leap_home): - leap_session = open_leap_session(username, - password, - provider, - leap_home) - leap_session.soledad_session.soledad.sync(defer_decryption=False) - leap_session.nicknym.generate_openpgp_key() - return leap_session - - -def init_leap_cert(leap_provider_cert, leap_provider_cert_fingerprint): - if leap_provider_cert_fingerprint is None: - certs.LEAP_CERT = leap_provider_cert or True - certs.LEAP_FINGERPRINT = None - else: - certs.LEAP_FINGERPRINT = leap_provider_cert_fingerprint - certs.LEAP_CERT = False - - -def init_monkeypatches(): - import pixelated.extensions.protobuf_socket - import pixelated.extensions.sqlcipher_wal - import pixelated.extensions.esmtp_sender_factory - import pixelated.extensions.incoming_decrypt_header - import pixelated.extensions.soledad_sync_exception - import pixelated.extensions.keymanager_fetch_key - import pixelated.extensions.requests_urllib3 - import pixelated.extensions.shared_db diff --git a/service/pixelated/config/leap.py b/service/pixelated/config/leap.py new file mode 100644 index 00000000..0ff6ea18 --- /dev/null +++ b/service/pixelated/config/leap.py @@ -0,0 +1,48 @@ +from __future__ import absolute_import +from pixelated.config import credentials +from leap.common.events import server as events_server +import pixelated.bitmask_libraries.certs as certs +from pixelated.bitmask_libraries.session import open_leap_session + + +def initialize_leap(leap_provider_cert, + leap_provider_cert_fingerprint, + credentials_file, + organization_mode, + leap_home): + init_monkeypatches() + provider, user, password = credentials.read(organization_mode, credentials_file) + init_leap_cert(leap_provider_cert, leap_provider_cert_fingerprint) + events_server.ensure_server(port=8090) + leap_session = create_leap_session(provider, user, password, leap_home) + return leap_session + + +def create_leap_session(provider, username, password, leap_home): + leap_session = open_leap_session(username, + password, + provider, + leap_home) + leap_session.soledad_session.soledad.sync(defer_decryption=False) + leap_session.nicknym.generate_openpgp_key() + return leap_session + + +def init_leap_cert(leap_provider_cert, leap_provider_cert_fingerprint): + if leap_provider_cert_fingerprint is None: + certs.LEAP_CERT = leap_provider_cert or True + certs.LEAP_FINGERPRINT = None + else: + certs.LEAP_FINGERPRINT = leap_provider_cert_fingerprint + certs.LEAP_CERT = False + + +def init_monkeypatches(): + import pixelated.extensions.protobuf_socket + import pixelated.extensions.sqlcipher_wal + import pixelated.extensions.esmtp_sender_factory + import pixelated.extensions.incoming_decrypt_header + import pixelated.extensions.soledad_sync_exception + import pixelated.extensions.keymanager_fetch_key + import pixelated.extensions.requests_urllib3 + import pixelated.extensions.shared_db diff --git a/service/pixelated/config/welcome_mail.py b/service/pixelated/config/welcome_mail.py deleted file mode 100644 index 5dcbf9bc..00000000 --- a/service/pixelated/config/welcome_mail.py +++ /dev/null @@ -1,42 +0,0 @@ -# -# Copyright (c) 2014 ThoughtWorks, Inc. -# -# Pixelated is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pixelated is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Pixelated. If not, see . -import os -from pixelated.adapter.model.mail import InputMail -from pixelated.support.date import iso_now -from email import message_from_file -from email.MIMEMultipart import MIMEMultipart - - -def check_welcome_mail(mailbox): - if mailbox.fresh: - welcome_mail = build_welcome_mail() - mailbox.add(welcome_mail) - - -def build_welcome_mail(): - current_path = os.path.dirname(os.path.abspath(__file__)) - with open(os.path.join(current_path, '..', 'assets', 'welcome.mail')) as mail_template_file: - mail_template = message_from_file(mail_template_file) - welcome_mail = InputMail() - welcome_mail.headers['To'] = InputMail.FROM_EMAIL_ADDRESS - welcome_mail.headers['Subject'] = mail_template['Subject'] - welcome_mail.headers['Date'] = iso_now() - welcome_mail._mime = MIMEMultipart() - for payload in mail_template.get_payload(): - welcome_mail._mime.attach(payload) - if payload.get_content_type() == 'text/plain': - welcome_mail.body = payload.as_string() - return welcome_mail diff --git a/service/pixelated/maintenance.py b/service/pixelated/maintenance.py index 756ad435..97504103 100644 --- a/service/pixelated/maintenance.py +++ b/service/pixelated/maintenance.py @@ -17,7 +17,7 @@ from mailbox import Maildir from twisted.internet import reactor, defer from twisted.internet.threads import deferToThread -from pixelated.config.initialize_leap import initialize_leap +from pixelated.config.leap import initialize_leap from pixelated.config import logger, arguments from leap.mail.imap.fields import WithMsgFields diff --git a/service/pixelated/resources/mails_resource.py b/service/pixelated/resources/mails_resource.py index 3822abd3..c4b578ba 100644 --- a/service/pixelated/resources/mails_resource.py +++ b/service/pixelated/resources/mails_resource.py @@ -73,7 +73,7 @@ class MailsResource(Resource): def on_error(event): delivery_error_mail = InputMail.delivery_error_template(delivery_address=event.content) - self._mail_service.mailboxes.inbox().add(delivery_error_mail) + self._mail_service.mailboxes.inbox.add(delivery_error_mail) register(signal=proto.SMTP_SEND_MESSAGE_ERROR, callback=on_error) diff --git a/service/test/integration/test_contacts.py b/service/test/integration/test_contacts.py index f9cde9e5..1d82b0d7 100644 --- a/service/test/integration/test_contacts.py +++ b/service/test/integration/test_contacts.py @@ -87,7 +87,7 @@ class ContactsTest(SoledadTestBase): self.add_mail_to_inbox(to_be_bounced) bounced_mail_template = MailBuilder().build_input_mail() - bounced_mail = self.mailboxes.inbox().add(bounced_mail_template) + bounced_mail = self.mailboxes.inbox.add(bounced_mail_template) bounced_mail.hdoc.content = self._bounced_mail_hdoc_content() bounced_mail.save() self.search_engine.index_mail(bounced_mail) diff --git a/service/test/integration/test_tags.py b/service/test/integration/test_tags.py index 976b6d96..168e035f 100644 --- a/service/test/integration/test_tags.py +++ b/service/test/integration/test_tags.py @@ -86,5 +86,5 @@ class TagsTest(SoledadTestBase): response = self.post_tags(mail.ident, self._tags_json([tag.name.upper()])) self.assertEquals("None of the following words can be used as tags: %s" % tag.name, response) - mail = self.mailboxes.inbox().mail(mail.ident) + mail = self.mailboxes.inbox.mail(mail.ident) self.assertNotIn('drafts', mail.tags) diff --git a/service/test/integration/test_welcome_mail.py b/service/test/integration/test_welcome_mail.py index ed37f50e..a5ca555a 100644 --- a/service/test/integration/test_welcome_mail.py +++ b/service/test/integration/test_welcome_mail.py @@ -15,21 +15,20 @@ # along with Pixelated. If not, see . from test.support.integration import SoledadTestBase -from pixelated.config.welcome_mail import check_welcome_mail class TestWelcomeMail(SoledadTestBase): - def test_that_a_fresh_INBOX_will_receive_a_welcome_mail_only_once(self): - inbox = self.mailboxes.inbox() - check_welcome_mail(inbox) # adds a mail - check_welcome_mail(inbox) # should not repeat - + def test_welcome_mail_is_added_only_once(self): + self.mailboxes.add_welcome_mail_for_fresh_user() + self.mailboxes.add_welcome_mail_for_fresh_user() inbox_mails = self.get_mails_by_tag('inbox') self.assertEquals(1, len(inbox_mails)) + def test_empty_mailbox_doesnt_mean_fresh_mailbox(self): + self.mailboxes.add_welcome_mail_for_fresh_user() + inbox_mails = self.get_mails_by_tag('inbox') self.delete_mail(inbox_mails[0].ident) - check_welcome_mail(inbox) # it is empty, but not fresh anymore - + self.mailboxes.add_welcome_mail_for_fresh_user() inbox_mails = self.get_mails_by_tag('inbox') self.assertEquals(0, len(inbox_mails)) diff --git a/service/test/support/integration/app_test_client.py b/service/test/support/integration/app_test_client.py index 25171353..a1b0c3b8 100644 --- a/service/test/support/integration/app_test_client.py +++ b/service/test/support/integration/app_test_client.py @@ -125,7 +125,7 @@ class AppTestClient(object): self.soledad_querier.soledad.create_doc(_dict) def add_mail_to_inbox(self, input_mail): - mail = self.mailboxes.inbox().add(input_mail) + mail = self.mailboxes.inbox.add(input_mail) if input_mail.tags: mail.update_tags(input_mail.tags) self.search_engine.index_mail(mail) diff --git a/service/test/unit/adapter/test_draft_service.py b/service/test/unit/adapter/test_draft_service.py index 0dd6cd2a..79eca5f6 100644 --- a/service/test/unit/adapter/test_draft_service.py +++ b/service/test/unit/adapter/test_draft_service.py @@ -12,7 +12,7 @@ class DraftServiceTest(unittest.TestCase): self.mailboxes = mock() self.drafts_mailbox = mock() self.draft_service = DraftService(self.mailboxes) - when(self.mailboxes).drafts().thenReturn(self.drafts_mailbox) + self.mailboxes.drafts = self.drafts_mailbox def test_add_draft(self): mail = InputMail() diff --git a/service/test/unit/adapter/test_mail_service.py b/service/test/unit/adapter/test_mail_service.py index f4b89f57..f5e29b0c 100644 --- a/service/test/unit/adapter/test_mail_service.py +++ b/service/test/unit/adapter/test_mail_service.py @@ -27,9 +27,9 @@ class TestMailService(unittest.TestCase): self.drafts = mock() self.querier = mock() self.mailboxes = mock() - self.mailboxes.drafts = lambda: self.drafts - self.mailboxes.trash = lambda: mock() - self.mailboxes.sent = lambda: mock() + self.mailboxes.drafts = self.drafts + self.mailboxes.trash = mock() + self.mailboxes.sent = mock() self.mail_sender = mock() self.search_engine = mock() diff --git a/service/test/unit/adapter/test_mailboxes.py b/service/test/unit/adapter/test_mailboxes.py index 5b4548eb..6ff3849b 100644 --- a/service/test/unit/adapter/test_mailboxes.py +++ b/service/test/unit/adapter/test_mailboxes.py @@ -23,6 +23,7 @@ from mock import MagicMock class PixelatedMailboxesTest(unittest.TestCase): + def setUp(self): self.querier = mock() self.search_engine = mock() diff --git a/service/test/unit/config/test_welcome_mail.py b/service/test/unit/config/test_welcome_mail.py deleted file mode 100644 index 3971c73f..00000000 --- a/service/test/unit/config/test_welcome_mail.py +++ /dev/null @@ -1,32 +0,0 @@ -# -# Copyright (c) 2014 ThoughtWorks, Inc. -# -# Pixelated is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pixelated is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Pixelated. If not, see . -import unittest - -from pixelated.config.welcome_mail import build_welcome_mail -from pixelated.adapter.model.mail import InputMail - - -class WelcomeMailTest(unittest.TestCase): - - def test_build_plain_welcome_mail(self): - user_address = InputMail.FROM_EMAIL_ADDRESS = 'welcomed@user' - mail = build_welcome_mail() - self.assertEquals(user_address, mail.to) - self.assertEquals('Welcome to Pixelated Mail', mail.headers['Subject']) - self.assertIn('How to use it', mail.body) - self.assertIn('text/plain', mail._mime.as_string()) - self.assertIn('text/html', mail._mime.as_string()) - self.assertTrue(mail.headers['Date']) -- cgit v1.2.3