summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Shyba <victor1984@riseup.net>2017-08-31 03:17:19 -0300
committerVictor Shyba <victor1984@riseup.net>2017-09-02 05:59:11 -0300
commit83f386ea7258e9ecb92c3d5dbcb09ed514f437b4 (patch)
tree7f1971f8ebab32293e43006874b3e13296c41baa
parent5b754b03d80be6b820350fdc7cbec3219077476b (diff)
[feature] add support for soledad incoming api
-- Related: #8664
-rw-r--r--pkg/mx.tac10
-rw-r--r--pkg/requirements-leap.pip1
-rw-r--r--src/leap/mx/mail_receiver.py13
-rw-r--r--src/leap/mx/soledadhelper.py89
-rw-r--r--src/leap/mx/tests/tester.py9
5 files changed, 115 insertions, 7 deletions
diff --git a/pkg/mx.tac b/pkg/mx.tac
index 42d40a8..fe01a0e 100644
--- a/pkg/mx.tac
+++ b/pkg/mx.tac
@@ -21,6 +21,7 @@ import ConfigParser
from functools import partial
from leap.mx import couchdbhelper
+from leap.mx import soledadhelper
from leap.mx.mail_receiver import MailReceiver
from leap.mx.alias_resolver import AliasResolverFactory
from leap.mx.check_recipient_access import CheckRecipientAccessFactory
@@ -66,6 +67,11 @@ cdb = couchdbhelper.ConnectedCouchDB(server,
username=user,
password=password)
+incoming_api = False
+if config.has_section("incoming api"):
+ args = [config.get("incoming api", option) for option in ["host", "port", "token"]]
+ incoming_api = soledadhelper.SoledadIncomingAPI(*args)
+
application = service.Application("LEAP MX")
@@ -91,11 +97,11 @@ fingerprint_map.setServiceParent(application)
directories = []
for section in config.sections():
if section in ("couchdb", "alias map", "check recipient",
- "fingerprint map", "bounce"):
+ "fingerprint map", "bounce", "incoming api"):
continue
to_watch = config.get(section, "path")
recursive = config.getboolean(section, "recursive")
directories.append([to_watch, recursive])
-mr = MailReceiver(cdb, directories, bounce_from, bounce_subject)
+mr = MailReceiver(cdb, directories, bounce_from, bounce_subject, incoming_api)
mr.setServiceParent(application)
diff --git a/pkg/requirements-leap.pip b/pkg/requirements-leap.pip
index 1cfe861..da61bde 100644
--- a/pkg/requirements-leap.pip
+++ b/pkg/requirements-leap.pip
@@ -1,2 +1,3 @@
leap.common>=0.5.1
leap.soledad.common>=0.8.0
+treq
diff --git a/src/leap/mx/mail_receiver.py b/src/leap/mx/mail_receiver.py
index 276ae13..3b3997a 100644
--- a/src/leap/mx/mail_receiver.py
+++ b/src/leap/mx/mail_receiver.py
@@ -83,7 +83,7 @@ class MailReceiver(Service):
MAX_BOUNCE_DELTA = timedelta(days=5)
def __init__(self, users_cdb, directories, bounce_from,
- bounce_subject):
+ bounce_subject, incoming_api_helper=False):
"""
Constructor
@@ -107,6 +107,7 @@ class MailReceiver(Service):
self._bounce_subject = bounce_subject
self._bounce_timestamp = {}
self._processing_skipped = False
+ self._incoming_api = incoming_api_helper
def startService(self):
"""
@@ -237,8 +238,14 @@ class MailReceiver(Service):
"I know: %r | %r" % (uuid, doc))
raise Exception("No uuid or doc")
- log.msg("Exporting message for %s" % (uuid,))
- yield self._users_cdb.put_doc(uuid, doc)
+ if self._incoming_api:
+ log.msg("Exporting message for %s over Incoming API" % (uuid,))
+ # TODO: Stop using ServerDocument when old code gets deprecated
+ content = doc.content[ENC_JSON_KEY]
+ yield self._incoming_api.put_doc(uuid, doc.doc_id, content)
+ else:
+ log.msg("Exporting message for %s directly into CouchDB" % (uuid,))
+ yield self._users_cdb.put_doc(uuid, doc)
log.msg("Done exporting")
def _remove(self, filepath):
diff --git a/src/leap/mx/soledadhelper.py b/src/leap/mx/soledadhelper.py
new file mode 100644
index 0000000..4b2a099
--- /dev/null
+++ b/src/leap/mx/soledadhelper.py
@@ -0,0 +1,89 @@
+# -*- encoding: utf-8 -*-
+# soledadhelper.py
+# Copyright (C) 2017 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+"""
+Classes for working with Soledad Incoming API.
+See: http://soledad.readthedocs.io/en/latest/incoming_box.html
+"""
+
+
+import base64
+import treq
+from six import raise_from
+from io import BytesIO
+from twisted.internet import defer
+
+
+class UnavailableIncomingAPIException(Exception):
+ pass
+
+
+class SoledadIncomingAPI:
+ """
+ Delivers messages using Soledad Incoming API.
+ """
+
+ def __init__(self, host, port, token):
+ """
+ Creates a SoledadIncomingAPI helper to deliver messages into user's
+ database.
+
+ :param host: A hostname string for the Soledad incoming service host.
+ This will usually be localhost, unless served over stunnel.
+ :type host: str
+ :param port: The port of the Soledad incoming service host.
+ :type port: int
+ :param token: Incoming service authentication token as configured in
+ Soledad.
+ :type token: str
+ """
+ self._incoming_url = "http://%s:%s/incoming/" % (host, port)
+ b64_token = base64.b64encode(token)
+ self._auth_header = {'Authorization': ['Token %s' % b64_token]}
+
+ @defer.inlineCallbacks
+ def put_doc(self, uuid, doc_id, content):
+ """
+ Make a PUT request to Soledad's incoming API, delivering a message into
+ user's database.
+
+ :param uuid: The uuid of a user
+ :type uuid: str
+ :param content: Message content.
+ :type content: str
+
+ :return: A deferred which fires after the HTTP request is complete, or
+ which fails with the correspondent exception if there was any
+ error.
+ """
+ url = self._incoming_url + "user-%s/%s" % (uuid, doc_id)
+ try:
+ response = yield treq.put(
+ url,
+ BytesIO(str(content)),
+ headers=self._auth_header,
+ persistent=False)
+ except Exception as original_exception:
+ error_message = "Server unreacheable or unknown error: %s"
+ error_message %= (original_exception.message)
+ our_exception = UnavailableIncomingAPIException(error_message)
+ raise_from(our_exception, original_exception)
+ if not response.code == 200:
+ error_message = '%s returned status %s instead of 200'
+ error_message %= (url, response.code)
+ raise UnavailableIncomingAPIException(error_message)
diff --git a/src/leap/mx/tests/tester.py b/src/leap/mx/tests/tester.py
index 05d2d05..8ca0987 100644
--- a/src/leap/mx/tests/tester.py
+++ b/src/leap/mx/tests/tester.py
@@ -2,10 +2,11 @@ import ConfigParser
import sys
import os
-from twisted.internet import reactor, defer
+from twisted.internet import reactor
from twisted.python import filepath, log
from leap.mx import couchdbhelper
+from leap.mx import soledadhelper
from leap.mx.mail_receiver import MailReceiver
if __name__ == "__main__":
@@ -30,6 +31,10 @@ if __name__ == "__main__":
dbName="identities",
username=user,
password=password)
+ incoming_api = False
+ if config.has_section("incoming api"):
+ args = (config.get(option) for option in ["host", "port", "token"])
+ incoming_api = soledadhelper.SoledadIncomingAPI(*args)
# Mail receiver
mail_couch_url_prefix = "http://%s:%s@%s:%s" % (user,
@@ -37,7 +42,7 @@ if __name__ == "__main__":
server,
port)
- mr = MailReceiver(mail_couch_url_prefix, cdb, [])
+ mr = MailReceiver(mail_couch_url_prefix, cdb, [], incoming_api)
fpath = filepath.FilePath(fullpath)
d = mr._process_incoming_email(None, fpath, 0)