[infrae.fileupload][Sylvain Viollon] Prevent bug.

[email protected]
Newsgroups gmane.comp.web.zope.silva.cvs
Message-ID <[email protected]>
author:    Sylvain Viollon
date:      Fri Aug 23 12:59:40 2013 +0200
revision:  21:fca2860100fc in infrae.fileupload
branch:    
details:   https://hg.infrae.com/infrae.fileupload?cmd=changeset;node=fca2860100fc
modified:  src/infrae/fileupload/middleware.py
added:     
removed:   
log:       Prevent bug.

Subject: [infrae.fileupload][Sylvain Viollon] Add some basic security.


author:    Sylvain Viollon
date:      Fri Aug 23 16:14:58 2013 +0200
revision:  22:91e201463abc in infrae.fileupload
branch:    
details:   https://hg.infrae.com/infrae.fileupload?cmd=changeset;node=91e201463abc
modified:  src/infrae/fileupload/middleware.py
added:     
removed:   
log:       Add some basic security.


diffstat:

 src/infrae/fileupload/middleware.py |  72 +++++++++++++++++++++++++++++++-----
 1 files changed, 62 insertions(+), 10 deletions(-)

diffs (171 lines):

diff -r 559b71701ece -r 91e201463abc src/infrae/fileupload/middleware.py
--- a/src/infrae/fileupload/middleware.py	Mon Aug 19 18:58:13 2013 +0200
+++ b/src/infrae/fileupload/middleware.py	Fri Aug 23 16:14:58 2013 +0200
@@ -2,17 +2,19 @@
 
 import cgi
 import fcntl
+import hmac
 import io
 import json
 import logging
 import os
 import re
 import shutil
+import uuid
 
 from webob import Request, Response, exc
 
 logger = logging.getLogger('infrae.fileupload')
-VALID_ID = re.compile(r'^[a-zA-Z0-9=-]*$')
+VALID_ID = re.compile(r'^[a-zA-Z0-9:-]*$')
 IDENTIFIER_KEY = 'X-Progress-ID'
 BLOCK_SIZE = 16 * 1024 * 1024
 
@@ -52,6 +54,10 @@
     msg = 'Unknown upload error'
 
 
+class InvalidIdentifierError(UploadError):
+    msg = 'Provided upload identifier was invalid'
+
+
 class RequestError(UploadError):
 
     def __init__(self, msg):
@@ -91,7 +97,8 @@
     """Manage the upload directory.
     """
 
-    def __init__(self, directory, upload_url=None):
+    def __init__(self, directory, upload_url=None, upload_key=None):
+        self._key = upload_key
         self._directory = directory
         self._lock = os.path.join(directory, 'upload.lock')
         self._upload_url = upload_url
@@ -113,7 +120,36 @@
                     return None
             return factory(self, identifier, path)
 
+    def _check_identifier(self, identifier):
+        if identifier is None and not VALID_ID.match(identifier):
+            return None
+        if ':' in identifier:
+            identifier, user_key = identifier.split(':', 1)
+            if self._key:
+                expected_key = hmac.new(self._key, identifier).hexdigest()
+                if user_key == expected_key:
+                    return identifier
+            # There was a key but non was configured.
+            return None
+        else:
+            if self._key:
+                # Key was provided but not present in user input.
+                return None
+        return identifier
+
+    def verify_identifier(self, identifier):
+        return self._check_identifier(identifier) != None
+
+    def create_identifier(self):
+        identifier = str(uuid.uuid1())
+        if self._key:
+            identifier += ':' + hmac.new(self._key, identifier).hexdigest()
+        return identifier
+
     def create_upload_bucket(self, identifier, *args):
+        identifier = self._check_identifier(identifier)
+        if identifier is None:
+            raise InvalidIdentifierError()
         return self._get_upload_bucket(
             True,
             identifier,
@@ -121,9 +157,15 @@
                 api, identifier, path, *args))
 
     def access_upload_bucket(self, identifier):
+        identifier = self._check_identifier(identifier)
+        if identifier is None:
+            raise InvalidIdentifierError()
         return self._get_upload_bucket(False, identifier, FileBucket)
 
     def clear_upload_bucket(self, identifier):
+        identifier = self._check_identifier(identifier)
+        if identifier is None:
+            raise InvalidIdentifierError()
         path = os.path.join(self._directory, identifier)
         with self._get_lock():
             if os.path.isdir(path):
@@ -171,7 +213,11 @@
 
     def clear(self):
         # This is called by the middleware if the upload fails.
-        self._api.clear_upload_bucket(self._identifier)
+        path = os.path.join(self._api._directory, self._identifier)
+        with self._api._get_lock():
+            if os.path.isdir(path):
+                shutil.rmtree(path)
+
 
     def is_complete(self):
         status = self.get_status()
@@ -251,7 +297,10 @@
             except GeneratorExit:
                 self._data_descriptor.close()
                 raise StopIteration
-            self._data_descriptor.write(block)
+            if isinstance(block, str):
+                self._data_descriptor.write(block)
+            else:
+                logger.error('Received invalid data to write: %r', block)
 
 
 class Reader(object):
@@ -298,9 +347,11 @@
     file upload progress.
     """
 
-    def __init__(self, application, directory, max_size=None, upload_url=None):
+    def __init__(self, application, directory, max_size=None,
+             upload_url=None, upload_key=None):
         self.application = application
-        self.manager = UploadManager(directory, upload_url=upload_url)
+        self.manager = UploadManager(
+            directory, upload_url=upload_url, upload_key=upload_key)
         self.max_size = max_size
 
     def __call__(self, environ, start_response):
@@ -312,7 +363,7 @@
             if request.path_info.endswith('/upload'):
                 identifier = request.GET.get(IDENTIFIER_KEY)
 
-                if identifier is None or not VALID_ID.match(identifier):
+                if not self.manager.verify_identifier(identifier):
                     logger.error('Malformed upload identifier "%s"', identifier)
                     application = exc.HTTPServerError(
                         'Malformed upload identifier')
@@ -327,7 +378,7 @@
             elif request.path_info.endswith('/upload/status'):
                 identifier = request.GET.get(IDENTIFIER_KEY)
 
-                if identifier is None or not VALID_ID.match(identifier):
+                if not self.manager.verify_identifier(identifier):
                     logger.error('Malformed upload identifier "%s"', identifier)
                     application = exc.HTTPServerError(
                         'Malformed upload identifier')
@@ -474,7 +525,7 @@
 
 
 def make_filter(application, global_conf, directory,
-                max_size=0, upload_url=None):
+                max_size=0, upload_url=None, upload_key=None):
     """build a FileUpload application
     """
     directory = os.path.normpath(directory)
@@ -493,4 +544,5 @@
         logger.info('Uploading to external URL: %s' % upload_url)
 
     return UploadMiddleware(
-        application, directory, max_size=max_size, upload_url=upload_url)
+        application, directory, max_size=max_size,
+        upload_url=upload_url, upload_key=upload_key)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.