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.