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]) | 
