diff options
-rw-r--r-- | server/src/leap/soledad/server/_blobs.py | 98 | ||||
-rw-r--r-- | server/src/leap/soledad/server/interfaces.py | 72 | ||||
-rw-r--r-- | testing/tests/blobs/test_fs_backend.py | 2 |
3 files changed, 99 insertions, 73 deletions
diff --git a/server/src/leap/soledad/server/_blobs.py b/server/src/leap/soledad/server/_blobs.py index 44abeb44..74707111 100644 --- a/server/src/leap/soledad/server/_blobs.py +++ b/server/src/leap/soledad/server/_blobs.py @@ -36,74 +36,28 @@ from twisted.web.client import FileBodyProducer from twisted.web.server import NOT_DONE_YET from twisted.internet import utils, defer -from zope.interface import Interface, implementer +from zope.interface import implementer + +from leap.common.files import mkdir_p +from leap.soledad.server import interfaces __all__ = ['BlobsResource'] logger = Logger() + # Used for sanitizers, we accept only letters, numbers, '-' and '_' VALID_STRINGS = re.compile('^[a-zA-Z0-9_-]+$') -# TODO some error handling needed -# [ ] sanitize path - # for the future: # [ ] isolate user avatar in a safer way # [ ] catch timeout in the server (and delete incomplete upload) # [ ] chunking (should we do it on the client or on the server?) -class IBlobsBackend(Interface): - - """ - An interface for a BlobsBackend. - """ - - def read_blob(user, blob_id, request): - """ - Read blob with a given blob_id, and write it to the passed request. - - :returns: a deferred that fires upon finishing. - """ - - def list_blobs(user, request): - """ - Returns a json-encoded list of ids from user's blob. - - :returns: a deferred that fires upon finishing. - """ - - def tag_header(user, blob_id, request): - """ - Adds a header 'Tag' with the last 16 bytes of the encoded file, - which contains the tag. - - :returns: a deferred that fires upon finishing. - """ - - def write_blob(user, blob_id, request): - """ - Write blob to the storage, reading it from the passed request. - - :returns: a deferred that fires upon finishing. - """ - - # other stuff for the API - - def delete_blob(user, blob_id): - pass - - def get_blob_size(user, blob_id): - pass - - def get_total_storage(user): - pass - - -@implementer(IBlobsBackend) +@implementer(interfaces.IBlobsBackend) class FilesystemBlobsBackend(object): def __init__(self, blobs_path='/tmp/blobs/', quota=200 * 1024): @@ -112,19 +66,6 @@ class FilesystemBlobsBackend(object): os.makedirs(blobs_path) self.path = blobs_path - def list_blobs(self, user, request): - blob_ids = [] - base_path = self._get_path(user) - for _, _, filenames in os.walk(base_path): - blob_ids += filenames - return json.dumps(blob_ids) - - def tag_header(self, user, blob_id, request): - with open(self._get_path(user, blob_id)) as doc_file: - doc_file.seek(-16, 2) - tag = base64.urlsafe_b64encode(doc_file.read()) - request.responseHeaders.setRawHeaders('Tag', [tag]) - def read_blob(self, user, blob_id, request): logger.info('reading blob: %s - %s' % (user, blob_id)) path = self._get_path(user, blob_id) @@ -136,8 +77,8 @@ class FilesystemBlobsBackend(object): def write_blob(self, user, blob_id, request): path = self._get_path(user, blob_id) try: - os.makedirs(os.path.split(path)[0]) - except: + mkdir_p(os.path.split(path)[0]) + except OSError: pass if os.path.isfile(path): # 409 - Conflict @@ -154,9 +95,6 @@ class FilesystemBlobsBackend(object): fbp = FileBodyProducer(request.content) yield fbp.startProducing(open(path, 'wb')) - def get_total_storage(self, user): - return self._get_disk_usage(self._get_path(user)) - def delete_blob(self, user, blob_id): blob_path = self._get_path(user, blob_id) os.unlink(blob_path) @@ -164,6 +102,22 @@ class FilesystemBlobsBackend(object): def get_blob_size(user, blob_id): raise NotImplementedError + def list_blobs(self, user, request): + blob_ids = [] + base_path = self._get_path(user) + for _, _, filenames in os.walk(base_path): + blob_ids += filenames + return json.dumps(blob_ids) + + def get_total_storage(self, user): + return self._get_disk_usage(self._get_path(user)) + + def add_tag_header(self, user, blob_id, request): + with open(self._get_path(user, blob_id)) as doc_file: + doc_file.seek(-16, 2) + tag = base64.urlsafe_b64encode(doc_file.read()) + request.responseHeaders.setRawHeaders('Tag', [tag]) + @defer.inlineCallbacks def _get_disk_usage(self, start_path): if not os.path.isdir(start_path): @@ -207,7 +161,7 @@ class BlobsResource(resource.Resource): resource.Resource.__init__(self) self._blobs_path = blobs_path self._handler = self.blobsFactoryClass(blobs_path) - assert IBlobsBackend.providedBy(self._handler) + assert interfaces.IBlobsBackend.providedBy(self._handler) # TODO double check credentials, we can have then # under request. @@ -217,7 +171,7 @@ class BlobsResource(resource.Resource): user, blob_id = self._validate(request) if not blob_id: return self._handler.list_blobs(user, request) - self._handler.tag_header(user, blob_id, request) + self._handler.add_tag_header(user, blob_id, request) return self._handler.read_blob(user, blob_id, request) def render_DELETE(self, request): diff --git a/server/src/leap/soledad/server/interfaces.py b/server/src/leap/soledad/server/interfaces.py new file mode 100644 index 00000000..67b04bc3 --- /dev/null +++ b/server/src/leap/soledad/server/interfaces.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# interfaces.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/>. + + +from zope.interface import Interface + + +class IBlobsBackend(Interface): + + """ + An interface for a BlobsBackend. + """ + + def read_blob(user, blob_id, request): + """ + Read blob with a given blob_id, and write it to the passed request. + + :returns: a deferred that fires upon finishing. + """ + + def write_blob(user, blob_id, request): + """ + Write blob to the storage, reading it from the passed request. + + :returns: a deferred that fires upon finishing. + """ + + def delete_blob(user, blob_id): + """ + Delete the given blob_id. + """ + + def get_blob_size(user, blob_id): + """ + Get the size of the given blob id. + """ + + def list_blobs(user, request): + """ + Returns a json-encoded list of ids from user's blob. + + :returns: a deferred that fires upon finishing. + """ + + def get_total_storage(user): + """ + Get the size used by a given user as the sum of all the blobs stored + unders its namespace. + """ + + def add_tag_header(user, blob_id, request): + """ + Adds a header 'Tag' to the passed request object, containing the last + 16 bytes of the encoded blob, which according to the spec contains the + tag. + + :returns: a deferred that fires upon finishing. + """ diff --git a/testing/tests/blobs/test_fs_backend.py b/testing/tests/blobs/test_fs_backend.py index 83a5708e..27d4fc61 100644 --- a/testing/tests/blobs/test_fs_backend.py +++ b/testing/tests/blobs/test_fs_backend.py @@ -39,7 +39,7 @@ class FilesystemBackendTestCase(unittest.TestCase): expected_method = Mock() backend = _blobs.FilesystemBlobsBackend() request = Mock(responseHeaders=Mock(setRawHeaders=expected_method)) - backend.tag_header('user', 'blob_id', request) + backend.add_tag_header('user', 'blob_id', request) expected_method.assert_called_once_with('Tag', [expected_tag]) |