summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Shyba <victor1984@riseup.net>2017-07-05 02:17:38 -0300
committerdrebs <drebs@leap.se>2017-07-18 15:22:23 -0300
commit361eb8d7121cc8abb94d9fef784d5ff2b27722d0 (patch)
tree54f1fd298522303aec74a9a622e39f0cfb6800f5
parentec8d1f5aedcd077b5b1521e15b1432e616f83f3a (diff)
[feature] add blobs as a incoming api backend
We started with CouchDB due legacy system relying on it. This commit adds the possibility of adding blobs as a IncomingAPI backend if blobs is enabled on config file. -- Resolves: #8868
-rw-r--r--src/leap/soledad/server/_blobs.py20
-rw-r--r--src/leap/soledad/server/_incoming.py45
-rw-r--r--testing/tests/server/test_incoming_server.py42
3 files changed, 92 insertions, 15 deletions
diff --git a/src/leap/soledad/server/_blobs.py b/src/leap/soledad/server/_blobs.py
index f87c3818..87b171fb 100644
--- a/src/leap/soledad/server/_blobs.py
+++ b/src/leap/soledad/server/_blobs.py
@@ -237,3 +237,23 @@ if __name__ == '__main__':
factory = Site(root)
reactor.listenTCP(args.port, factory)
reactor.run()
+
+
+class BlobsServerState(object):
+ """
+ Given a backend name, it gives a instance of IBlobsBackend
+ """
+ # Allowed backend classes are defined here
+ handlers = {"filesystem": FilesystemBlobsBackend}
+
+ def __init__(self, backend, **backend_kwargs):
+ if backend not in self.handlers:
+ raise ImproperlyConfiguredException("No such backend: %s", backend)
+ self.backend = self.handlers[backend](**backend_kwargs)
+
+ def open_database(self, user_id):
+ """
+ That method is just for compatibility with CouchServerState, so
+ IncomingAPI can change backends.
+ """
+ return self.backend
diff --git a/src/leap/soledad/server/_incoming.py b/src/leap/soledad/server/_incoming.py
index 6fd91a08..6fdeb7ae 100644
--- a/src/leap/soledad/server/_incoming.py
+++ b/src/leap/soledad/server/_incoming.py
@@ -17,38 +17,61 @@
"""
A twisted resource that saves externally delivered documents into user's db.
"""
+from twisted.web.server import NOT_DONE_YET
from twisted.web.resource import Resource
from ._config import get_config
+from io import BytesIO
+from leap.soledad.server._blobs import BlobsServerState
from leap.soledad.common.couch.state import CouchServerState
from leap.soledad.common.document import ServerDocument
from leap.soledad.common.crypto import ENC_JSON_KEY
from leap.soledad.common.crypto import ENC_SCHEME_KEY
from leap.soledad.common.crypto import EncryptionSchemes
+from leap.soledad.common import preamble
__all__ = ['IncomingResource']
-def _default_backend():
+def _get_backend_from_config():
conf = get_config()
- return CouchServerState(conf['couch_url'], create_cmd=conf['create_cmd'])
+ if conf['blobs']:
+ return BlobsServerState("filesystem", conf['blobs_path'])
+ return CouchServerState(conf['couch_url'])
class IncomingResource(Resource):
isLeaf = True
def __init__(self, backend_factory=None):
- self.factory = backend_factory or _default_backend()
+ self.factory = backend_factory or _get_backend_from_config()
self.formatter = IncomingFormatter()
def render_PUT(self, request):
uuid, doc_id = request.postpath
scheme = EncryptionSchemes.PUBKEY
db = self.factory.open_database(uuid)
- doc = ServerDocument(doc_id)
- doc.content = self.formatter.format(request.content.read(), scheme)
- db.put_doc(doc)
- return '{"success": true}'
+ if hasattr(db, 'put_doc'):
+ doc = ServerDocument(doc_id)
+ doc.content = self.formatter.format(request.content.read(), scheme)
+ db.put_doc(doc)
+ self._finish(request)
+ else:
+ raw_content = request.content.read()
+ preamble = self.formatter.preamble(raw_content, doc_id)
+ request.content = BytesIO(preamble + raw_content)
+ d = db.write_blob(uuid, doc_id, request, namespace='incoming')
+ d.addCallback(lambda _: self._finish(request))
+ return NOT_DONE_YET
+
+ def _finish(self, request):
+ request.write('{"success": true}')
+ request.finish()
+
+ def _error(self, e, request):
+ request.write('{"success": false}')
+ request.setResponseCode(500)
+ request.finish()
class IncomingFormatter(object):
@@ -65,3 +88,11 @@ class IncomingFormatter(object):
self.ERROR_DECRYPTING_KEY: False,
ENC_SCHEME_KEY: EncryptionSchemes.NONE,
ENC_JSON_KEY: raw_content}
+
+ def preamble(self, raw_content, doc_id):
+ rev = '0'
+ scheme = preamble.ENC_SCHEME.external
+ method = preamble.ENC_METHOD.pgp
+ size = len(raw_content)
+ return preamble.Preamble(doc_id, rev, scheme, method,
+ content_size=size).encode()
diff --git a/testing/tests/server/test_incoming_server.py b/testing/tests/server/test_incoming_server.py
index 64ac4353..78bc86a8 100644
--- a/testing/tests/server/test_incoming_server.py
+++ b/testing/tests/server/test_incoming_server.py
@@ -20,36 +20,46 @@ Integration tests for incoming API
import pytest
from io import BytesIO
from uuid import uuid4
+from twisted.web.test.test_web import DummyRequest
from twisted.web.server import Site
from twisted.internet import reactor
from twisted.internet import defer
import treq
from leap.soledad.server._incoming import IncomingResource
+from leap.soledad.server._blobs import BlobsServerState
from leap.soledad.server._incoming import IncomingFormatter
from leap.soledad.common.crypto import EncryptionSchemes
from test_soledad.util import CouchServerStateForTests
from test_soledad.util import CouchDBTestCase
-class IncomingServerTestCase(CouchDBTestCase):
+class IncomingOnCouchServerTestCase(CouchDBTestCase):
def setUp(self):
- self.state = CouchServerStateForTests(self.couch_url)
+ self.port = None
+
+ def tearDown(self):
+ if self.port:
+ self.port.stopListening()
+
+ def prepare(self, backend):
+ self.user_id = 'user-' + uuid4().hex
+ if backend == 'couch':
+ self.state = CouchServerStateForTests(self.couch_url)
+ self.state.ensure_database(self.user_id)
+ else:
+ self.state = BlobsServerState(backend)
root = IncomingResource(self.state)
site = Site(root)
self.port = reactor.listenTCP(0, site, interface='127.0.0.1')
self.host = self.port.getHost()
self.uri = 'http://%s:%s/' % (self.host.host, self.host.port)
- self.user_id = 'user-' + uuid4().hex
- self.state.ensure_database(self.user_id)
-
- def tearDown(self):
- self.port.stopListening()
@defer.inlineCallbacks
@pytest.mark.usefixtures("method_tmpdir")
- def test_put_incoming_creates_a_document(self):
+ def test_put_incoming_creates_a_document_using_couch(self):
+ self.prepare('couch')
user_id, doc_id = self.user_id, uuid4().hex
content, scheme = 'Hi', EncryptionSchemes.PUBKEY
formatter = IncomingFormatter()
@@ -59,3 +69,19 @@ class IncomingServerTestCase(CouchDBTestCase):
doc = db.get_doc(doc_id)
self.assertEquals(doc.content, formatter.format(content, scheme))
+
+ @defer.inlineCallbacks
+ @pytest.mark.usefixtures("method_tmpdir")
+ def test_put_incoming_creates_a_blob_using_filesystem(self):
+ self.prepare('filesystem')
+ user_id, doc_id = self.user_id, uuid4().hex
+ content = 'Hi'
+ formatter = IncomingFormatter()
+ incoming_endpoint = self.uri + '%s/%s' % (user_id, doc_id)
+ yield treq.put(incoming_endpoint, BytesIO(content), persistent=False)
+
+ db = self.state.open_database(user_id)
+ request = DummyRequest([user_id, doc_id])
+ yield db.read_blob(user_id, doc_id, request, 'incoming')
+ expected = formatter.preamble(content, doc_id) + content
+ self.assertEquals(expected, request.written[0])