From 8da9557f66bcbaa91f540d44f7175a9e2436fa9b Mon Sep 17 00:00:00 2001 From: Folker Bernitt Date: Tue, 3 Nov 2015 14:00:14 +0100 Subject: Redirect to / if ajax request returns 401 status - see issue pixelated/project-issues#162 - move test code to monitored_ajax_call.spec.js --- web-ui/app/js/helpers/monitored_ajax.js | 2 ++ .../test/spec/helpers/monitored_ajax_call.spec.js | 38 ++++++++++++++++++++++ web-ui/test/spec/services/mail_service.spec.js | 18 ---------- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/web-ui/app/js/helpers/monitored_ajax.js b/web-ui/app/js/helpers/monitored_ajax.js index d05fdc4c..1cb720de 100644 --- a/web-ui/app/js/helpers/monitored_ajax.js +++ b/web-ui/app/js/helpers/monitored_ajax.js @@ -54,6 +54,8 @@ define(['page/events', 'views/i18n', 'helpers/browser'], function (events, i18n, if (xmlhttprequest.status === 302) { var redirectUrl = xmlhttprequest.getResponseHeader('Location'); browser.redirect(redirectUrl); + }else if (xmlhttprequest.status === 401) { + browser.redirect('/'); } }.bind(this)); diff --git a/web-ui/test/spec/helpers/monitored_ajax_call.spec.js b/web-ui/test/spec/helpers/monitored_ajax_call.spec.js index bf7a2db4..972ca3ae 100644 --- a/web-ui/test/spec/helpers/monitored_ajax_call.spec.js +++ b/web-ui/test/spec/helpers/monitored_ajax_call.spec.js @@ -35,6 +35,44 @@ define(['helpers/monitored_ajax'], function (monitoredAjax) { expect(component.trigger).toHaveBeenCalledWith(document, Pixelated.events.ui.userAlerts.displayMessage, { message: 'Server Message' }); }); + }); + + describe('when user seems to be logged out', function () { + var component, browser; + + beforeEach(function () { + component = { trigger: function () {}}; + browser = require('helpers/browser'); + + }); + + it('will redirect the browser to the location specified', function () { + var redirectUrl = '/some/redirect/url'; + var deferred = $.Deferred(); + spyOn($, 'ajax').and.returnValue(deferred); + var spyRedirect = spyOn(browser, 'redirect').and.returnValue($.Deferred()); + + monitoredAjax(component, '/some/url', {}); + + deferred.reject({status: 302, getResponseHeader: function (_) {return redirectUrl;}}, '', ''); + + expect(spyRedirect).toHaveBeenCalled(); + expect(spyRedirect.calls.mostRecent().args[0]).toEqual(redirectUrl); + }); + + it ('will redirect the browser to root if authentication is required', function () { + var redirectUrl = '/'; + var deferred = $.Deferred(); + spyOn($, 'ajax').and.returnValue(deferred); + var spyRedirect = spyOn(browser, 'redirect').and.returnValue($.Deferred()); + + monitoredAjax(component, '/some/url', {}); + + deferred.reject({status: 401}, '', ''); + + expect(spyRedirect).toHaveBeenCalled(); + expect(spyRedirect.calls.mostRecent().args[0]).toEqual(redirectUrl); + }); }); }); diff --git a/web-ui/test/spec/services/mail_service.spec.js b/web-ui/test/spec/services/mail_service.spec.js index 2cf69a9d..7fb2bfda 100644 --- a/web-ui/test/spec/services/mail_service.spec.js +++ b/web-ui/test/spec/services/mail_service.spec.js @@ -130,24 +130,6 @@ describeComponent('services/mail_service', function () { expect(spyAjax.calls.mostRecent().args[1].type).toEqual('DELETE'); }); - describe('when request fails', function () { - it ('will redirect the browser to the location specified', function () { - var browser = require('helpers/browser'); - var redirectUrl = '/some/redirect/url'; - var me = {}; - var deferred = $.Deferred(); - spyOn($, 'ajax').and.returnValue(deferred); - var spyRedirect = spyOn(browser, 'redirect').and.returnValue($.Deferred()); - - this.component.trigger(Pixelated.events.mail.want, { caller: me, mail: email1.ident }); - - deferred.reject({status: 302, getResponseHeader: function (_) {return redirectUrl;}}, '', ''); - - expect(spyRedirect).toHaveBeenCalled(); - expect(spyRedirect.calls.mostRecent().args[0]).toEqual(redirectUrl); - }); - }); - describe('when successfuly deletes an email', function () { var displayMessageEvent, uncheckAllEvent, mailsDeletedEvent; -- cgit v1.2.3 From 5c3468304f13fa0fe279a6e04fb90432d6737345 Mon Sep 17 00:00:00 2001 From: Folker Bernitt Date: Tue, 3 Nov 2015 16:34:52 +0100 Subject: Rename MailSender to LocalSmtpMailSender - Issue #499 --- service/pixelated/adapter/services/mail_sender.py | 2 +- service/pixelated/config/services.py | 4 ++-- service/test/unit/adapter/services/test_mail_sender.py | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/service/pixelated/adapter/services/mail_sender.py b/service/pixelated/adapter/services/mail_sender.py index 262a6e18..9c3ea3bb 100644 --- a/service/pixelated/adapter/services/mail_sender.py +++ b/service/pixelated/adapter/services/mail_sender.py @@ -27,7 +27,7 @@ class SMTPDownException(Exception): Exception.__init__(self, "Couldn't send mail now, try again later.") -class MailSender(object): +class LocalSmtpMailSender(object): def __init__(self, account_email_address, smtp): self.smtp = smtp diff --git a/service/pixelated/config/services.py b/service/pixelated/config/services.py index 41a357dc..23fd01d8 100644 --- a/service/pixelated/config/services.py +++ b/service/pixelated/config/services.py @@ -1,7 +1,7 @@ from pixelated.adapter.mailstore.searchable_mailstore import SearchableMailStore from pixelated.adapter.services.mail_service import MailService from pixelated.adapter.model.mail import InputMail -from pixelated.adapter.services.mail_sender import MailSender +from pixelated.adapter.services.mail_sender import LocalSmtpMailSender from pixelated.adapter.search import SearchEngine from pixelated.adapter.services.draft_service import DraftService from pixelated.adapter.listeners.mailbox_indexer_listener import listen_all_mailboxes @@ -57,7 +57,7 @@ class Services(object): def setup_mail_service(self, leap_session, search_engine): # if False: FIXME # yield pixelated_mailboxes.add_welcome_mail_for_fresh_user() - pixelated_mail_sender = MailSender( + pixelated_mail_sender = LocalSmtpMailSender( leap_session.account_email(), leap_session.smtp) return MailService( diff --git a/service/test/unit/adapter/services/test_mail_sender.py b/service/test/unit/adapter/services/test_mail_sender.py index 421a5f6d..7806c92a 100644 --- a/service/test/unit/adapter/services/test_mail_sender.py +++ b/service/test/unit/adapter/services/test_mail_sender.py @@ -16,14 +16,14 @@ from twisted.trial import unittest from mockito import mock, when, verify, any, unstub -from pixelated.adapter.services.mail_sender import MailSender, SMTPDownException +from pixelated.adapter.services.mail_sender import LocalSmtpMailSender, SMTPDownException from pixelated.adapter.model.mail import InputMail from test.support.test_helper import mail_dict from twisted.internet import reactor from twisted.internet.defer import Deferred -class MailSenderTest(unittest.TestCase): +class LocalSmtpMailSenderTest(unittest.TestCase): def setUp(self): self.smtp = mock() self.smtp.local_smtp_port_number = 4650 @@ -32,7 +32,7 @@ class MailSenderTest(unittest.TestCase): def test_sendmail(self): when(reactor).connectTCP('localhost', 4650, any()).thenReturn(None) input_mail = InputMail.from_dict(mail_dict()) - mail_sender = MailSender('someone@somedomain.tld', self.smtp) + mail_sender = LocalSmtpMailSender('someone@somedomain.tld', self.smtp) return self._succeed(mail_sender.sendmail(input_mail)) @@ -44,7 +44,7 @@ class MailSenderTest(unittest.TestCase): input_mail = InputMail.from_dict(mail_dict()) - mail_sender = MailSender('someone@somedomain.tld', self.smtp) + mail_sender = LocalSmtpMailSender('someone@somedomain.tld', self.smtp) sent_deferred = mail_sender.sendmail(input_mail) @@ -55,7 +55,7 @@ class MailSenderTest(unittest.TestCase): def test_senmail_returns_deffered(self): when(reactor).connectTCP('localhost', 4650, any()).thenReturn(None) input_mail = InputMail.from_dict(mail_dict()) - mail_sender = MailSender('someone@somedomain.tld', self.smtp) + mail_sender = LocalSmtpMailSender('someone@somedomain.tld', self.smtp) deferred = mail_sender.sendmail(input_mail) @@ -66,7 +66,7 @@ class MailSenderTest(unittest.TestCase): def test_doesnt_send_mail_if_smtp_is_not_running(self): self.smtp.ensure_running = lambda: False - mail_sender = MailSender('someone@somedomain.tld', self.smtp) + mail_sender = LocalSmtpMailSender('someone@somedomain.tld', self.smtp) deferred = mail_sender.sendmail({}) -- cgit v1.2.3 From af7631369c96d3da54abb4e1cab44ea61151c481 Mon Sep 17 00:00:00 2001 From: Folker Bernitt Date: Tue, 3 Nov 2015 17:12:15 +0100 Subject: Add new MailSender based on OutgoingMail - Issue #499 - No longer needs local smtp port --- service/pixelated/adapter/services/mail_sender.py | 26 ++++++++++++++++++- service/pixelated/bitmask_libraries/session.py | 2 ++ .../test/unit/adapter/services/test_mail_sender.py | 29 ++++++++++++++++++++-- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/service/pixelated/adapter/services/mail_sender.py b/service/pixelated/adapter/services/mail_sender.py index 9c3ea3bb..1befd1cc 100644 --- a/service/pixelated/adapter/services/mail_sender.py +++ b/service/pixelated/adapter/services/mail_sender.py @@ -15,10 +15,11 @@ # along with Pixelated. If not, see . from StringIO import StringIO from email.utils import parseaddr +from leap.mail.outgoing.service import OutgoingMail from twisted.internet.defer import Deferred, fail from twisted.mail.smtp import SMTPSenderFactory -from twisted.internet import reactor +from twisted.internet import reactor, defer from pixelated.support.functional import flatten @@ -27,6 +28,29 @@ class SMTPDownException(Exception): Exception.__init__(self, "Couldn't send mail now, try again later.") +class MailSender(object): + + def __init__(self, account_email_address, keymanager, cert_path, remote_smtp_host, remote_smtp_port): + self._from = account_email_address + self._keymanager = keymanager + self._cert_path = cert_path + self._remote_smtp_host = remote_smtp_host + self._remote_smtp_port = remote_smtp_port + + def sendmail(self, mail): + recipients = flatten([mail.to, mail.cc, mail.bcc]) + outgoing_mail = self._create_outgoing_mail() + deferreds = [] + + for recipient in recipients: + deferreds.append(outgoing_mail.send_message(mail.to_smtp_format(), recipient)) + + return defer.gatherResults(deferreds) + + def _create_outgoing_mail(self): + return OutgoingMail(self._from, self._keymanager, self._cert_path, self._cert_path, self._remote_smtp_host, self._remote_smtp_port) + + class LocalSmtpMailSender(object): def __init__(self, account_email_address, smtp): diff --git a/service/pixelated/bitmask_libraries/session.py b/service/pixelated/bitmask_libraries/session.py index da62b084..e13e5863 100644 --- a/service/pixelated/bitmask_libraries/session.py +++ b/service/pixelated/bitmask_libraries/session.py @@ -151,6 +151,8 @@ class LeapSessionFactory(object): smtp = LeapSmtp(self._provider, auth, nicknym.keymanager) + # TODO: Create the new mail sender based on what we have in available LeapSmtp, e.g. the certs + return LeapSession(self._provider, auth, mail_store, soledad, nicknym, smtp) def _lookup_session(self, key): diff --git a/service/test/unit/adapter/services/test_mail_sender.py b/service/test/unit/adapter/services/test_mail_sender.py index 7806c92a..ca68bdc6 100644 --- a/service/test/unit/adapter/services/test_mail_sender.py +++ b/service/test/unit/adapter/services/test_mail_sender.py @@ -13,16 +13,41 @@ # # You should have received a copy of the GNU Affero General Public License # along with Pixelated. If not, see . +from leap.mail.outgoing.service import OutgoingMail from twisted.trial import unittest from mockito import mock, when, verify, any, unstub -from pixelated.adapter.services.mail_sender import LocalSmtpMailSender, SMTPDownException +from pixelated.adapter.services.mail_sender import LocalSmtpMailSender, SMTPDownException, MailSender from pixelated.adapter.model.mail import InputMail +from pixelated.support.functional import flatten from test.support.test_helper import mail_dict -from twisted.internet import reactor +from twisted.internet import reactor, defer from twisted.internet.defer import Deferred +class MailSenderTest(unittest.TestCase): + + def setUp(self): + self._cert_path = u'/some/cert/path' + self._keymanager_mock = mock() + self._remote_smtp_host = 'some.host.test' + self._remote_smtp_port = 1234 + + def tearDown(self): + unstub() + + def test_iterates_over_recipients(self): + sender = MailSender('someone@somedomain.tld', self._keymanager_mock, self._cert_path, self._remote_smtp_host, self._remote_smtp_port) + input_mail = InputMail.from_dict(mail_dict()) + + when(OutgoingMail).send_message(any(), any()).thenAnswer(lambda: defer.succeed(None)) + + sender.sendmail(input_mail) + + for recipient in flatten([input_mail.to, input_mail.cc, input_mail.bcc]): + verify(OutgoingMail).send_message(any(), recipient) + + class LocalSmtpMailSenderTest(unittest.TestCase): def setUp(self): self.smtp = mock() -- cgit v1.2.3 From 0f84c6354a116fd53628b23a56c1528c5dd3e8ef Mon Sep 17 00:00:00 2001 From: Giovane Date: Tue, 3 Nov 2015 17:59:07 -0200 Subject: [#508] Making search also work with substrings - pairing w/ @pereiragislene --- service/pixelated/adapter/mailstore/leap_mailstore.py | 2 +- service/pixelated/adapter/search/__init__.py | 10 +++++----- web-ui/app/js/search/results_highlighter.js | 2 +- web-ui/test/spec/search/results_highlighter.spec.js | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/service/pixelated/adapter/mailstore/leap_mailstore.py b/service/pixelated/adapter/mailstore/leap_mailstore.py index 2754c624..519b124a 100644 --- a/service/pixelated/adapter/mailstore/leap_mailstore.py +++ b/service/pixelated/adapter/mailstore/leap_mailstore.py @@ -223,7 +223,7 @@ class LeapMailStore(MailStore): def get_mails(self, mail_ids): deferreds = [] for mail_id in mail_ids: - deferreds.append(self.get_mail(mail_id)) + deferreds.append(self.get_mail(mail_id, include_body=True)) return defer.gatherResults(deferreds, consumeErrors=True) diff --git a/service/pixelated/adapter/search/__init__.py b/service/pixelated/adapter/search/__init__.py index 56ab2255..065dd5e5 100644 --- a/service/pixelated/adapter/search/__init__.py +++ b/service/pixelated/adapter/search/__init__.py @@ -23,7 +23,7 @@ import time from pixelated.adapter.model.status import Status from pixelated.adapter.search.contacts import contacts_suggestions from whoosh.index import FileIndex -from whoosh.fields import Schema, ID, KEYWORD, TEXT, NUMERIC +from whoosh.fields import Schema, ID, KEYWORD, TEXT, NUMERIC, NGRAMWORDS from whoosh.qparser import QueryParser from whoosh.qparser import MultifieldParser from whoosh.writing import AsyncWriter @@ -103,9 +103,9 @@ class SearchEngine(object): to=KEYWORD(stored=False, commas=True), cc=KEYWORD(stored=False, commas=True), bcc=KEYWORD(stored=False, commas=True), - subject=TEXT(stored=False), + subject=NGRAMWORDS(stored=False), date=NUMERIC(stored=False, sortable=True, bits=64, signed=False), - body=TEXT(stored=False), + body=NGRAMWORDS(stored=False), tag=KEYWORD(stored=True, commas=True), flags=KEYWORD(stored=True, commas=True), raw=TEXT(stored=False)) @@ -116,7 +116,7 @@ class SearchEngine(object): def index_mail(self, mail): with AsyncWriter(self._index) as writer: - self._index_mail(writer, mail) + self._index_mail(writer, mail) def _index_mail(self, writer, mail): mdict = mail.as_dict() @@ -197,7 +197,7 @@ class SearchEngine(object): .replace('-in:', 'AND NOT tag:') .replace('in:all', '*') ) - return MultifieldParser(['raw', 'body'], self._index.schema).parse(query) + return MultifieldParser(['body', 'subject', 'raw'], self._index.schema).parse(query) def remove_from_index(self, mail_id): with AsyncWriter(self._index) as writer: diff --git a/web-ui/app/js/search/results_highlighter.js b/web-ui/app/js/search/results_highlighter.js index 2c7d1cc7..9e3ba167 100644 --- a/web-ui/app/js/search/results_highlighter.js +++ b/web-ui/app/js/search/results_highlighter.js @@ -40,7 +40,7 @@ define( var domIdent = data.where; if(this.attr.keywords) { _.each(this.attr.keywords, function (keyword) { - $(domIdent).highlightRegex(new RegExp('\\b' + keyword, 'i'), { + $(domIdent).highlightRegex(new RegExp(keyword, 'i'), { tagType: 'em', className: 'search-highlight' }); diff --git a/web-ui/test/spec/search/results_highlighter.spec.js b/web-ui/test/spec/search/results_highlighter.spec.js index 523c3599..cfb61e9c 100644 --- a/web-ui/test/spec/search/results_highlighter.spec.js +++ b/web-ui/test/spec/search/results_highlighter.spec.js @@ -1,7 +1,7 @@ describeComponent('search/results_highlighter', function () { 'use strict'; - it('highlights only words that matches with the keywords given', function () { + it('highlights words or parts of words that match with the keywords given', function () { this.setupComponent('
Any one seeing too many open bugs
'); this.component.attr = {keywords: ['any']}; @@ -9,7 +9,7 @@ describeComponent('search/results_highlighter', function () { var highlightedWords = this.component.$node.find('.search-highlight').length; - expect(highlightedWords).toEqual(1); + expect(highlightedWords).toEqual(2); }); it('resets highlights when a new search is performed', function() { -- cgit v1.2.3 From 20962bdea85b9e0ac04ab9f714853ca8516cd7f4 Mon Sep 17 00:00:00 2001 From: Bruno Wagner Date: Tue, 3 Nov 2015 19:41:34 -0200 Subject: Issue #499 Moved remote smtp configuration We removed the common parts from the bitmask libraries smtp and adapted the tests. We also advanced the new mail sender implementation, but it is coupled to the twisted.mail.smtp.User currently and we need to adapt leap mail to remove this dependency --- service/pixelated/adapter/services/mail_sender.py | 7 +++- service/pixelated/bitmask_libraries/provider.py | 15 +++++++ service/pixelated/bitmask_libraries/session.py | 39 ++++++++++++++---- service/pixelated/bitmask_libraries/smtp.py | 49 +---------------------- service/pixelated/config/services.py | 13 ++++-- service/test/unit/bitmask_libraries/test_smtp.py | 15 +++---- 6 files changed, 70 insertions(+), 68 deletions(-) diff --git a/service/pixelated/adapter/services/mail_sender.py b/service/pixelated/adapter/services/mail_sender.py index 1befd1cc..42cf13be 100644 --- a/service/pixelated/adapter/services/mail_sender.py +++ b/service/pixelated/adapter/services/mail_sender.py @@ -48,7 +48,12 @@ class MailSender(object): return defer.gatherResults(deferreds) def _create_outgoing_mail(self): - return OutgoingMail(self._from, self._keymanager, self._cert_path, self._cert_path, self._remote_smtp_host, self._remote_smtp_port) + return OutgoingMail(str(self._from), + self._keymanager, + unicode(self._cert_path), + unicode(self._cert_path), + str(self._remote_smtp_host), + int(self._remote_smtp_port)) class LocalSmtpMailSender(object): diff --git a/service/pixelated/bitmask_libraries/provider.py b/service/pixelated/bitmask_libraries/provider.py index b7f82f8a..071b0bbf 100644 --- a/service/pixelated/bitmask_libraries/provider.py +++ b/service/pixelated/bitmask_libraries/provider.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with Pixelated. If not, see . import json +import os from leap.common.certs import get_digest import requests @@ -95,6 +96,13 @@ class LeapProvider(object): if fingerprint.strip() != digest: raise Exception('Certificate fingerprints don\'t match! Expected [%s] but got [%s]' % (fingerprint.strip(), digest)) + def smtp_info(self): + json_data = self.fetch_smtp_json() + hosts = json_data['hosts'] + hostname = hosts.keys()[0] + host = hosts[hostname] + return host['hostname'], host['port'] + def _validated_get(self, url): session = requests.session() try: @@ -130,3 +138,10 @@ class LeapProvider(object): def address_for(self, username): return '%s@%s' % (username, self.domain) + + def _client_cert_path(self): + return os.path.join( + self.config.leap_home, + "providers", + self.domain, + "keys", "client", "smtp.pem") diff --git a/service/pixelated/bitmask_libraries/session.py b/service/pixelated/bitmask_libraries/session.py index e13e5863..4a503628 100644 --- a/service/pixelated/bitmask_libraries/session.py +++ b/service/pixelated/bitmask_libraries/session.py @@ -16,17 +16,18 @@ import errno import traceback import sys - import os +import requests + +from twisted.internet import reactor, defer +from pixelated.bitmask_libraries.certs import LeapCertificate +from pixelated.adapter.mailstore import LeapMailStore from leap.mail.incoming.service import IncomingMail -from twisted.internet import reactor -from .nicknym import NickNym from leap.auth import SRPAuth -from pixelated.adapter.mailstore import LeapMailStore -from .soledad import SoledadSessionFactory -from .smtp import LeapSmtp from leap.mail.imap.account import IMAPAccount -from twisted.internet import defer +from .nicknym import NickNym +from .smtp import LeapSmtp +from .soledad import SoledadSessionFactory from leap.common.events import ( register, @@ -149,12 +150,36 @@ class LeapSessionFactory(object): nicknym = self._create_nicknym(account_email, auth.token, auth.uuid, soledad) + self._download_smtp_cert(auth) smtp = LeapSmtp(self._provider, auth, nicknym.keymanager) # TODO: Create the new mail sender based on what we have in available LeapSmtp, e.g. the certs return LeapSession(self._provider, auth, mail_store, soledad, nicknym, smtp) + def _download_smtp_cert(self, auth): + cert_path = self._provider._client_cert_path() + + if not os.path.exists(os.path.dirname(cert_path)): + os.makedirs(os.path.dirname(cert_path)) + + cert_url = '%s/%s/cert' % (self._provider.api_uri, self._provider.api_version) + cookies = {"_session_id": auth.session_id} + headers = {} + headers["Authorization"] = 'Token token="{0}"'.format(auth.token) + response = requests.get( + cert_url, + verify=LeapCertificate(self._provider).provider_api_cert, + cookies=cookies, + timeout=self._provider.config.timeout_in_s, + headers=headers) + response.raise_for_status() + + client_cert = response.content + + with open(cert_path, 'w') as f: + f.write(client_cert) + def _lookup_session(self, key): global SESSIONS if key in SESSIONS: diff --git a/service/pixelated/bitmask_libraries/smtp.py b/service/pixelated/bitmask_libraries/smtp.py index ff2792fb..63d2d310 100644 --- a/service/pixelated/bitmask_libraries/smtp.py +++ b/service/pixelated/bitmask_libraries/smtp.py @@ -33,57 +33,12 @@ class LeapSmtp(object): self.session_id = auth.session_id self.user_token = auth.token self._keymanager = keymanager - self._remote_hostname, self._remote_port = self._discover_remote_smtp_server() + self._remote_hostname, self._remote_port = provider.smtp_info() self._local_smtp_service_socket = None self._local_smtp_service = None - def smtp_info(self): - return ('localhost', self.local_smtp_port_number) - - def _discover_remote_smtp_server(self): - json_data = self._provider.fetch_smtp_json() - hosts = json_data['hosts'] - hostname = hosts.keys()[0] - host = hosts[hostname] - - hostname = host['hostname'] - port = host['port'] - - return hostname, port - - def _download_client_certificates(self): - cert_path = self._client_cert_path() - - if not os.path.exists(os.path.dirname(cert_path)): - os.makedirs(os.path.dirname(cert_path)) - - cert_url = '%s/%s/cert' % (self._provider.api_uri, self._provider.api_version) - cookies = {"_session_id": self.session_id} - headers = {} - headers["Authorization"] = 'Token token="{0}"'.format(self.user_token) - response = requests.get( - cert_url, - verify=LeapCertificate(self._provider).provider_api_cert, - cookies=cookies, - timeout=self._provider.config.timeout_in_s, - headers=headers) - response.raise_for_status() - - client_cert = response.content - - with open(cert_path, 'w') as f: - f.write(client_cert) - - def _client_cert_path(self): - return os.path.join( - self._provider.config.leap_home, - "providers", - self._provider.domain, - "keys", "client", "smtp.pem") - def start(self): - self._download_client_certificates() - cert_path = self._client_cert_path() + cert_path = self._provider._client_cert_path() email = '%s@%s' % (self.username, self._provider.domain) self._local_smtp_service, self._local_smtp_service_socket = setup_smtp_gateway( diff --git a/service/pixelated/config/services.py b/service/pixelated/config/services.py index 23fd01d8..7c08d286 100644 --- a/service/pixelated/config/services.py +++ b/service/pixelated/config/services.py @@ -1,7 +1,7 @@ from pixelated.adapter.mailstore.searchable_mailstore import SearchableMailStore from pixelated.adapter.services.mail_service import MailService from pixelated.adapter.model.mail import InputMail -from pixelated.adapter.services.mail_sender import LocalSmtpMailSender +from pixelated.adapter.services.mail_sender import LocalSmtpMailSender # , MailSender from pixelated.adapter.search import SearchEngine from pixelated.adapter.services.draft_service import DraftService from pixelated.adapter.listeners.mailbox_indexer_listener import listen_all_mailboxes @@ -55,11 +55,18 @@ class Services(object): self.search_engine = search_engine def setup_mail_service(self, leap_session, search_engine): - # if False: FIXME - # yield pixelated_mailboxes.add_welcome_mail_for_fresh_user() + smtp_host, smtp_port = leap_session.provider.smtp_info() pixelated_mail_sender = LocalSmtpMailSender( leap_session.account_email(), leap_session.smtp) + + # pixelated_mail_sender = MailSender( + # leap_session.account_email(), + # leap_session.nicknym, + # leap_session.provider.local_ca_crt, + # smtp_host, + # smtp_port) + return MailService( pixelated_mail_sender, leap_session.mail_store, diff --git a/service/test/unit/bitmask_libraries/test_smtp.py b/service/test/unit/bitmask_libraries/test_smtp.py index 9481c488..182a0786 100644 --- a/service/test/unit/bitmask_libraries/test_smtp.py +++ b/service/test/unit/bitmask_libraries/test_smtp.py @@ -53,20 +53,13 @@ class LeapSmtpTest(AbstractLeapTest): } self.config.timeout_in_s = 15 - def test_that_client_cert_gets_downloaded(self): - smtp = LeapSmtp(self.provider, self.auth, self.keymanager) - - with HTTMock(ca_cert_mock, not_found_mock): - smtp._download_client_certificates() - - path = self._client_cert_path() - self.assertTrue(os.path.isfile(path)) - def _client_cert_path(self): return os.path.join(self.leap_home, 'providers', 'some-server.test', 'keys', 'client', 'smtp.pem') @patch('pixelated.bitmask_libraries.smtp.setup_smtp_gateway') def test_that_start_calls_setup_smtp_gateway(self, gateway_mock): + self.provider.smtp_info = MagicMock(return_value=('smtp.some-sever.test', 1234)) + self.provider._client_cert_path = MagicMock(return_value=self._client_cert_path()) smtp = LeapSmtp(self.provider, self.auth, self.keymanager) port = 500 @@ -76,9 +69,10 @@ class LeapSmtpTest(AbstractLeapTest): smtp.ensure_running() cert_path = self._client_cert_path() - gateway_mock.assert_called_with(keymanager=self.keymanager, smtp_cert=cert_path, smtp_key=cert_path, userid='test_user@some-server.test', smtp_port='1234', encrypted_only=False, smtp_host='smtp.some-sever.test', port=port) + gateway_mock.assert_called_with(smtp_cert=cert_path, userid='test_user@some-server.test', smtp_port=1234, smtp_key=cert_path, keymanager=self.keymanager, encrypted_only=False, smtp_host='smtp.some-sever.test', port=port) def test_that_client_stop_does_nothing_if_not_started(self): + self.provider.smtp_info = MagicMock(return_value=('smtp.some-sever.test', 1234)) smtp = LeapSmtp(self.provider, self.auth, self.keymanager) with HTTMock(not_found_mock): @@ -86,6 +80,7 @@ class LeapSmtpTest(AbstractLeapTest): @patch('pixelated.bitmask_libraries.smtp.setup_smtp_gateway') def test_that_running_smtp_sevice_is_stopped(self, gateway_mock): + self.provider.smtp_info = MagicMock(return_value=('smtp.some-sever.test', 1234)) smtp = LeapSmtp(self.provider, self.auth, self.keymanager) smtp_service = MagicMock() -- cgit v1.2.3 From ffa355c46eda5d05f25890420218845ac3e53f71 Mon Sep 17 00:00:00 2001 From: Jefferson Stachelski Date: Tue, 3 Nov 2015 20:00:48 -0200 Subject: Issue #499 - Fixed pep8 --- service/pixelated/adapter/services/mail_sender.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/service/pixelated/adapter/services/mail_sender.py b/service/pixelated/adapter/services/mail_sender.py index 42cf13be..a3315b53 100644 --- a/service/pixelated/adapter/services/mail_sender.py +++ b/service/pixelated/adapter/services/mail_sender.py @@ -48,12 +48,7 @@ class MailSender(object): return defer.gatherResults(deferreds) def _create_outgoing_mail(self): - return OutgoingMail(str(self._from), - self._keymanager, - unicode(self._cert_path), - unicode(self._cert_path), - str(self._remote_smtp_host), - int(self._remote_smtp_port)) + return OutgoingMail(str(self._from), self._keymanager, unicode(self._cert_path), unicode(self._cert_path), str(self._remote_smtp_host), int(self._remote_smtp_port)) class LocalSmtpMailSender(object): -- cgit v1.2.3 From d79aa00e3c24c5bf5e5ed5ba5a9b976f93034362 Mon Sep 17 00:00:00 2001 From: Folker Bernitt Date: Wed, 4 Nov 2015 10:11:19 +0100 Subject: Instantiate new MailSender in Services - Issue #499 - Some smaller refactorings - Extract smtp cert download to own class --- service/pixelated/adapter/services/mail_sender.py | 14 ++-- service/pixelated/bitmask_libraries/provider.py | 7 -- service/pixelated/bitmask_libraries/session.py | 61 +++++++++++------ service/pixelated/bitmask_libraries/smtp.py | 33 +++++---- service/pixelated/config/services.py | 10 +-- .../test/unit/adapter/services/test_mail_sender.py | 7 +- service/test/unit/bitmask_libraries/test_smtp.py | 53 +++------------ .../bitmask_libraries/test_smtp_cert_downloader.py | 78 ++++++++++++++++++++++ 8 files changed, 161 insertions(+), 102 deletions(-) create mode 100644 service/test/unit/bitmask_libraries/test_smtp_cert_downloader.py diff --git a/service/pixelated/adapter/services/mail_sender.py b/service/pixelated/adapter/services/mail_sender.py index a3315b53..ca1e99d7 100644 --- a/service/pixelated/adapter/services/mail_sender.py +++ b/service/pixelated/adapter/services/mail_sender.py @@ -30,12 +30,9 @@ class SMTPDownException(Exception): class MailSender(object): - def __init__(self, account_email_address, keymanager, cert_path, remote_smtp_host, remote_smtp_port): - self._from = account_email_address + def __init__(self, smtp_config, keymanager): + self._smtp_config = smtp_config self._keymanager = keymanager - self._cert_path = cert_path - self._remote_smtp_host = remote_smtp_host - self._remote_smtp_port = remote_smtp_port def sendmail(self, mail): recipients = flatten([mail.to, mail.cc, mail.bcc]) @@ -48,7 +45,12 @@ class MailSender(object): return defer.gatherResults(deferreds) def _create_outgoing_mail(self): - return OutgoingMail(str(self._from), self._keymanager, unicode(self._cert_path), unicode(self._cert_path), str(self._remote_smtp_host), int(self._remote_smtp_port)) + return OutgoingMail(str(self._smtp_config.account_email), + self._keymanager, + self._smtp_config.cert_path, + self._smtp_config.cert_path, + str(self._smtp_config.remote_smtp_host), + int(self._smtp_config.remote_smtp_port)) class LocalSmtpMailSender(object): diff --git a/service/pixelated/bitmask_libraries/provider.py b/service/pixelated/bitmask_libraries/provider.py index 071b0bbf..a529208d 100644 --- a/service/pixelated/bitmask_libraries/provider.py +++ b/service/pixelated/bitmask_libraries/provider.py @@ -138,10 +138,3 @@ class LeapProvider(object): def address_for(self, username): return '%s@%s' % (username, self.domain) - - def _client_cert_path(self): - return os.path.join( - self.config.leap_home, - "providers", - self.domain, - "keys", "client", "smtp.pem") diff --git a/service/pixelated/bitmask_libraries/session.py b/service/pixelated/bitmask_libraries/session.py index 4a503628..3f8e6de6 100644 --- a/service/pixelated/bitmask_libraries/session.py +++ b/service/pixelated/bitmask_libraries/session.py @@ -26,7 +26,7 @@ from leap.mail.incoming.service import IncomingMail from leap.auth import SRPAuth from leap.mail.imap.account import IMAPAccount from .nicknym import NickNym -from .smtp import LeapSmtp +from .smtp import LeapSmtp, LeapSMTPConfig from .soledad import SoledadSessionFactory from leap.common.events import ( @@ -123,6 +123,36 @@ class LeapSession(object): raise +class SmtpCertDownloader(object): + + def __init__(self, provider, auth): + self._provider = provider + self._auth = auth + + def download(self): + cert_url = '%s/%s/cert' % (self._provider.api_uri, self._provider.api_version) + cookies = {"_session_id": self._auth.session_id} + headers = {} + headers["Authorization"] = 'Token token="{0}"'.format(self._auth.token) + response = requests.get( + cert_url, + verify=LeapCertificate(self._provider).provider_api_cert, + cookies=cookies, + timeout=self._provider.config.timeout_in_s, + headers=headers) + response.raise_for_status() + + client_cert = response.content + + return client_cert + + def download_to(self, target_file): + client_cert = self.download() + + with open(target_file, 'w') as f: + f.write(client_cert) + + class LeapSessionFactory(object): def __init__(self, provider): self._provider = provider @@ -151,34 +181,27 @@ class LeapSessionFactory(object): nicknym = self._create_nicknym(account_email, auth.token, auth.uuid, soledad) self._download_smtp_cert(auth) - smtp = LeapSmtp(self._provider, auth, nicknym.keymanager) - # TODO: Create the new mail sender based on what we have in available LeapSmtp, e.g. the certs + smtp_host, smtp_port = self._provider.smtp_info() + smtp_config = LeapSMTPConfig(account_email, self._smtp_client_cert_path(), smtp_host, smtp_port) + smtp = LeapSmtp(smtp_config, nicknym.keymanager) return LeapSession(self._provider, auth, mail_store, soledad, nicknym, smtp) def _download_smtp_cert(self, auth): - cert_path = self._provider._client_cert_path() + cert_path = self._smtp_client_cert_path() if not os.path.exists(os.path.dirname(cert_path)): os.makedirs(os.path.dirname(cert_path)) - cert_url = '%s/%s/cert' % (self._provider.api_uri, self._provider.api_version) - cookies = {"_session_id": auth.session_id} - headers = {} - headers["Authorization"] = 'Token token="{0}"'.format(auth.token) - response = requests.get( - cert_url, - verify=LeapCertificate(self._provider).provider_api_cert, - cookies=cookies, - timeout=self._provider.config.timeout_in_s, - headers=headers) - response.raise_for_status() - - client_cert = response.content + SmtpCertDownloader(self._provider, auth).download_to(cert_path) - with open(cert_path, 'w') as f: - f.write(client_cert) + def _smtp_client_cert_path(self): + return os.path.join( + self._config.leap_home, + "providers", + self._provider.domain, + "keys", "client", "smtp.pem") def _lookup_session(self, key): global SESSIONS diff --git a/service/pixelated/bitmask_libraries/smtp.py b/service/pixelated/bitmask_libraries/smtp.py index 63d2d310..f4ab00f7 100644 --- a/service/pixelated/bitmask_libraries/smtp.py +++ b/service/pixelated/bitmask_libraries/smtp.py @@ -14,41 +14,40 @@ # You should have received a copy of the GNU Affero General Public License # along with Pixelated. If not, see . import logging -import os -import requests import random from leap.mail.smtp import setup_smtp_gateway -from pixelated.bitmask_libraries.certs import LeapCertificate logger = logging.getLogger(__name__) +class LeapSMTPConfig(object): + + def __init__(self, account_email, cert_path, remote_smtp_host, remote_smtp_port): + self.account_email = account_email + self.cert_path = cert_path + self.remote_smtp_host = remote_smtp_host + self.remote_smtp_port = remote_smtp_port + + class LeapSmtp(object): - def __init__(self, provider, auth, keymanager=None): + def __init__(self, smtp_config, keymanager=None): self.local_smtp_port_number = random.randrange(12000, 16000) - self._provider = provider - self.username = auth.username - self.session_id = auth.session_id - self.user_token = auth.token + self._smtp_config = smtp_config self._keymanager = keymanager - self._remote_hostname, self._remote_port = provider.smtp_info() self._local_smtp_service_socket = None self._local_smtp_service = None def start(self): - cert_path = self._provider._client_cert_path() - email = '%s@%s' % (self.username, self._provider.domain) - self._local_smtp_service, self._local_smtp_service_socket = setup_smtp_gateway( port=self.local_smtp_port_number, - userid=str(email), + userid=str(self._smtp_config.account_email), keymanager=self._keymanager, - smtp_host=self._remote_hostname.encode('UTF-8'), - smtp_port=self._remote_port, - smtp_cert=cert_path, - smtp_key=cert_path, + smtp_host=self._smtp_config.remote_smtp_host.encode('UTF-8'), + smtp_port=self._smtp_config.remote_smtp_port, + smtp_cert=self._smtp_config.cert_path, + smtp_key=self._smtp_config.cert_path, encrypted_only=False ) diff --git a/service/pixelated/config/services.py b/service/pixelated/config/services.py index 7c08d286..cd475228 100644 --- a/service/pixelated/config/services.py +++ b/service/pixelated/config/services.py @@ -1,7 +1,7 @@ from pixelated.adapter.mailstore.searchable_mailstore import SearchableMailStore from pixelated.adapter.services.mail_service import MailService from pixelated.adapter.model.mail import InputMail -from pixelated.adapter.services.mail_sender import LocalSmtpMailSender # , MailSender +from pixelated.adapter.services.mail_sender import LocalSmtpMailSender, MailSender # , MailSender from pixelated.adapter.search import SearchEngine from pixelated.adapter.services.draft_service import DraftService from pixelated.adapter.listeners.mailbox_indexer_listener import listen_all_mailboxes @@ -55,17 +55,11 @@ class Services(object): self.search_engine = search_engine def setup_mail_service(self, leap_session, search_engine): - smtp_host, smtp_port = leap_session.provider.smtp_info() pixelated_mail_sender = LocalSmtpMailSender( leap_session.account_email(), leap_session.smtp) - # pixelated_mail_sender = MailSender( - # leap_session.account_email(), - # leap_session.nicknym, - # leap_session.provider.local_ca_crt, - # smtp_host, - # smtp_port) + MailSender(leap_session.smtp._smtp_config, leap_session.nicknym.keymanager) return MailService( pixelated_mail_sender, diff --git a/service/test/unit/adapter/services/test_mail_sender.py b/service/test/unit/adapter/services/test_mail_sender.py index ca68bdc6..d8c33f17 100644 --- a/service/test/unit/adapter/services/test_mail_sender.py +++ b/service/test/unit/adapter/services/test_mail_sender.py @@ -19,6 +19,7 @@ from twisted.trial import unittest from mockito import mock, when, verify, any, unstub from pixelated.adapter.services.mail_sender import LocalSmtpMailSender, SMTPDownException, MailSender from pixelated.adapter.model.mail import InputMail +from pixelated.bitmask_libraries.smtp import LeapSMTPConfig from pixelated.support.functional import flatten from test.support.test_helper import mail_dict from twisted.internet import reactor, defer @@ -32,17 +33,19 @@ class MailSenderTest(unittest.TestCase): self._keymanager_mock = mock() self._remote_smtp_host = 'some.host.test' self._remote_smtp_port = 1234 + self._smtp_config = LeapSMTPConfig('someone@somedomain.tld', self._cert_path, self._remote_smtp_host, self._remote_smtp_port) def tearDown(self): unstub() + @defer.inlineCallbacks def test_iterates_over_recipients(self): - sender = MailSender('someone@somedomain.tld', self._keymanager_mock, self._cert_path, self._remote_smtp_host, self._remote_smtp_port) + sender = MailSender(self._smtp_config, self._keymanager_mock) input_mail = InputMail.from_dict(mail_dict()) when(OutgoingMail).send_message(any(), any()).thenAnswer(lambda: defer.succeed(None)) - sender.sendmail(input_mail) + yield sender.sendmail(input_mail) for recipient in flatten([input_mail.to, input_mail.cc, input_mail.bcc]): verify(OutgoingMail).send_message(any(), recipient) diff --git a/service/test/unit/bitmask_libraries/test_smtp.py b/service/test/unit/bitmask_libraries/test_smtp.py index 182a0786..22b69b9e 100644 --- a/service/test/unit/bitmask_libraries/test_smtp.py +++ b/service/test/unit/bitmask_libraries/test_smtp.py @@ -13,29 +13,10 @@ # # You should have received a copy of the GNU Affero General Public License # along with Pixelated. If not, see . -import sys - import os from mock import MagicMock, patch from test_abstract_leap import AbstractLeapTest -from pixelated.bitmask_libraries.smtp import LeapSmtp -from httmock import all_requests, HTTMock, urlmatch - - -@all_requests -def not_found_mock(url, request): - sys.stderr.write('url=%s\n' % url.netloc) - sys.stderr.write('path=%s\n' % url.path) - return {'status_code': 404, - 'content': 'foobar'} - - -@urlmatch(netloc='api.some-server.test:4430', path='/1/cert') -def ca_cert_mock(url, request): - return { - "status_code": 200, - "content": "some content" - } +from pixelated.bitmask_libraries.smtp import LeapSmtp, LeapSMTPConfig class LeapSmtpTest(AbstractLeapTest): @@ -43,53 +24,39 @@ class LeapSmtpTest(AbstractLeapTest): def setUp(self): super(LeapSmtpTest, self).setUp() - self.provider.fetch_smtp_json.return_value = { - 'hosts': { - 'leap-mx': { - 'hostname': 'smtp.some-sever.test', - 'port': '1234' - } - } - } self.config.timeout_in_s = 15 + self._smtp_config = LeapSMTPConfig('test_user@some-server.test', self._client_cert_path(), 'smtp.some-server.test', 1234) def _client_cert_path(self): return os.path.join(self.leap_home, 'providers', 'some-server.test', 'keys', 'client', 'smtp.pem') @patch('pixelated.bitmask_libraries.smtp.setup_smtp_gateway') def test_that_start_calls_setup_smtp_gateway(self, gateway_mock): - self.provider.smtp_info = MagicMock(return_value=('smtp.some-sever.test', 1234)) - self.provider._client_cert_path = MagicMock(return_value=self._client_cert_path()) - smtp = LeapSmtp(self.provider, self.auth, self.keymanager) + smtp = LeapSmtp(self._smtp_config, self.keymanager) port = 500 smtp.local_smtp_port_number = port gateway_mock.return_value = (None, None) - with HTTMock(ca_cert_mock, not_found_mock): - smtp.ensure_running() + smtp.ensure_running() cert_path = self._client_cert_path() - gateway_mock.assert_called_with(smtp_cert=cert_path, userid='test_user@some-server.test', smtp_port=1234, smtp_key=cert_path, keymanager=self.keymanager, encrypted_only=False, smtp_host='smtp.some-sever.test', port=port) + gateway_mock.assert_called_with(smtp_cert=cert_path, userid='test_user@some-server.test', smtp_port=1234, smtp_key=cert_path, keymanager=self.keymanager, encrypted_only=False, smtp_host='smtp.some-server.test', port=port) def test_that_client_stop_does_nothing_if_not_started(self): - self.provider.smtp_info = MagicMock(return_value=('smtp.some-sever.test', 1234)) - smtp = LeapSmtp(self.provider, self.auth, self.keymanager) + smtp = LeapSmtp(self._smtp_config, self.keymanager) - with HTTMock(not_found_mock): - smtp.stop() + smtp.stop() @patch('pixelated.bitmask_libraries.smtp.setup_smtp_gateway') def test_that_running_smtp_sevice_is_stopped(self, gateway_mock): - self.provider.smtp_info = MagicMock(return_value=('smtp.some-sever.test', 1234)) - smtp = LeapSmtp(self.provider, self.auth, self.keymanager) + smtp = LeapSmtp(self._smtp_config, self.keymanager) smtp_service = MagicMock() smtp_port = MagicMock() gateway_mock.return_value = (smtp_service, smtp_port) - with HTTMock(ca_cert_mock, not_found_mock): - smtp.ensure_running() - smtp.stop() + smtp.ensure_running() + smtp.stop() smtp_port.stopListening.assert_called_with() smtp_service.doStop.assert_called_with() diff --git a/service/test/unit/bitmask_libraries/test_smtp_cert_downloader.py b/service/test/unit/bitmask_libraries/test_smtp_cert_downloader.py new file mode 100644 index 00000000..5644ab6a --- /dev/null +++ b/service/test/unit/bitmask_libraries/test_smtp_cert_downloader.py @@ -0,0 +1,78 @@ +# +# Copyright (c) 2015 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 mockito import mock, unstub +from requests import HTTPError +from pixelated.bitmask_libraries.session import SmtpCertDownloader +from tempfile import NamedTemporaryFile +from httmock import all_requests, HTTMock, urlmatch + +CERTIFICATE_DATA = 'some cert data' + + +@all_requests +def not_found_mock(url, request): + return {'status_code': 404, + 'content': 'foobar'} + + +@urlmatch(netloc='api.some-server.test:4430', path='/1/cert') +def ca_cert_mock(url, request): + return { + "status_code": 200, + "content": CERTIFICATE_DATA + } + + +class TestSmtpCertDownloader(unittest.TestCase): + + def setUp(self): + self._provider = mock() + self._config = mock() + self._config.leap_home = '/tmp' + self._auth = mock() + + self._provider.config = self._config + self._provider.api_uri = 'https://api.some-server.test:4430' + self._provider.api_version = '1' + self._provider.server_name = 'some.host.tld' + + self._auth.session_id = 'some session id' + self._auth.token = 'some token' + + def tearDown(self): + unstub() + + def test_download_certificate(self): + with HTTMock(ca_cert_mock, not_found_mock): + cert_data = SmtpCertDownloader(self._provider, self._auth).download() + + self.assertEqual(CERTIFICATE_DATA, cert_data) + + def test_error_if_not_found(self): + downloader = SmtpCertDownloader(self._provider, self._auth) + with HTTMock(not_found_mock): + self.assertRaises(HTTPError, downloader.download) + + def test_download_to(self): + downloader = SmtpCertDownloader(self._provider, self._auth) + + with NamedTemporaryFile() as tmp_file: + with HTTMock(ca_cert_mock, not_found_mock): + downloader.download_to(tmp_file.name) + + file_content = open(tmp_file.name).read() + self.assertEqual(CERTIFICATE_DATA, file_content) -- cgit v1.2.3