From c0532a6ef24965584206ddb952c4ee7f1379398e Mon Sep 17 00:00:00 2001 From: gholt Date: Tue, 8 May 2012 19:48:29 +0000 Subject: [PATCH] Pulled out TempURL/FormPOST TempURL/FormPOST is now at http://gholt.github.com/swift-tempurl/ For current users of TempURL/FormPOST, this will require installing the new package and changing the "use" line of the tempurl and formpost conf section's to: [filter:tempurl] use = egg:swifttempurl#tempurl [filter:formpost] use = egg:swifttempurl#formpost And then 'swift-init proxy reload'. Change-Id: I5bddf7f9e09ee07815530a41c46ff901fc21b447 --- bin/swift-form-signature | 70 - bin/swift-temp-url | 59 - doc/manpages/proxy-server.conf.5 | 34 - doc/source/associated_projects.rst | 1 + doc/source/misc.rst | 14 - etc/proxy-server.conf-sample | 30 - setup.py | 4 - swift/common/middleware/formpost.py | 543 ------- swift/common/middleware/tempurl.py | 490 ------ test/unit/common/middleware/test_formpost.py | 1447 ------------------ test/unit/common/middleware/test_tempurl.py | 655 -------- 11 files changed, 1 insertion(+), 3346 deletions(-) delete mode 100755 bin/swift-form-signature delete mode 100755 bin/swift-temp-url delete mode 100644 swift/common/middleware/formpost.py delete mode 100644 swift/common/middleware/tempurl.py delete mode 100644 test/unit/common/middleware/test_formpost.py delete mode 100644 test/unit/common/middleware/test_tempurl.py diff --git a/bin/swift-form-signature b/bin/swift-form-signature deleted file mode 100755 index 1226897a55..0000000000 --- a/bin/swift-form-signature +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python - -import hmac -from hashlib import sha1 -from os.path import basename -from sys import argv, exit -from time import time - - -if __name__ == '__main__': - if len(argv) != 7: - prog = basename(argv[0]) - print 'Syntax: %s ' \ - ' ' % prog - print - print 'Where:' - print ' The prefix to use for form uploaded' - print ' objects. For example:' - print ' /v1/account/container/object_prefix_ would' - print ' ensure all form uploads have that path' - print ' prepended to the browser-given file name.' - print ' The URL to redirect the browser to after' - print ' the uploads have completed.' - print ' The maximum file size per file uploaded.' - print ' The maximum number of uploaded files' - print ' allowed.' - print ' The number of seconds from now to allow' - print ' the form post to begin.' - print ' The X-Account-Meta-Temp-URL-Key for the' - print ' account.' - print - print 'Example output:' - print ' Expires: 1323842228' - print ' Signature: 18de97e47345a82c4dbfb3b06a640dbb' - exit(1) - path, redirect, max_file_size, max_file_count, seconds, key = argv[1:] - try: - max_file_size = int(max_file_size) - except ValueError: - max_file_size = -1 - if max_file_size < 0: - print 'Please use a value greater than or equal to 0.' - exit(1) - try: - max_file_count = int(max_file_count) - except ValueError: - max_file_count = 0 - if max_file_count < 1: - print 'Please use a positive value.' - exit(1) - try: - expires = int(time() + int(seconds)) - except ValueError: - expires = 0 - if expires < 1: - print 'Please use a positive value.' - exit(1) - parts = path.split('/', 4) - # Must be four parts, ['', 'v1', 'a', 'c'], must be a v1 request, have - # account and container values, and optionally have an object prefix. - if len(parts) < 4 or parts[0] or parts[1] != 'v1' or not parts[2] or \ - not parts[3]: - print ' must point to a container at least.' - print 'For example: /v1/account/container' - print ' Or: /v1/account/container/object_prefix' - exit(1) - sig = hmac.new(key, '%s\n%s\n%s\n%s\n%s' % (path, redirect, max_file_size, - max_file_count, expires), sha1).hexdigest() - print ' Expires:', expires - print 'Signature:', sig diff --git a/bin/swift-temp-url b/bin/swift-temp-url deleted file mode 100755 index da7595a753..0000000000 --- a/bin/swift-temp-url +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python - -import hmac -from hashlib import sha1 -from os.path import basename -from sys import argv, exit -from time import time - - -if __name__ == '__main__': - if len(argv) != 5: - prog = basename(argv[0]) - print 'Syntax: %s ' % prog - print - print 'Where:' - print ' The method to allow, GET or PUT.' - print ' Note: HEAD will also be allowed.' - print ' The number of seconds from now to allow requests.' - print ' The full path to the resource.' - print ' Example: /v1/AUTH_account/c/o' - print ' The X-Account-Meta-Temp-URL-Key for the account.' - print - print 'Example output:' - print ' /v1/AUTH_account/c/o?temp_url_sig=34d49efc32fe6e3082e411e' \ - 'eeb85bd8a&temp_url_expires=1323482948' - print - print 'This can be used to form a URL to give out for the access ' - print 'allowed. For example:' - print ' echo https://swift-cluster.example.com`%s GET 60 ' \ - '/v1/AUTH_account/c/o mykey`' % prog - print - print 'Might output:' - print ' https://swift-cluster.example.com/v1/AUTH_account/c/o?' \ - 'temp_url_sig=34d49efc32fe6e3082e411eeeb85bd8a&' \ - 'temp_url_expires=1323482948' - exit(1) - method, seconds, path, key = argv[1:] - if method not in ('GET', 'PUT'): - print 'Please use either the GET or PUT method.' - exit(1) - try: - expires = int(time() + int(seconds)) - except ValueError: - expires = 0 - if expires < 1: - print 'Please use a positive value.' - exit(1) - parts = path.split('/', 4) - # Must be five parts, ['', 'v1', 'a', 'c', 'o'], must be a v1 request, have - # account, container, and object values, and the object value can't just - # have '/'s. - if len(parts) != 5 or parts[0] or parts[1] != 'v1' or not parts[2] or \ - not parts[3] or not parts[4].strip('/'): - print ' must point to an object.' - print 'For example: /v1/account/container/object' - exit(1) - sig = hmac.new(key, '%s\n%s\n%s' % (method, expires, path), - sha1).hexdigest() - print '%s?temp_url_sig=%s&temp_url_expires=%s' % (path, sig, expires) diff --git a/doc/manpages/proxy-server.conf.5 b/doc/manpages/proxy-server.conf.5 index 109f053941..9ab793dbc7 100644 --- a/doc/manpages/proxy-server.conf.5 +++ b/doc/manpages/proxy-server.conf.5 @@ -336,40 +336,6 @@ The default is 1. -.RS 0 -.IP "\fB[filter:tempurl]\fR" -.RE - -Note: Put tempurl just before your auth filter(s) in the pipeline - -.RS 3 -.IP \fBincoming_remove_headers\fR -The headers to remove from incoming requests. Simply a whitespace delimited list of header names and names can optionally end with '*' to indicate a prefix match. incoming_allow_headers is a list of exceptions to these removals. -.IP \fBincoming_allow_headers\fR -The headers allowed as exceptions to incoming_remove_headers. Simply a whitespace delimited list of header names and names can optionally end with '*' to indicate a prefix match. -.IP "\fBoutgoing_remove_headers\fR" -The headers to remove from outgoing responses. Simply a whitespace delimited list of header names and names can optionally end with '*' to indicate a prefix match. outgoing_allow_headers is a list of exceptions to these removals. -.IP "\fBoutgoing_allow_headers\fR" -The headers allowed as exceptions to outgoing_remove_headers. Simply a whitespace delimited list of header names and names can optionally end with '*' to indicate a prefix match. -.IP "\fBset log_level\fR " -.RE - - - -.RS 0 -.IP "\fB[filter:formpost]\fR" -.RE - -Note: Put formpost just before your auth filter(s) in the pipeline - -.RS 3 -.IP \fBuse\fR -Entry point for paste.deploy for the formpost middleware. This is the reference to the installed python egg. -The default is \fBegg:swift#formpost\fR. -.RE - - - .RS 0 .IP "\fB[filter:name_check]\fR" .RE diff --git a/doc/source/associated_projects.rst b/doc/source/associated_projects.rst index 4989296673..3e526e9b65 100644 --- a/doc/source/associated_projects.rst +++ b/doc/source/associated_projects.rst @@ -54,3 +54,4 @@ Other * `Glance `_ - Provides services for discovering, registering, and retrieving virtual machine images (for OpenStack Compute [Nova], for example). * `StaticWeb `_ - Allows serving static websites from Swift containers using ACLs and other metadata on those containers. +* `TempURL/FormPOST `_ - Temporary, Expiring URLs and Form POSTing middleware. diff --git a/doc/source/misc.rst b/doc/source/misc.rst index ee6c34830b..039b00831e 100644 --- a/doc/source/misc.rst +++ b/doc/source/misc.rst @@ -147,20 +147,6 @@ Swift3 :members: :show-inheritance: -TempURL -======= - -.. automodule:: swift.common.middleware.tempurl - :members: - :show-inheritance: - -FormPost -======== - -.. automodule:: swift.common.middleware.formpost - :members: - :show-inheritance: - Domain Remap ============ diff --git a/etc/proxy-server.conf-sample b/etc/proxy-server.conf-sample index 6d3fed05d5..aca205df76 100644 --- a/etc/proxy-server.conf-sample +++ b/etc/proxy-server.conf-sample @@ -192,36 +192,6 @@ use = egg:swift#cname_lookup # storage_domain = example.com # lookup_depth = 1 -# Note: Put tempurl just before your auth filter(s) in the pipeline -[filter:tempurl] -use = egg:swift#tempurl -# -# The headers to remove from incoming requests. Simply a whitespace delimited -# list of header names and names can optionally end with '*' to indicate a -# prefix match. incoming_allow_headers is a list of exceptions to these -# removals. -# incoming_remove_headers = x-timestamp -# -# The headers allowed as exceptions to incoming_remove_headers. Simply a -# whitespace delimited list of header names and names can optionally end with -# '*' to indicate a prefix match. -# incoming_allow_headers = -# -# The headers to remove from outgoing responses. Simply a whitespace delimited -# list of header names and names can optionally end with '*' to indicate a -# prefix match. outgoing_allow_headers is a list of exceptions to these -# removals. -# outgoing_remove_headers = x-object-meta-* -# -# The headers allowed as exceptions to outgoing_remove_headers. Simply a -# whitespace delimited list of header names and names can optionally end with -# '*' to indicate a prefix match. -# outgoing_allow_headers = x-object-meta-public-* - -# Note: Put formpost just before your auth filter(s) in the pipeline -[filter:formpost] -use = egg:swift#formpost - # Note: Just needs to be placed before the proxy-server in the pipeline. [filter:name_check] use = egg:swift#name_check diff --git a/setup.py b/setup.py index 66bf89d8db..598233e230 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,6 @@ setup( 'bin/swift-dispersion-populate', 'bin/swift-dispersion-report', 'bin/swift-drive-audit', - 'bin/swift-form-signature', 'bin/swift-get-nodes', 'bin/swift-init', 'bin/swift-object-auditor', @@ -71,7 +70,6 @@ setup( 'bin/swift-recon', 'bin/swift-recon-cron', 'bin/swift-ring-builder', - 'bin/swift-temp-url', ], entry_points={ 'paste.app_factory': [ @@ -90,8 +88,6 @@ setup( 'swift3=swift.common.middleware.swift3:filter_factory', 'tempauth=swift.common.middleware.tempauth:filter_factory', 'recon=swift.common.middleware.recon:filter_factory', - 'tempurl=swift.common.middleware.tempurl:filter_factory', - 'formpost=swift.common.middleware.formpost:filter_factory', 'name_check=swift.common.middleware.name_check:filter_factory', ], }, diff --git a/swift/common/middleware/formpost.py b/swift/common/middleware/formpost.py deleted file mode 100644 index 4680cf8f6f..0000000000 --- a/swift/common/middleware/formpost.py +++ /dev/null @@ -1,543 +0,0 @@ -# Copyright (c) 2011 OpenStack, LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -FormPost Middleware - -Translates a browser form post into a regular Swift object PUT. - -The format of the form is:: - -
- - - - - -
- -
- -The is the URL to the Swift desination, such as:: - - https://swift-cluster.example.com/v1/AUTH_account/container/object_prefix - -The name of each file uploaded will be appended to the -given. So, you can upload directly to the root of container with a -url like:: - - https://swift-cluster.example.com/v1/AUTH_account/container/ - -Optionally, you can include an object prefix to better separate -different users' uploads, such as:: - - https://swift-cluster.example.com/v1/AUTH_account/container/object_prefix - -Note the form method must be POST and the enctype must be set as -"multipart/form-data". - -The redirect attribute is the URL to redirect the browser to after -the upload completes. The URL will have status and message query -parameters added to it, indicating the HTTP status code for the -upload (2xx is success) and a possible message for further -information if there was an error (such as "max_file_size exceeded"). - -The max_file_size attribute must be included and indicates the -largest single file upload that can be done, in bytes. - -The max_file_count attribute must be included and indicates the -maximum number of files that can be uploaded with the form. Include -additional ```` attributes if -desired. - -The expires attribute is the Unix timestamp before which the form -must be submitted before it is invalidated. - -The signature attribute is the HMAC-SHA1 signature of the form. Here is -sample code for computing the signature:: - - import hmac - from hashlib import sha1 - from time import time - path = '/v1/account/container/object_prefix' - redirect = 'https://myserver.com/some-page' - max_file_size = 104857600 - max_file_count = 10 - expires = int(time() + 600) - key = 'mykey' - hmac_body = '%s\\n%s\\n%s\\n%s\\n%s' % (path, redirect, - max_file_size, max_file_count, expires) - signature = hmac.new(key, hmac_body, sha1).hexdigest() - -The key is the value of the X-Account-Meta-Temp-URL-Key header on the -account. - -Be certain to use the full path, from the /v1/ onward. - -The command line tool ``swift-form-signature`` may be used (mostly -just when testing) to compute expires and signature. - -Also note that the file attributes must be after the other attributes -in order to be processed correctly. If attributes come after the -file, they won't be sent with the subrequest (there is no way to -parse all the attributes on the server-side without reading the whole -thing into memory -- to service many requests, some with large files, -there just isn't enough memory on the server, so attributes following -the file are simply ignored). -""" - -__all__ = ['FormPost', 'filter_factory', 'READ_CHUNK_SIZE', 'MAX_VALUE_LENGTH'] - -import hmac -import re -import rfc822 -from hashlib import sha1 -from StringIO import StringIO -from time import gmtime, strftime, time -from time import time -from urllib import quote, unquote - -from swift.common.utils import get_logger, streq_const_time -from swift.common.wsgi import make_pre_authed_env -from swift.common.http import HTTP_BAD_REQUEST - - -#: The size of data to read from the form at any given time. -READ_CHUNK_SIZE = 4096 - -#: The maximum size of any attribute's value. Any additional data will be -#: truncated. -MAX_VALUE_LENGTH = 4096 - -#: Regular expression to match form attributes. -ATTRIBUTES_RE = re.compile(r'(\w+)=(".*?"|[^";]+)(; ?|$)') - - -class FormInvalid(Exception): - pass - - -def _parse_attrs(header): - """ - Given the value of a header like: - Content-Disposition: form-data; name="somefile"; filename="test.html" - - Return data like - ("form-data", {"name": "somefile", "filename": "test.html"}) - - :param header: Value of a header (the part after the ': '). - :returns: (value name, dict) of the attribute data parsed (see above). - """ - attributes = {} - attrs = '' - if '; ' in header: - header, attrs = header.split('; ', 1) - m = True - while m: - m = ATTRIBUTES_RE.match(attrs) - if m: - attrs = attrs[len(m.group(0)):] - attributes[m.group(1)] = m.group(2).strip('"') - return header, attributes - - -class _IterRequestsFileLikeObject(object): - - def __init__(self, wsgi_input, boundary, input_buffer): - self.no_more_data_for_this_file = False - self.no_more_files = False - self.wsgi_input = wsgi_input - self.boundary = boundary - self.input_buffer = input_buffer - - def read(self, length=None): - if not length: - length = READ_CHUNK_SIZE - if self.no_more_data_for_this_file: - return '' - - # read enough data to know whether we're going to run - # into a boundary in next [length] bytes - if len(self.input_buffer) < length + len(self.boundary) + 2: - to_read = length + len(self.boundary) + 2 - while to_read > 0: - chunk = self.wsgi_input.read(to_read) - to_read -= len(chunk) - self.input_buffer += chunk - if not chunk: - self.no_more_files = True - break - - boundary_pos = self.input_buffer.find(self.boundary) - - # boundary does not exist in the next (length) bytes - if boundary_pos == -1 or boundary_pos > length: - ret = self.input_buffer[:length] - self.input_buffer = self.input_buffer[length:] - # if it does, just return data up to the boundary - else: - ret, self.input_buffer = self.input_buffer.split(self.boundary, 1) - self.no_more_files = self.input_buffer.startswith('--') - self.no_more_data_for_this_file = True - self.input_buffer = self.input_buffer[2:] - return ret - - def readline(self): - if self.no_more_data_for_this_file: - return '' - boundary_pos = newline_pos = -1 - while newline_pos < 0 and boundary_pos < 0: - chunk = self.wsgi_input.read(READ_CHUNK_SIZE) - self.input_buffer += chunk - newline_pos = self.input_buffer.find('\r\n') - boundary_pos = self.input_buffer.find(self.boundary) - if not chunk: - self.no_more_files = True - break - # found a newline - if newline_pos >= 0 and \ - (boundary_pos < 0 or newline_pos < boundary_pos): - # Use self.read to ensure any logic there happens... - ret = '' - to_read = newline_pos + 2 - while to_read > 0: - chunk = self.read(to_read) - # Should never happen since we're reading from input_buffer, - # but just for completeness... - if not chunk: - break - to_read -= len(chunk) - ret += chunk - return ret - else: # no newlines, just return up to next boundary - return self.read(len(self.input_buffer)) - - -def _iter_requests(wsgi_input, boundary): - """ - Given a multi-part mime encoded input file object and boundary, - yield file-like objects for each part. - - :param wsgi_input: The file-like object to read from. - :param boundary: The mime boundary to separate new file-like - objects on. - :returns: A generator of file-like objects for each part. - """ - boundary = '--' + boundary - if wsgi_input.readline().strip() != boundary: - raise FormInvalid('invalid starting boundary') - boundary = '\r\n' + boundary - input_buffer = '' - done = False - while not done: - it = _IterRequestsFileLikeObject(wsgi_input, boundary, input_buffer) - yield it - done = it.no_more_files - input_buffer = it.input_buffer - - -class _CappedFileLikeObject(object): - """ - A file-like object wrapping another file-like object that raises - an EOFError if the amount of data read exceeds a given - max_file_size. - - :param fp: The file-like object to wrap. - :param max_file_size: The maximum bytes to read before raising an - EOFError. - """ - - def __init__(self, fp, max_file_size): - self.fp = fp - self.max_file_size = max_file_size - self.amount_read = 0 - - def read(self, size=None): - ret = self.fp.read(size) - self.amount_read += len(ret) - if self.amount_read > self.max_file_size: - raise EOFError('max_file_size exceeded') - return ret - - def readline(self): - ret = self.fp.readline() - self.amount_read += len(ret) - if self.amount_read > self.max_file_size: - raise EOFError('max_file_size exceeded') - return ret - - -class FormPost(object): - """ - FormPost Middleware - - See above for a full description. - - :param app: The next WSGI filter or app in the paste.deploy - chain. - :param conf: The configuration dict for the middleware. - """ - - def __init__(self, app, conf): - #: The next WSGI application/filter in the paste.deploy pipeline. - self.app = app - #: The filter configuration dict. - self.conf = conf - #: The logger to use with this middleware. - self.logger = get_logger(conf, log_route='formpost') - #: The HTTP user agent to use with subrequests. - self.agent = '%(orig)s FormPost' - - def __call__(self, env, start_response): - """ - Main hook into the WSGI paste.deploy filter/app pipeline. - - :param env: The WSGI environment dict. - :param start_response: The WSGI start_response hook. - :returns: Response as per WSGI. - """ - if env['REQUEST_METHOD'] == 'POST': - try: - content_type, attrs = \ - _parse_attrs(env.get('CONTENT_TYPE') or '') - if content_type == 'multipart/form-data' and \ - 'boundary' in attrs: - resp_status = [0] - - def _start_response(status, headers, exc_info=None): - resp_status[0] = int(status.split(' ', 1)[0]) - start_response(status, headers, exc_info) - - self._log_request(env, resp_status) - return self._translate_form(env, start_response, - attrs['boundary']) - except (FormInvalid, EOFError), err: - self._log_request(env, HTTP_BAD_REQUEST) - body = 'FormPost: %s' % err - start_response('400 Bad Request', - (('Content-Type', 'text/plain'), - ('Content-Length', str(len(body))))) - return [body] - return self.app(env, start_response) - - def _translate_form(self, env, start_response, boundary): - """ - Translates the form data into subrequests and issues a - response. - - :param env: The WSGI environment dict. - :param start_response: The WSGI start_response hook. - :returns: Response as per WSGI. - """ - key = self._get_key(env) - status = message = '' - attributes = {} - file_count = 0 - for fp in _iter_requests(env['wsgi.input'], boundary): - hdrs = rfc822.Message(fp, 0) - disp, attrs = \ - _parse_attrs(hdrs.getheader('Content-Disposition', '')) - if disp == 'form-data' and attrs.get('filename'): - file_count += 1 - try: - if file_count > int(attributes.get('max_file_count') or 0): - status = '400 Bad Request' - message = 'max file count exceeded' - break - except ValueError: - raise FormInvalid('max_file_count not an integer') - attributes['filename'] = attrs['filename'] or 'filename' - if 'content-type' not in attributes and 'content-type' in hdrs: - attributes['content-type'] = \ - hdrs['Content-Type'] or 'application/octet-stream' - status, message = self._perform_subrequest(env, start_response, - attributes, fp, key) - if status[:1] != '2': - break - else: - data = '' - mxln = MAX_VALUE_LENGTH - while mxln: - chunk = fp.read(mxln) - if not chunk: - break - mxln -= len(chunk) - data += chunk - while fp.read(READ_CHUNK_SIZE): - pass - if 'name' in attrs: - attributes[attrs['name'].lower()] = data.rstrip('\r\n--') - if not status: - status = '400 Bad Request' - message = 'no files to process' - if not attributes.get('redirect'): - body = status - if message: - body = status + '\r\nFormPost: ' + message.title() - start_response(status, [('Content-Type', 'text/plain'), - ('Content-Length', len(body))]) - return [body] - status = status.split(' ', 1)[0] - body = '

Click to ' \ - 'continue...

' % \ - (attributes['redirect'], quote(status), quote(message)) - start_response('303 See Other', - [('Location', '%s?status=%s&message=%s' % - (attributes['redirect'], quote(status), quote(message))), - ('Content-Length', str(len(body)))]) - return [body] - - def _perform_subrequest(self, env, start_response, attributes, fp, key): - """ - Performs the subrequest and returns a new response. - - :param env: The WSGI environment dict. - :param start_response: The WSGI start_response hook. - :param attributes: dict of the attributes of the form so far. - :param fp: The file-like object containing the request body. - :param key: The account key to validate the signature with. - :returns: Response as per WSGI. - """ - if not key: - return '401 Unauthorized', 'invalid signature' - try: - max_file_size = int(attributes.get('max_file_size') or 0) - except ValueError: - raise FormInvalid('max_file_size not an integer') - subenv = make_pre_authed_env(env, 'PUT', agent=self.agent) - subenv['HTTP_TRANSFER_ENCODING'] = 'chunked' - subenv['wsgi.input'] = _CappedFileLikeObject(fp, max_file_size) - if subenv['PATH_INFO'][-1] != '/' and \ - subenv['PATH_INFO'].count('/') < 4: - subenv['PATH_INFO'] += '/' - subenv['PATH_INFO'] += attributes['filename'] or 'filename' - if 'content-type' in attributes: - subenv['CONTENT_TYPE'] = \ - attributes['content-type'] or 'application/octet-stream' - elif 'CONTENT_TYPE' in subenv: - del subenv['CONTENT_TYPE'] - try: - if int(attributes.get('expires') or 0) < time(): - return '401 Unauthorized', 'form expired' - except ValueError: - raise FormInvalid('expired not an integer') - hmac_body = '%s\n%s\n%s\n%s\n%s' % ( - env['PATH_INFO'], - attributes.get('redirect') or '', - attributes.get('max_file_size') or '0', - attributes.get('max_file_count') or '0', - attributes.get('expires') or '0' - ) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - if not streq_const_time(sig, (attributes.get('signature') or - 'invalid')): - return '401 Unauthorized', 'invalid signature' - substatus = [None] - - def _start_response(status, headers, exc_info=None): - substatus[0] = status - - i = iter(self.app(subenv, _start_response)) - try: - i.next() - except StopIteration: - pass - return substatus[0], '' - - def _get_key(self, env): - """ - Returns the X-Account-Meta-Temp-URL-Key header value for the - account, or None if none is set. - - :param env: The WSGI environment for the request. - :returns: X-Account-Meta-Temp-URL-Key str value, or None. - """ - parts = env['PATH_INFO'].split('/', 4) - if len(parts) < 4 or parts[0] or parts[1] != 'v1' or not parts[2] or \ - not parts[3]: - return None - account = parts[2] - key = None - memcache = env.get('swift.cache') - if memcache: - key = memcache.get('temp-url-key/%s' % account) - if not key: - newenv = make_pre_authed_env(env, 'HEAD', '/v1/' + account, - self.agent) - newenv['CONTENT_LENGTH'] = '0' - newenv['wsgi.input'] = StringIO('') - key = [None] - - def _start_response(status, response_headers, exc_info=None): - for h, v in response_headers: - if h.lower() == 'x-account-meta-temp-url-key': - key[0] = v - - i = iter(self.app(newenv, _start_response)) - try: - i.next() - except StopIteration: - pass - key = key[0] - if key and memcache: - memcache.set('temp-url-key/%s' % account, key, timeout=60) - return key - - def _log_request(self, env, response_status_int): - """ - Used when a request might not be logged by the underlying - WSGI application, but we'd still like to record what - happened. An early 401 Unauthorized is a good example of - this. - - :param env: The WSGI environment for the request. - :param response_status_int: The HTTP status we'll be replying - to the request with. - """ - the_request = quote(unquote(env.get('PATH_INFO') or '/')) - if env.get('QUERY_STRING'): - the_request = the_request + '?' + env['QUERY_STRING'] - client = env.get('HTTP_X_CLUSTER_CLIENT_IP') - if not client and 'HTTP_X_FORWARDED_FOR' in env: - # remote host for other lbs - client = env['HTTP_X_FORWARDED_FOR'].split(',')[0].strip() - if not client: - client = env.get('REMOTE_ADDR') - self.logger.info(' '.join(quote(str(x)) for x in ( - client or '-', - env.get('REMOTE_ADDR') or '-', - strftime('%d/%b/%Y/%H/%M/%S', gmtime()), - env.get('REQUEST_METHOD') or 'GET', - the_request, - env.get('SERVER_PROTOCOL') or '1.0', - response_status_int, - env.get('HTTP_REFERER') or '-', - (env.get('HTTP_USER_AGENT') or '-') + ' FormPOST', - env.get('HTTP_X_AUTH_TOKEN') or '-', - '-', - '-', - '-', - env.get('swift.trans_id') or '-', - '-', - '-', - ))) - - -def filter_factory(global_conf, **local_conf): - """ Returns the WSGI filter for use with paste.deploy. """ - conf = global_conf.copy() - conf.update(local_conf) - return lambda app: FormPost(app, conf) diff --git a/swift/common/middleware/tempurl.py b/swift/common/middleware/tempurl.py deleted file mode 100644 index f581078f71..0000000000 --- a/swift/common/middleware/tempurl.py +++ /dev/null @@ -1,490 +0,0 @@ -# Copyright (c) 2010-2012 OpenStack, LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -TempURL Middleware - -Allows the creation of URLs to provide temporary access to objects. - -For example, a website may wish to provide a link to download a large -object in Swift, but the Swift account has no public access. The -website can generate a URL that will provide GET access for a limited -time to the resource. When the web browser user clicks on the link, -the browser will download the object directly from Swift, obviating -the need for the website to act as a proxy for the request. - -If the user were to share the link with all his friends, or -accidentally post it on a forum, etc. the direct access would be -limited to the expiration time set when the website created the link. - -To create such temporary URLs, first an X-Account-Meta-Temp-URL-Key -header must be set on the Swift account. Then, an HMAC-SHA1 (RFC 2104) -signature is generated using the HTTP method to allow (GET or PUT), -the Unix timestamp the access should be allowed until, the full path -to the object, and the key set on the account. - -For example, here is code generating the signature for a GET for 60 -seconds on /v1/AUTH_account/container/object:: - - import hmac - from hashlib import sha1 - from time import time - method = 'GET' - expires = int(time() + 60) - path = '/v1/AUTH_account/container/object' - key = 'mykey' - hmac_body = '%s\\n%s\\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - -Be certain to use the full path, from the /v1/ onward. - -Let's say the sig ends up equaling -da39a3ee5e6b4b0d3255bfef95601890afd80709 and expires ends up -1323479485. Then, for example, the website could provide a link to:: - - https://swift-cluster.example.com/v1/AUTH_account/container/object? - temp_url_sig=da39a3ee5e6b4b0d3255bfef95601890afd80709& - temp_url_expires=1323479485 - -Any alteration of the resource path or query arguments would result -in 401 Unauthorized. Similary, a PUT where GET was the allowed method -would 401. HEAD is allowed if GET or PUT is allowed. - -Using this in combination with browser form post translation -middleware could also allow direct-from-browser uploads to specific -locations in Swift. - -Note that changing the X-Account-Meta-Temp-URL-Key will invalidate -any previously generated temporary URLs within 60 seconds (the -memcache time for the key). -""" - -__all__ = ['TempURL', 'filter_factory', - 'DEFAULT_INCOMING_REMOVE_HEADERS', - 'DEFAULT_INCOMING_ALLOW_HEADERS', - 'DEFAULT_OUTGOING_REMOVE_HEADERS', - 'DEFAULT_OUTGOING_ALLOW_HEADERS'] - - -import hmac -from hashlib import sha1 -from os.path import basename -from StringIO import StringIO -from time import gmtime, strftime, time -from urllib import quote, unquote -from urlparse import parse_qs - -from swift.common.utils import get_logger -from swift.common.wsgi import make_pre_authed_env -from swift.common.http import HTTP_UNAUTHORIZED - - -#: Default headers to remove from incoming requests. Simply a whitespace -#: delimited list of header names and names can optionally end with '*' to -#: indicate a prefix match. DEFAULT_INCOMING_ALLOW_HEADERS is a list of -#: exceptions to these removals. -DEFAULT_INCOMING_REMOVE_HEADERS = 'x-timestamp' - -#: Default headers as exceptions to DEFAULT_INCOMING_REMOVE_HEADERS. Simply a -#: whitespace delimited list of header names and names can optionally end with -#: '*' to indicate a prefix match. -DEFAULT_INCOMING_ALLOW_HEADERS = '' - -#: Default headers to remove from outgoing responses. Simply a whitespace -#: delimited list of header names and names can optionally end with '*' to -#: indicate a prefix match. DEFAULT_OUTGOING_ALLOW_HEADERS is a list of -#: exceptions to these removals. -DEFAULT_OUTGOING_REMOVE_HEADERS = 'x-object-meta-*' - -#: Default headers as exceptions to DEFAULT_OUTGOING_REMOVE_HEADERS. Simply a -#: whitespace delimited list of header names and names can optionally end with -#: '*' to indicate a prefix match. -DEFAULT_OUTGOING_ALLOW_HEADERS = 'x-object-meta-public-*' - - -class TempURL(object): - """ - WSGI Middleware to grant temporary URLs specific access to Swift - resources. See the overview for more information. - - This middleware understands the following configuration settings:: - - incoming_remove_headers - The headers to remove from incoming requests. Simply a - whitespace delimited list of header names and names can - optionally end with '*' to indicate a prefix match. - incoming_allow_headers is a list of exceptions to these - removals. - Default: x-timestamp - - incoming_allow_headers - The headers allowed as exceptions to - incoming_remove_headers. Simply a whitespace delimited - list of header names and names can optionally end with - '*' to indicate a prefix match. - Default: None - - outgoing_remove_headers - The headers to remove from outgoing responses. Simply a - whitespace delimited list of header names and names can - optionally end with '*' to indicate a prefix match. - outgoing_allow_headers is a list of exceptions to these - removals. - Default: x-object-meta-* - - outgoing_allow_headers - The headers allowed as exceptions to - outgoing_remove_headers. Simply a whitespace delimited - list of header names and names can optionally end with - '*' to indicate a prefix match. - Default: x-object-meta-public-* - - :param app: The next WSGI filter or app in the paste.deploy - chain. - :param conf: The configuration dict for the middleware. - """ - - def __init__(self, app, conf): - #: The next WSGI application/filter in the paste.deploy pipeline. - self.app = app - #: The filter configuration dict. - self.conf = conf - #: The logger to use with this middleware. - self.logger = get_logger(conf, log_route='tempurl') - - headers = DEFAULT_INCOMING_REMOVE_HEADERS - if 'incoming_remove_headers' in conf: - headers = conf['incoming_remove_headers'] - headers = \ - ['HTTP_' + h.upper().replace('-', '_') for h in headers.split()] - #: Headers to remove from incoming requests. Uppercase WSGI env style, - #: like `HTTP_X_PRIVATE`. - self.incoming_remove_headers = [h for h in headers if h[-1] != '*'] - #: Header with match prefixes to remove from incoming requests. - #: Uppercase WSGI env style, like `HTTP_X_SENSITIVE_*`. - self.incoming_remove_headers_startswith = \ - [h[:-1] for h in headers if h[-1] == '*'] - - headers = DEFAULT_INCOMING_ALLOW_HEADERS - if 'incoming_allow_headers' in conf: - headers = conf['incoming_allow_headers'] - headers = \ - ['HTTP_' + h.upper().replace('-', '_') for h in headers.split()] - #: Headers to allow in incoming requests. Uppercase WSGI env style, - #: like `HTTP_X_MATCHES_REMOVE_PREFIX_BUT_OKAY`. - self.incoming_allow_headers = [h for h in headers if h[-1] != '*'] - #: Header with match prefixes to allow in incoming requests. Uppercase - #: WSGI env style, like `HTTP_X_MATCHES_REMOVE_PREFIX_BUT_OKAY_*`. - self.incoming_allow_headers_startswith = \ - [h[:-1] for h in headers if h[-1] == '*'] - - headers = DEFAULT_OUTGOING_REMOVE_HEADERS - if 'outgoing_remove_headers' in conf: - headers = conf['outgoing_remove_headers'] - headers = [h.lower() for h in headers.split()] - #: Headers to remove from outgoing responses. Lowercase, like - #: `x-account-meta-temp-url-key`. - self.outgoing_remove_headers = [h for h in headers if h[-1] != '*'] - #: Header with match prefixes to remove from outgoing responses. - #: Lowercase, like `x-account-meta-private-*`. - self.outgoing_remove_headers_startswith = \ - [h[:-1] for h in headers if h[-1] == '*'] - - headers = DEFAULT_OUTGOING_ALLOW_HEADERS - if 'outgoing_allow_headers' in conf: - headers = conf['outgoing_allow_headers'] - headers = [h.lower() for h in headers.split()] - #: Headers to allow in outgoing responses. Lowercase, like - #: `x-matches-remove-prefix-but-okay`. - self.outgoing_allow_headers = [h for h in headers if h[-1] != '*'] - #: Header with match prefixes to allow in outgoing responses. - #: Lowercase, like `x-matches-remove-prefix-but-okay-*`. - self.outgoing_allow_headers_startswith = \ - [h[:-1] for h in headers if h[-1] == '*'] - #: HTTP user agent to use for subrequests. - self.agent = '%(orig)s TempURL' - - def __call__(self, env, start_response): - """ - Main hook into the WSGI paste.deploy filter/app pipeline. - - :param env: The WSGI environment dict. - :param start_response: The WSGI start_response hook. - :returns: Response as per WSGI. - """ - temp_url_sig, temp_url_expires = self._get_temp_url_info(env) - if temp_url_sig is None and temp_url_expires is None: - return self.app(env, start_response) - if not temp_url_sig or not temp_url_expires: - return self._invalid(env, start_response) - account = self._get_account(env) - if not account: - return self._invalid(env, start_response) - key = self._get_key(env, account) - if not key: - return self._invalid(env, start_response) - if env['REQUEST_METHOD'] == 'HEAD': - hmac_val = self._get_hmac(env, temp_url_expires, key, - request_method='GET') - if temp_url_sig != hmac_val: - hmac_val = self._get_hmac(env, temp_url_expires, key, - request_method='PUT') - if temp_url_sig != hmac_val: - return self._invalid(env, start_response) - else: - hmac_val = self._get_hmac(env, temp_url_expires, key) - if temp_url_sig != hmac_val: - return self._invalid(env, start_response) - self._clean_incoming_headers(env) - env['swift.authorize'] = lambda req: None - env['swift.authorize_override'] = True - env['REMOTE_USER'] = '.wsgi.tempurl' - - def _start_response(status, headers, exc_info=None): - headers = self._clean_outgoing_headers(headers) - if env['REQUEST_METHOD'] == 'GET': - already = False - for h, v in headers: - if h.lower() == 'content-disposition': - already = True - break - if not already: - headers.append(('Content-Disposition', - 'attachment; filename=%s' % - (quote(basename(env['PATH_INFO']))))) - return start_response(status, headers, exc_info) - - return self.app(env, _start_response) - - def _get_account(self, env): - """ - Returns just the account for the request, if it's an object GET, PUT, - or HEAD request; otherwise, None is returned. - - :param env: The WSGI environment for the request. - :returns: Account str or None. - """ - account = None - if env['REQUEST_METHOD'] in ('GET', 'PUT', 'HEAD'): - parts = env['PATH_INFO'].split('/', 4) - # Must be five parts, ['', 'v1', 'a', 'c', 'o'], must be a v1 - # request, have account, container, and object values, and the - # object value can't just have '/'s. - if len(parts) == 5 and not parts[0] and parts[1] == 'v1' and \ - parts[2] and parts[3] and parts[4].strip('/'): - account = parts[2] - return account - - def _get_temp_url_info(self, env): - """ - Returns the provided temporary URL parameters (sig, expires), - if given and syntactically valid. Either sig or expires could - be None if not provided. If provided, expires is also - converted to an int if possible or 0 if not, and checked for - expiration (returns 0 if expired). - - :param env: The WSGI environment for the request. - :returns: (sig, expires) as described above. - """ - temp_url_sig = temp_url_expires = None - qs = parse_qs(env.get('QUERY_STRING', '')) - if 'temp_url_sig' in qs: - temp_url_sig = qs['temp_url_sig'][0] - if 'temp_url_expires' in qs: - try: - temp_url_expires = int(qs['temp_url_expires'][0]) - except ValueError: - temp_url_expires = 0 - if temp_url_expires < time(): - temp_url_expires = 0 - return temp_url_sig, temp_url_expires - - def _get_key(self, env, account): - """ - Returns the X-Account-Meta-Temp-URL-Key header value for the - account, or None if none is set. - - :param env: The WSGI environment for the request. - :param account: Account str. - :returns: X-Account-Meta-Temp-URL-Key str value, or None. - """ - key = None - memcache = env.get('swift.cache') - if memcache: - key = memcache.get('temp-url-key/%s' % account) - if not key: - newenv = make_pre_authed_env(env, 'HEAD', '/v1/' + account, - self.agent) - newenv['CONTENT_LENGTH'] = '0' - newenv['wsgi.input'] = StringIO('') - key = [None] - - def _start_response(status, response_headers, exc_info=None): - for h, v in response_headers: - if h.lower() == 'x-account-meta-temp-url-key': - key[0] = v - - i = iter(self.app(newenv, _start_response)) - try: - i.next() - except StopIteration: - pass - key = key[0] - if key and memcache: - memcache.set('temp-url-key/%s' % account, key, timeout=60) - return key - - def _get_hmac(self, env, expires, key, request_method=None): - """ - Returns the hexdigest string of the HMAC-SHA1 (RFC 2104) for - the request. - - :param env: The WSGI environment for the request. - :param expires: Unix timestamp as an int for when the URL - expires. - :param key: Key str, from the X-Account-Meta-Temp-URL-Key of - the account. - :param request_method: Optional override of the request in - the WSGI env. For example, if a HEAD - does not match, you may wish to - override with GET to still allow the - HEAD. - :returns: hexdigest str of the HMAC-SHA1 for the request. - """ - if not request_method: - request_method = env['REQUEST_METHOD'] - return hmac.new(key, '%s\n%s\n%s' % (request_method, expires, - env['PATH_INFO']), sha1).hexdigest() - - def _invalid(self, env, start_response): - """ - Performs the necessary steps to indicate a WSGI 401 - Unauthorized response to the request. - - :param env: The WSGI environment for the request. - :param start_response: The WSGI start_response hook. - :returns: 401 response as per WSGI. - """ - self._log_request(env, HTTP_UNAUTHORIZED) - body = '401 Unauthorized: Temp URL invalid\n' - start_response('401 Unauthorized', - [('Content-Type', 'text/plain'), - ('Content-Length', str(len(body)))]) - if env['REQUEST_METHOD'] == 'HEAD': - return [] - return [body] - - def _clean_incoming_headers(self, env): - """ - Removes any headers from the WSGI environment as per the - middleware configuration for incoming requests. - - :param env: The WSGI environment for the request. - """ - for h in env.keys(): - remove = h in self.incoming_remove_headers - if not remove: - for p in self.incoming_remove_headers_startswith: - if h.startswith(p): - remove = True - break - if remove: - if h in self.incoming_allow_headers: - remove = False - if remove: - for p in self.incoming_allow_headers_startswith: - if h.startswith(p): - remove = False - break - if remove: - del env[h] - - def _clean_outgoing_headers(self, headers): - """ - Removes any headers as per the middleware configuration for - outgoing responses. - - :param headers: A WSGI start_response style list of headers, - [('header1', 'value), ('header2', 'value), - ...] - :returns: The same headers list, but with some headers - removed as per the middlware configuration for - outgoing responses. - """ - headers = dict(headers) - for h in headers.keys(): - remove = h in self.outgoing_remove_headers - if not remove: - for p in self.outgoing_remove_headers_startswith: - if h.startswith(p): - remove = True - break - if remove: - if h in self.outgoing_allow_headers: - remove = False - if remove: - for p in self.outgoing_allow_headers_startswith: - if h.startswith(p): - remove = False - break - if remove: - del headers[h] - return headers.items() - - def _log_request(self, env, response_status_int): - """ - Used when a request might not be logged by the underlying - WSGI application, but we'd still like to record what - happened. An early 401 Unauthorized is a good example of - this. - - :param env: The WSGI environment for the request. - :param response_status_int: The HTTP status we'll be replying - to the request with. - """ - the_request = quote(unquote(env.get('PATH_INFO') or '/')) - if env.get('QUERY_STRING'): - the_request = the_request + '?' + env['QUERY_STRING'] - client = env.get('HTTP_X_CLUSTER_CLIENT_IP') - if not client and 'HTTP_X_FORWARDED_FOR' in env: - # remote host for other lbs - client = env['HTTP_X_FORWARDED_FOR'].split(',')[0].strip() - if not client: - client = env.get('REMOTE_ADDR') - self.logger.info(' '.join(quote(str(x)) for x in ( - client or '-', - env.get('REMOTE_ADDR') or '-', - strftime('%d/%b/%Y/%H/%M/%S', gmtime()), - env.get('REQUEST_METHOD') or 'GET', - the_request, - env.get('SERVER_PROTOCOL') or '1.0', - response_status_int, - env.get('HTTP_REFERER') or '-', - (env.get('HTTP_USER_AGENT') or '-') + ' TempURL', - env.get('HTTP_X_AUTH_TOKEN') or '-', - '-', - '-', - '-', - env.get('swift.trans_id') or '-', - '-', - '-', - ))) - - -def filter_factory(global_conf, **local_conf): - """ Returns the WSGI filter for use with paste.deploy. """ - conf = global_conf.copy() - conf.update(local_conf) - return lambda app: TempURL(app, conf) diff --git a/test/unit/common/middleware/test_formpost.py b/test/unit/common/middleware/test_formpost.py deleted file mode 100644 index cb77dcf871..0000000000 --- a/test/unit/common/middleware/test_formpost.py +++ /dev/null @@ -1,1447 +0,0 @@ -# Copyright (c) 2011 OpenStack, LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import hmac -import unittest -from hashlib import sha1 -from contextlib import contextmanager -from StringIO import StringIO -from time import time - -from webob import Request, Response - -from swift.common.middleware import tempauth, formpost - - -class FakeMemcache(object): - - def __init__(self): - self.store = {} - - def get(self, key): - return self.store.get(key) - - def set(self, key, value, timeout=0): - self.store[key] = value - return True - - def incr(self, key, timeout=0): - self.store[key] = self.store.setdefault(key, 0) + 1 - return self.store[key] - - @contextmanager - def soft_lock(self, key, timeout=0, retries=5): - yield True - - def delete(self, key): - try: - del self.store[key] - except Exception: - pass - return True - - -class FakeApp(object): - - def __init__(self, status_headers_body_iter=None): - self.status_headers_body_iter = status_headers_body_iter - if not self.status_headers_body_iter: - self.status_headers_body_iter = iter([('404 Not Found', { - 'x-test-header-one-a': 'value1', - 'x-test-header-two-a': 'value2', - 'x-test-header-two-b': 'value3'}, '')]) - self.requests = [] - - def __call__(self, env, start_response): - body = '' - while True: - chunk = env['wsgi.input'].read() - if not chunk: - break - body += chunk - env['wsgi.input'] = StringIO(body) - self.requests.append(Request.blank('', environ=env)) - if env.get('swift.authorize_override') and \ - env.get('REMOTE_USER') != '.wsgi.pre_authed': - raise Exception('Invalid REMOTE_USER %r with ' - 'swift.authorize_override' % (env.get('REMOTE_USER'),)) - if 'swift.authorize' in env: - resp = env['swift.authorize'](self.requests[-1]) - if resp: - return resp(env, start_response) - status, headers, body = self.status_headers_body_iter.next() - return Response(status=status, headers=headers, - body=body)(env, start_response) - - -class TestParseAttrs(unittest.TestCase): - - def test_basic_content_type(self): - name, attrs = formpost._parse_attrs('text/plain') - self.assertEquals(name, 'text/plain') - self.assertEquals(attrs, {}) - - def test_content_type_with_charset(self): - name, attrs = formpost._parse_attrs('text/plain; charset=UTF8') - self.assertEquals(name, 'text/plain') - self.assertEquals(attrs, {'charset': 'UTF8'}) - - def test_content_disposition(self): - name, attrs = formpost._parse_attrs( - 'form-data; name="somefile"; filename="test.html"') - self.assertEquals(name, 'form-data') - self.assertEquals(attrs, {'name': 'somefile', 'filename': 'test.html'}) - - -class TestIterRequests(unittest.TestCase): - - def test_bad_start(self): - it = formpost._iter_requests(StringIO('blah'), 'unique') - exc = None - try: - it.next() - except formpost.FormInvalid, err: - exc = err - self.assertEquals(str(exc), 'invalid starting boundary') - - def test_empty(self): - it = formpost._iter_requests(StringIO('--unique'), 'unique') - fp = it.next() - self.assertEquals(fp.read(), '') - exc = None - try: - it.next() - except StopIteration, err: - exc = err - self.assertTrue(exc is not None) - - def test_basic(self): - it = formpost._iter_requests( - StringIO('--unique\r\nabcdefg\r\n--unique--'), 'unique') - fp = it.next() - self.assertEquals(fp.read(), 'abcdefg') - exc = None - try: - it.next() - except StopIteration, err: - exc = err - self.assertTrue(exc is not None) - - def test_basic2(self): - it = formpost._iter_requests( - StringIO('--unique\r\nabcdefg\r\n--unique\r\nhijkl\r\n--unique--'), - 'unique') - fp = it.next() - self.assertEquals(fp.read(), 'abcdefg') - fp = it.next() - self.assertEquals(fp.read(), 'hijkl') - exc = None - try: - it.next() - except StopIteration, err: - exc = err - self.assertTrue(exc is not None) - - def test_tiny_reads(self): - it = formpost._iter_requests( - StringIO('--unique\r\nabcdefg\r\n--unique\r\nhijkl\r\n--unique--'), - 'unique') - fp = it.next() - self.assertEquals(fp.read(2), 'ab') - self.assertEquals(fp.read(2), 'cd') - self.assertEquals(fp.read(2), 'ef') - self.assertEquals(fp.read(2), 'g') - self.assertEquals(fp.read(2), '') - fp = it.next() - self.assertEquals(fp.read(), 'hijkl') - exc = None - try: - it.next() - except StopIteration, err: - exc = err - self.assertTrue(exc is not None) - - def test_big_reads(self): - it = formpost._iter_requests( - StringIO('--unique\r\nabcdefg\r\n--unique\r\nhijkl\r\n--unique--'), - 'unique') - fp = it.next() - self.assertEquals(fp.read(65536), 'abcdefg') - self.assertEquals(fp.read(), '') - fp = it.next() - self.assertEquals(fp.read(), 'hijkl') - exc = None - try: - it.next() - except StopIteration, err: - exc = err - self.assertTrue(exc is not None) - - def test_broken_mid_stream(self): - # We go ahead and accept whatever is sent instead of rejecting the - # whole request, in case the partial form is still useful. - it = formpost._iter_requests( - StringIO('--unique\r\nabc'), 'unique') - fp = it.next() - self.assertEquals(fp.read(), 'abc') - exc = None - try: - it.next() - except StopIteration, err: - exc = err - self.assertTrue(exc is not None) - - def test_readline(self): - it = formpost._iter_requests(StringIO('--unique\r\nab\r\ncd\ref\ng\r\n' - '--unique\r\nhi\r\n\r\njkl\r\n\r\n--unique--'), 'unique') - fp = it.next() - self.assertEquals(fp.readline(), 'ab\r\n') - self.assertEquals(fp.readline(), 'cd\ref\ng') - self.assertEquals(fp.readline(), '') - fp = it.next() - self.assertEquals(fp.readline(), 'hi\r\n') - self.assertEquals(fp.readline(), '\r\n') - self.assertEquals(fp.readline(), 'jkl\r\n') - exc = None - try: - it.next() - except StopIteration, err: - exc = err - self.assertTrue(exc is not None) - - def test_readline_with_tiny_chunks(self): - orig_read_chunk_size = formpost.READ_CHUNK_SIZE - try: - formpost.READ_CHUNK_SIZE = 2 - it = formpost._iter_requests(StringIO('--unique\r\nab\r\ncd\ref\ng' - '\r\n--unique\r\nhi\r\n\r\njkl\r\n\r\n--unique--'), 'unique') - fp = it.next() - self.assertEquals(fp.readline(), 'ab\r\n') - self.assertEquals(fp.readline(), 'cd\ref\ng') - self.assertEquals(fp.readline(), '') - fp = it.next() - self.assertEquals(fp.readline(), 'hi\r\n') - self.assertEquals(fp.readline(), '\r\n') - self.assertEquals(fp.readline(), 'jkl\r\n') - exc = None - try: - it.next() - except StopIteration, err: - exc = err - self.assertTrue(exc is not None) - finally: - formpost.READ_CHUNK_SIZE = orig_read_chunk_size - - -class TestCappedFileLikeObject(unittest.TestCase): - - def test_whole(self): - self.assertEquals( - formpost._CappedFileLikeObject(StringIO('abc'), 10).read(), 'abc') - - def test_exceeded(self): - exc = None - try: - formpost._CappedFileLikeObject(StringIO('abc'), 2).read() - except EOFError, err: - exc = err - self.assertEquals(str(exc), 'max_file_size exceeded') - - def test_whole_readline(self): - fp = formpost._CappedFileLikeObject(StringIO('abc\ndef'), 10) - self.assertEquals(fp.readline(), 'abc\n') - self.assertEquals(fp.readline(), 'def') - self.assertEquals(fp.readline(), '') - - def test_exceeded_readline(self): - fp = formpost._CappedFileLikeObject(StringIO('abc\ndef'), 5) - self.assertEquals(fp.readline(), 'abc\n') - exc = None - try: - self.assertEquals(fp.readline(), 'def') - except EOFError, err: - exc = err - self.assertEquals(str(err), 'max_file_size exceeded') - - def test_read_sized(self): - fp = formpost._CappedFileLikeObject(StringIO('abcdefg'), 10) - self.assertEquals(fp.read(2), 'ab') - self.assertEquals(fp.read(2), 'cd') - self.assertEquals(fp.read(2), 'ef') - self.assertEquals(fp.read(2), 'g') - self.assertEquals(fp.read(2), '') - - -class TestFormPost(unittest.TestCase): - - def setUp(self): - self.app = FakeApp() - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - - def _make_request(self, path, **kwargs): - req = Request.blank(path, **kwargs) - req.environ['swift.cache'] = FakeMemcache() - return req - - def _make_sig_env_body(self, path, redirect, max_file_size, max_file_count, - expires, key): - sig = hmac.new(key, '%s\n%s\n%s\n%s\n%s' % (path, redirect, - max_file_size, max_file_count, expires), sha1).hexdigest() - body = [ - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="redirect"', - '', - redirect, - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="max_file_size"', - '', - str(max_file_size), - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="max_file_count"', - '', - str(max_file_count), - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="expires"', - '', - str(expires), - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="signature"', - '', - sig, - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="file1"; ' - 'filename="testfile1.txt"', - 'Content-Type: text/plain', - '', - 'Test File\nOne\n', - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="file2"; ' - 'filename="testfile2.txt"', - 'Content-Type: text/plain', - '', - 'Test\nFile\nTwo\n', - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="file3"; filename=""', - 'Content-Type: application/octet-stream', - '', - '', - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR--', - '', - ] - wsgi_errors = StringIO() - env = { - 'CONTENT_TYPE': 'multipart/form-data; ' - 'boundary=----WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'HTTP_ACCEPT_ENCODING': 'gzip, deflate', - 'HTTP_ACCEPT_LANGUAGE': 'en-us', - 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;' - 'q=0.9,*/*;q=0.8', - 'HTTP_CONNECTION': 'keep-alive', - 'HTTP_HOST': 'ubuntu:8080', - 'HTTP_ORIGIN': 'file://', - 'HTTP_USER_AGENT': 'Mozilla/5.0 (Macintosh; Intel Mac OS X ' - '10_7_2) AppleWebKit/534.52.7 (KHTML, like Gecko) ' - 'Version/5.1.2 Safari/534.52.7', - 'PATH_INFO': path, - 'REMOTE_ADDR': '172.16.83.1', - 'REQUEST_METHOD': 'POST', - 'SCRIPT_NAME': '', - 'SERVER_NAME': '172.16.83.128', - 'SERVER_PORT': '8080', - 'SERVER_PROTOCOL': 'HTTP/1.0', - 'wsgi.errors': wsgi_errors, - 'wsgi.multiprocess': False, - 'wsgi.multithread': True, - 'wsgi.run_once': False, - 'wsgi.url_scheme': 'http', - 'wsgi.version': (1, 0), - } - return sig, env, body - - def test_passthrough(self): - for method in ('HEAD', 'GET', 'PUT', 'POST', 'DELETE'): - resp = self._make_request('/v1/a/c/o', - environ={'REQUEST_METHOD': method}).get_response(self.formpost) - self.assertEquals(resp.status_int, 401) - self.assertTrue('FormPost' not in resp.body) - - def test_safari(self): - key = 'abc' - path = '/v1/AUTH_test/container' - redirect = 'http://brim.net' - max_file_size = 1024 - max_file_count = 10 - expires = int(time() + 86400) - sig = hmac.new(key, '%s\n%s\n%s\n%s\n%s' % (path, redirect, - max_file_size, max_file_count, expires), sha1).hexdigest() - memcache = FakeMemcache() - memcache.set('temp-url-key/AUTH_test', key) - wsgi_input = StringIO('\r\n'.join([ - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="redirect"', - '', - redirect, - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="max_file_size"', - '', - str(max_file_size), - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="max_file_count"', - '', - str(max_file_count), - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="expires"', - '', - str(expires), - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="signature"', - '', - sig, - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="file1"; ' - 'filename="testfile1.txt"', - 'Content-Type: text/plain', - '', - 'Test File\nOne\n', - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="file2"; ' - 'filename="testfile2.txt"', - 'Content-Type: text/plain', - '', - 'Test\nFile\nTwo\n', - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="file3"; filename=""', - 'Content-Type: application/octet-stream', - '', - '', - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR--', - '', - ])) - wsgi_errors = StringIO() - env = { - 'CONTENT_TYPE': 'multipart/form-data; ' - 'boundary=----WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'HTTP_ACCEPT_ENCODING': 'gzip, deflate', - 'HTTP_ACCEPT_LANGUAGE': 'en-us', - 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;' - 'q=0.9,*/*;q=0.8', - 'HTTP_CONNECTION': 'keep-alive', - 'HTTP_HOST': 'ubuntu:8080', - 'HTTP_ORIGIN': 'file://', - 'HTTP_USER_AGENT': 'Mozilla/5.0 (Macintosh; Intel Mac OS X ' - '10_7_2) AppleWebKit/534.52.7 (KHTML, like Gecko) ' - 'Version/5.1.2 Safari/534.52.7', - 'PATH_INFO': path, - 'REMOTE_ADDR': '172.16.83.1', - 'REQUEST_METHOD': 'POST', - 'SCRIPT_NAME': '', - 'SERVER_NAME': '172.16.83.128', - 'SERVER_PORT': '8080', - 'SERVER_PROTOCOL': 'HTTP/1.0', - 'swift.cache': memcache, - 'wsgi.errors': wsgi_errors, - 'wsgi.input': wsgi_input, - 'wsgi.multiprocess': False, - 'wsgi.multithread': True, - 'wsgi.run_once': False, - 'wsgi.url_scheme': 'http', - 'wsgi.version': (1, 0), - } - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '303 See Other') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, 'http://brim.net?status=201&message=') - self.assertEquals(exc_info, None) - self.assertTrue('http://brim.net?status=201&message=' in body) - self.assertEquals(len(self.app.requests), 2) - self.assertEquals(self.app.requests[0].body, 'Test File\nOne\n') - self.assertEquals(self.app.requests[1].body, 'Test\nFile\nTwo\n') - - def test_firefox(self): - key = 'abc' - path = '/v1/AUTH_test/container' - redirect = 'http://brim.net' - max_file_size = 1024 - max_file_count = 10 - expires = int(time() + 86400) - sig = hmac.new(key, '%s\n%s\n%s\n%s\n%s' % (path, redirect, - max_file_size, max_file_count, expires), sha1).hexdigest() - memcache = FakeMemcache() - memcache.set('temp-url-key/AUTH_test', key) - wsgi_input = StringIO('\r\n'.join([ - '-----------------------------168072824752491622650073', - 'Content-Disposition: form-data; name="redirect"', - '', - redirect, - '-----------------------------168072824752491622650073', - 'Content-Disposition: form-data; name="max_file_size"', - '', - str(max_file_size), - '-----------------------------168072824752491622650073', - 'Content-Disposition: form-data; name="max_file_count"', - '', - str(max_file_count), - '-----------------------------168072824752491622650073', - 'Content-Disposition: form-data; name="expires"', - '', - str(expires), - '-----------------------------168072824752491622650073', - 'Content-Disposition: form-data; name="signature"', - '', - sig, - '-----------------------------168072824752491622650073', - 'Content-Disposition: form-data; name="file1"; ' - 'filename="testfile1.txt"', - 'Content-Type: text/plain', - '', - 'Test File\nOne\n', - '-----------------------------168072824752491622650073', - 'Content-Disposition: form-data; name="file2"; ' - 'filename="testfile2.txt"', - 'Content-Type: text/plain', - '', - 'Test\nFile\nTwo\n', - '-----------------------------168072824752491622650073', - 'Content-Disposition: form-data; name="file3"; filename=""', - 'Content-Type: application/octet-stream', - '', - '', - '-----------------------------168072824752491622650073--', - '' - ])) - wsgi_errors = StringIO() - env = { - 'CONTENT_TYPE': 'multipart/form-data; ' - 'boundary=---------------------------168072824752491622650073', - 'HTTP_ACCEPT_CHARSET': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', - 'HTTP_ACCEPT_ENCODING': 'gzip, deflate', - 'HTTP_ACCEPT_LANGUAGE': 'en-us,en;q=0.5', - 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;' - 'q=0.9,*/*;q=0.8', - 'HTTP_CONNECTION': 'keep-alive', - 'HTTP_HOST': 'ubuntu:8080', - 'HTTP_USER_AGENT': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; ' - 'rv:8.0.1) Gecko/20100101 Firefox/8.0.1', - 'PATH_INFO': '/v1/AUTH_test/container', - 'REMOTE_ADDR': '172.16.83.1', - 'REQUEST_METHOD': 'POST', - 'SCRIPT_NAME': '', - 'SERVER_NAME': '172.16.83.128', - 'SERVER_PORT': '8080', - 'SERVER_PROTOCOL': 'HTTP/1.0', - 'swift.cache': memcache, - 'wsgi.errors': wsgi_errors, - 'wsgi.input': wsgi_input, - 'wsgi.multiprocess': False, - 'wsgi.multithread': True, - 'wsgi.run_once': False, - 'wsgi.url_scheme': 'http', - 'wsgi.version': (1, 0), - } - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '303 See Other') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, 'http://brim.net?status=201&message=') - self.assertEquals(exc_info, None) - self.assertTrue('http://brim.net?status=201&message=' in body) - self.assertEquals(len(self.app.requests), 2) - self.assertEquals(self.app.requests[0].body, 'Test File\nOne\n') - self.assertEquals(self.app.requests[1].body, 'Test\nFile\nTwo\n') - - def test_chrome(self): - key = 'abc' - path = '/v1/AUTH_test/container' - redirect = 'http://brim.net' - max_file_size = 1024 - max_file_count = 10 - expires = int(time() + 86400) - sig = hmac.new(key, '%s\n%s\n%s\n%s\n%s' % (path, redirect, - max_file_size, max_file_count, expires), sha1).hexdigest() - memcache = FakeMemcache() - memcache.set('temp-url-key/AUTH_test', key) - wsgi_input = StringIO('\r\n'.join([ - '------WebKitFormBoundaryq3CFxUjfsDMu8XsA', - 'Content-Disposition: form-data; name="redirect"', - '', - redirect, - '------WebKitFormBoundaryq3CFxUjfsDMu8XsA', - 'Content-Disposition: form-data; name="max_file_size"', - '', - str(max_file_size), - '------WebKitFormBoundaryq3CFxUjfsDMu8XsA', - 'Content-Disposition: form-data; name="max_file_count"', - '', - str(max_file_count), - '------WebKitFormBoundaryq3CFxUjfsDMu8XsA', - 'Content-Disposition: form-data; name="expires"', - '', - str(expires), - '------WebKitFormBoundaryq3CFxUjfsDMu8XsA', - 'Content-Disposition: form-data; name="signature"', - '', - sig, - '------WebKitFormBoundaryq3CFxUjfsDMu8XsA', - 'Content-Disposition: form-data; name="file1"; ' - 'filename="testfile1.txt"', - 'Content-Type: text/plain', - '', - 'Test File\nOne\n', - '------WebKitFormBoundaryq3CFxUjfsDMu8XsA', - 'Content-Disposition: form-data; name="file2"; ' - 'filename="testfile2.txt"', - 'Content-Type: text/plain', - '', - 'Test\nFile\nTwo\n', - '------WebKitFormBoundaryq3CFxUjfsDMu8XsA', - 'Content-Disposition: form-data; name="file3"; filename=""', - 'Content-Type: application/octet-stream', - '', - '', - '------WebKitFormBoundaryq3CFxUjfsDMu8XsA--', - '' - ])) - wsgi_errors = StringIO() - env = { - 'CONTENT_TYPE': 'multipart/form-data; ' - 'boundary=----WebKitFormBoundaryq3CFxUjfsDMu8XsA', - 'HTTP_ACCEPT_CHARSET': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3', - 'HTTP_ACCEPT_ENCODING': 'gzip,deflate,sdch', - 'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.8', - 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;' - 'q=0.9,*/*;q=0.8', - 'HTTP_CACHE_CONTROL': 'max-age=0', - 'HTTP_CONNECTION': 'keep-alive', - 'HTTP_HOST': 'ubuntu:8080', - 'HTTP_ORIGIN': 'null', - 'HTTP_USER_AGENT': 'Mozilla/5.0 (Macintosh; Intel Mac OS X ' - '10_7_2) AppleWebKit/535.7 (KHTML, like Gecko) ' - 'Chrome/16.0.912.63 Safari/535.7', - 'PATH_INFO': '/v1/AUTH_test/container', - 'REMOTE_ADDR': '172.16.83.1', - 'REQUEST_METHOD': 'POST', - 'SCRIPT_NAME': '', - 'SERVER_NAME': '172.16.83.128', - 'SERVER_PORT': '8080', - 'SERVER_PROTOCOL': 'HTTP/1.0', - 'swift.cache': memcache, - 'wsgi.errors': wsgi_errors, - 'wsgi.input': wsgi_input, - 'wsgi.multiprocess': False, - 'wsgi.multithread': True, - 'wsgi.run_once': False, - 'wsgi.url_scheme': 'http', - 'wsgi.version': (1, 0), - } - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '303 See Other') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, 'http://brim.net?status=201&message=') - self.assertEquals(exc_info, None) - self.assertTrue('http://brim.net?status=201&message=' in body) - self.assertEquals(len(self.app.requests), 2) - self.assertEquals(self.app.requests[0].body, 'Test File\nOne\n') - self.assertEquals(self.app.requests[1].body, 'Test\nFile\nTwo\n') - - def test_explorer(self): - key = 'abc' - path = '/v1/AUTH_test/container' - redirect = 'http://brim.net' - max_file_size = 1024 - max_file_count = 10 - expires = int(time() + 86400) - sig = hmac.new(key, '%s\n%s\n%s\n%s\n%s' % (path, redirect, - max_file_size, max_file_count, expires), sha1).hexdigest() - memcache = FakeMemcache() - memcache.set('temp-url-key/AUTH_test', key) - wsgi_input = StringIO('\r\n'.join([ - '-----------------------------7db20d93017c', - 'Content-Disposition: form-data; name="redirect"', - '', - redirect, - '-----------------------------7db20d93017c', - 'Content-Disposition: form-data; name="max_file_size"', - '', - str(max_file_size), - '-----------------------------7db20d93017c', - 'Content-Disposition: form-data; name="max_file_count"', - '', - str(max_file_count), - '-----------------------------7db20d93017c', - 'Content-Disposition: form-data; name="expires"', - '', - str(expires), - '-----------------------------7db20d93017c', - 'Content-Disposition: form-data; name="signature"', - '', - sig, - '-----------------------------7db20d93017c', - 'Content-Disposition: form-data; name="file1"; ' - 'filename="C:\\testfile1.txt"', - 'Content-Type: text/plain', - '', - 'Test File\nOne\n', - '-----------------------------7db20d93017c', - 'Content-Disposition: form-data; name="file2"; ' - 'filename="C:\\testfile2.txt"', - 'Content-Type: text/plain', - '', - 'Test\nFile\nTwo\n', - '-----------------------------7db20d93017c', - 'Content-Disposition: form-data; name="file3"; filename=""', - 'Content-Type: application/octet-stream', - '', - '', - '-----------------------------7db20d93017c--', - '' - ])) - wsgi_errors = StringIO() - env = { - 'CONTENT_TYPE': 'multipart/form-data; ' - 'boundary=---------------------------7db20d93017c', - 'HTTP_ACCEPT_ENCODING': 'gzip, deflate', - 'HTTP_ACCEPT_LANGUAGE': 'en-US', - 'HTTP_ACCEPT': 'text/html, application/xhtml+xml, */*', - 'HTTP_CACHE_CONTROL': 'no-cache', - 'HTTP_CONNECTION': 'Keep-Alive', - 'HTTP_HOST': '172.16.83.128:8080', - 'HTTP_USER_AGENT': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT ' - '6.1; WOW64; Trident/5.0)', - 'PATH_INFO': '/v1/AUTH_test/container', - 'REMOTE_ADDR': '172.16.83.129', - 'REQUEST_METHOD': 'POST', - 'SCRIPT_NAME': '', - 'SERVER_NAME': '172.16.83.128', - 'SERVER_PORT': '8080', - 'SERVER_PROTOCOL': 'HTTP/1.0', - 'swift.cache': memcache, - 'wsgi.errors': wsgi_errors, - 'wsgi.input': wsgi_input, - 'wsgi.multiprocess': False, - 'wsgi.multithread': True, - 'wsgi.run_once': False, - 'wsgi.url_scheme': 'http', - 'wsgi.version': (1, 0), - } - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '303 See Other') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, 'http://brim.net?status=201&message=') - self.assertEquals(exc_info, None) - self.assertTrue('http://brim.net?status=201&message=' in body) - self.assertEquals(len(self.app.requests), 2) - self.assertEquals(self.app.requests[0].body, 'Test File\nOne\n') - self.assertEquals(self.app.requests[1].body, 'Test\nFile\nTwo\n') - - def test_messed_up_start(self): - key = 'abc' - sig, env, body = self._make_sig_env_body('/v1/AUTH_test/container', - 'http://brim.net', 5, 10, int(time() + 86400), key) - env['wsgi.input'] = StringIO('XX' + '\r\n'.join(body)) - env['swift.cache'] = FakeMemcache() - env['swift.cache'].set('temp-url-key/AUTH_test', key) - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '400 Bad Request') - self.assertEquals(exc_info, None) - self.assertTrue('FormPost: invalid starting boundary' in body) - self.assertEquals(len(self.app.requests), 0) - - def test_max_file_size_exceeded(self): - key = 'abc' - sig, env, body = self._make_sig_env_body('/v1/AUTH_test/container', - 'http://brim.net', 5, 10, int(time() + 86400), key) - env['wsgi.input'] = StringIO('\r\n'.join(body)) - env['swift.cache'] = FakeMemcache() - env['swift.cache'].set('temp-url-key/AUTH_test', key) - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '400 Bad Request') - self.assertEquals(exc_info, None) - self.assertTrue('FormPost: max_file_size exceeded' in body) - self.assertEquals(len(self.app.requests), 0) - - def test_max_file_count_exceeded(self): - key = 'abc' - sig, env, body = self._make_sig_env_body('/v1/AUTH_test/container', - 'http://brim.net', 1024, 1, int(time() + 86400), key) - env['wsgi.input'] = StringIO('\r\n'.join(body)) - env['swift.cache'] = FakeMemcache() - env['swift.cache'].set('temp-url-key/AUTH_test', key) - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '303 See Other') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, - 'http://brim.net?status=400&message=max%20file%20count%20exceeded') - self.assertEquals(exc_info, None) - self.assertTrue( - 'http://brim.net?status=400&message=max%20file%20count%20exceeded' - in body) - self.assertEquals(len(self.app.requests), 1) - self.assertEquals(self.app.requests[0].body, 'Test File\nOne\n') - - def test_subrequest_fails(self): - key = 'abc' - sig, env, body = self._make_sig_env_body('/v1/AUTH_test/container', - 'http://brim.net', 1024, 10, int(time() + 86400), key) - env['wsgi.input'] = StringIO('\r\n'.join(body)) - env['swift.cache'] = FakeMemcache() - env['swift.cache'].set('temp-url-key/AUTH_test', key) - self.app = FakeApp(iter([('404 Not Found', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '303 See Other') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, 'http://brim.net?status=404&message=') - self.assertEquals(exc_info, None) - self.assertTrue('http://brim.net?status=404&message=' in body) - self.assertEquals(len(self.app.requests), 1) - - def test_truncated_attr_value(self): - key = 'abc' - redirect = 'a' * formpost.MAX_VALUE_LENGTH - max_file_size = 1024 - max_file_count = 10 - expires = int(time() + 86400) - sig, env, body = self._make_sig_env_body('/v1/AUTH_test/container', - redirect, max_file_size, max_file_count, expires, key) - # Tack on an extra char to redirect, but shouldn't matter since it - # should get truncated off on read. - redirect += 'b' - env['wsgi.input'] = StringIO('\r\n'.join([ - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="redirect"', - '', - redirect, - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="max_file_size"', - '', - str(max_file_size), - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="max_file_count"', - '', - str(max_file_count), - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="expires"', - '', - str(expires), - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="signature"', - '', - sig, - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="file1"; ' - 'filename="testfile1.txt"', - 'Content-Type: text/plain', - '', - 'Test File\nOne\n', - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="file2"; ' - 'filename="testfile2.txt"', - 'Content-Type: text/plain', - '', - 'Test\nFile\nTwo\n', - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="file3"; filename=""', - 'Content-Type: application/octet-stream', - '', - '', - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR--', - '', - ])) - env['swift.cache'] = FakeMemcache() - env['swift.cache'].set('temp-url-key/AUTH_test', key) - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '303 See Other') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, - ('a' * formpost.MAX_VALUE_LENGTH) + '?status=201&message=') - self.assertEquals(exc_info, None) - self.assertTrue( - ('a' * formpost.MAX_VALUE_LENGTH) + '?status=201&message=' in body) - self.assertEquals(len(self.app.requests), 2) - self.assertEquals(self.app.requests[0].body, 'Test File\nOne\n') - self.assertEquals(self.app.requests[1].body, 'Test\nFile\nTwo\n') - - def test_no_file_to_process(self): - key = 'abc' - redirect = 'http://brim.net' - max_file_size = 1024 - max_file_count = 10 - expires = int(time() + 86400) - sig, env, body = self._make_sig_env_body('/v1/AUTH_test/container', - redirect, max_file_size, max_file_count, expires, key) - env['wsgi.input'] = StringIO('\r\n'.join([ - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="redirect"', - '', - redirect, - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="max_file_size"', - '', - str(max_file_size), - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="max_file_count"', - '', - str(max_file_count), - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="expires"', - '', - str(expires), - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', - 'Content-Disposition: form-data; name="signature"', - '', - sig, - '------WebKitFormBoundaryNcxTqxSlX7t4TDkR--', - '', - ])) - env['swift.cache'] = FakeMemcache() - env['swift.cache'].set('temp-url-key/AUTH_test', key) - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '303 See Other') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, - 'http://brim.net?status=400&message=no%20files%20to%20process') - self.assertEquals(exc_info, None) - self.assertTrue( - 'http://brim.net?status=400&message=no%20files%20to%20process' - in body) - self.assertEquals(len(self.app.requests), 0) - - def test_no_redirect(self): - key = 'abc' - sig, env, body = self._make_sig_env_body( - '/v1/AUTH_test/container', '', 1024, 10, int(time() + 86400), key) - env['wsgi.input'] = StringIO('\r\n'.join(body)) - env['swift.cache'] = FakeMemcache() - env['swift.cache'].set('temp-url-key/AUTH_test', key) - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '201 Created') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, None) - self.assertEquals(exc_info, None) - self.assertTrue('201 Created' in body) - self.assertEquals(len(self.app.requests), 2) - self.assertEquals(self.app.requests[0].body, 'Test File\nOne\n') - self.assertEquals(self.app.requests[1].body, 'Test\nFile\nTwo\n') - - def test_no_redirect_expired(self): - key = 'abc' - sig, env, body = self._make_sig_env_body( - '/v1/AUTH_test/container', '', 1024, 10, int(time() - 10), key) - env['wsgi.input'] = StringIO('\r\n'.join(body)) - env['swift.cache'] = FakeMemcache() - env['swift.cache'].set('temp-url-key/AUTH_test', key) - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '401 Unauthorized') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, None) - self.assertEquals(exc_info, None) - self.assertTrue('FormPost: Form Expired' in body) - - def test_no_redirect_invalid_sig(self): - key = 'abc' - sig, env, body = self._make_sig_env_body( - '/v1/AUTH_test/container', '', 1024, 10, int(time() + 86400), key) - env['wsgi.input'] = StringIO('\r\n'.join(body)) - env['swift.cache'] = FakeMemcache() - # Change key to invalidate sig - key = 'def' - env['swift.cache'].set('temp-url-key/AUTH_test', key) - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '401 Unauthorized') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, None) - self.assertEquals(exc_info, None) - self.assertTrue('FormPost: Invalid Signature' in body) - - def test_no_redirect_with_error(self): - key = 'abc' - sig, env, body = self._make_sig_env_body( - '/v1/AUTH_test/container', '', 1024, 10, int(time() + 86400), key) - env['wsgi.input'] = StringIO('XX' + '\r\n'.join(body)) - env['swift.cache'] = FakeMemcache() - env['swift.cache'].set('temp-url-key/AUTH_test', key) - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '400 Bad Request') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, None) - self.assertEquals(exc_info, None) - self.assertTrue('FormPost: invalid starting boundary' in body) - - def test_no_v1(self): - key = 'abc' - sig, env, body = self._make_sig_env_body( - '/v2/AUTH_test/container', '', 1024, 10, int(time() + 86400), key) - env['wsgi.input'] = StringIO('\r\n'.join(body)) - env['swift.cache'] = FakeMemcache() - env['swift.cache'].set('temp-url-key/AUTH_test', key) - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '401 Unauthorized') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, None) - self.assertEquals(exc_info, None) - self.assertTrue('FormPost: Invalid Signature' in body) - - def test_empty_v1(self): - key = 'abc' - sig, env, body = self._make_sig_env_body( - '//AUTH_test/container', '', 1024, 10, int(time() + 86400), key) - env['wsgi.input'] = StringIO('\r\n'.join(body)) - env['swift.cache'] = FakeMemcache() - env['swift.cache'].set('temp-url-key/AUTH_test', key) - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '401 Unauthorized') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, None) - self.assertEquals(exc_info, None) - self.assertTrue('FormPost: Invalid Signature' in body) - - def test_empty_account(self): - key = 'abc' - sig, env, body = self._make_sig_env_body( - '/v1//container', '', 1024, 10, int(time() + 86400), key) - env['wsgi.input'] = StringIO('\r\n'.join(body)) - env['swift.cache'] = FakeMemcache() - env['swift.cache'].set('temp-url-key/AUTH_test', key) - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '401 Unauthorized') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, None) - self.assertEquals(exc_info, None) - self.assertTrue('FormPost: Invalid Signature' in body) - - def test_wrong_account(self): - key = 'abc' - sig, env, body = self._make_sig_env_body( - '/v1/AUTH_tst/container', '', 1024, 10, int(time() + 86400), key) - env['wsgi.input'] = StringIO('\r\n'.join(body)) - env['swift.cache'] = FakeMemcache() - env['swift.cache'].set('temp-url-key/AUTH_test', key) - self.app = FakeApp(iter([ - ('200 Ok', {'x-account-meta-temp-url-key': 'def'}, ''), - ('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '401 Unauthorized') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, None) - self.assertEquals(exc_info, None) - self.assertTrue('FormPost: Invalid Signature' in body) - - def test_no_container(self): - key = 'abc' - sig, env, body = self._make_sig_env_body( - '/v1/AUTH_test', '', 1024, 10, int(time() + 86400), key) - env['wsgi.input'] = StringIO('\r\n'.join(body)) - env['swift.cache'] = FakeMemcache() - env['swift.cache'].set('temp-url-key/AUTH_test', key) - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '401 Unauthorized') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, None) - self.assertEquals(exc_info, None) - self.assertTrue('FormPost: Invalid Signature' in body) - - def test_completely_non_int_expires(self): - key = 'abc' - expires = int(time() + 86400) - sig, env, body = self._make_sig_env_body( - '/v1/AUTH_test/container', '', 1024, 10, expires, key) - for i, v in enumerate(body): - if v == str(expires): - body[i] = 'badvalue' - break - env['wsgi.input'] = StringIO('\r\n'.join(body)) - env['swift.cache'] = FakeMemcache() - env['swift.cache'].set('temp-url-key/AUTH_test', key) - self.app = FakeApp(iter([('201 Created', {}, ''), - ('201 Created', {}, '')])) - self.auth = tempauth.filter_factory({})(self.app) - self.formpost = formpost.filter_factory({})(self.auth) - status = [None] - headers = [None] - exc_info = [None] - - def start_response(s, h, e=None): - status[0] = s - headers[0] = h - exc_info[0] = e - - body = ''.join(self.formpost(env, start_response)) - status = status[0] - headers = headers[0] - exc_info = exc_info[0] - self.assertEquals(status, '400 Bad Request') - location = None - for h, v in headers: - if h.lower() == 'location': - location = v - self.assertEquals(location, None) - self.assertEquals(exc_info, None) - self.assertTrue('FormPost: expired not an integer' in body) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/unit/common/middleware/test_tempurl.py b/test/unit/common/middleware/test_tempurl.py deleted file mode 100644 index eb91c65bbe..0000000000 --- a/test/unit/common/middleware/test_tempurl.py +++ /dev/null @@ -1,655 +0,0 @@ -# Copyright (c) 2011 OpenStack, LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import hmac -import unittest -from hashlib import sha1 -from contextlib import contextmanager -from time import time - -from webob import Request, Response - -from swift.common.middleware import tempauth, tempurl - - -class FakeMemcache(object): - - def __init__(self): - self.store = {} - - def get(self, key): - return self.store.get(key) - - def set(self, key, value, timeout=0): - self.store[key] = value - return True - - def incr(self, key, timeout=0): - self.store[key] = self.store.setdefault(key, 0) + 1 - return self.store[key] - - @contextmanager - def soft_lock(self, key, timeout=0, retries=5): - yield True - - def delete(self, key): - try: - del self.store[key] - except Exception: - pass - return True - - -class FakeApp(object): - - def __init__(self, status_headers_body_iter=None): - self.calls = 0 - self.status_headers_body_iter = status_headers_body_iter - if not self.status_headers_body_iter: - self.status_headers_body_iter = iter([('404 Not Found', { - 'x-test-header-one-a': 'value1', - 'x-test-header-two-a': 'value2', - 'x-test-header-two-b': 'value3'}, '')]) - self.request = None - - def __call__(self, env, start_response): - self.calls += 1 - self.request = Request.blank('', environ=env) - if 'swift.authorize' in env: - resp = env['swift.authorize'](self.request) - if resp: - return resp(env, start_response) - status, headers, body = self.status_headers_body_iter.next() - return Response(status=status, headers=headers, - body=body)(env, start_response) - - -class TestTempURL(unittest.TestCase): - - def setUp(self): - self.app = FakeApp() - self.auth = tempauth.filter_factory({})(self.app) - self.tempurl = tempurl.filter_factory({})(self.auth) - - def _make_request(self, path, **kwargs): - req = Request.blank(path, **kwargs) - req.environ['swift.cache'] = FakeMemcache() - return req - - def test_passthrough(self): - resp = self._make_request('/v1/a/c/o').get_response(self.tempurl) - self.assertEquals(resp.status_int, 401) - self.assertTrue('Temp URL invalid' not in resp.body) - - def test_get_valid(self): - method = 'GET' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 404) - self.assertEquals(resp.headers['content-disposition'], - 'attachment; filename=o') - self.assertEquals(resp.environ['swift.authorize_override'], True) - self.assertEquals(resp.environ['REMOTE_USER'], '.wsgi.tempurl') - - def test_put_not_allowed_by_get(self): - method = 'GET' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'REQUEST_METHOD': 'PUT', - 'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 401) - self.assertTrue('Temp URL invalid' in resp.body) - - def test_put_valid(self): - method = 'PUT' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'REQUEST_METHOD': 'PUT', - 'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 404) - self.assertEquals(resp.environ['swift.authorize_override'], True) - self.assertEquals(resp.environ['REMOTE_USER'], '.wsgi.tempurl') - - def test_get_not_allowed_by_put(self): - method = 'PUT' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 401) - self.assertTrue('Temp URL invalid' in resp.body) - - def test_missing_sig(self): - method = 'GET' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'QUERY_STRING': 'temp_url_expires=%s' % expires}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 401) - self.assertTrue('Temp URL invalid' in resp.body) - - def test_missing_expires(self): - method = 'GET' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'QUERY_STRING': 'temp_url_sig=%s' % sig}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 401) - self.assertTrue('Temp URL invalid' in resp.body) - - def test_bad_path(self): - method = 'GET' - expires = int(time() + 86400) - path = '/v1/a/c/' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 401) - self.assertTrue('Temp URL invalid' in resp.body) - - def test_no_key(self): - method = 'GET' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 401) - self.assertTrue('Temp URL invalid' in resp.body) - - def test_head_allowed_by_get(self): - method = 'GET' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'REQUEST_METHOD': 'HEAD', - 'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 404) - self.assertEquals(resp.environ['swift.authorize_override'], True) - self.assertEquals(resp.environ['REMOTE_USER'], '.wsgi.tempurl') - - def test_head_allowed_by_put(self): - method = 'PUT' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'REQUEST_METHOD': 'HEAD', - 'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 404) - self.assertEquals(resp.environ['swift.authorize_override'], True) - self.assertEquals(resp.environ['REMOTE_USER'], '.wsgi.tempurl') - - def test_head_otherwise_not_allowed(self): - method = 'PUT' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - # Deliberately fudge expires to show HEADs aren't just automatically - # allowed. - expires += 1 - req = self._make_request(path, - environ={'REQUEST_METHOD': 'HEAD', - 'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 401) - - def test_post_not_allowed(self): - method = 'POST' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'REQUEST_METHOD': 'POST', - 'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 401) - self.assertTrue('Temp URL invalid' in resp.body) - - def test_delete_not_allowed(self): - method = 'DELETE' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'REQUEST_METHOD': 'DELETE', - 'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 401) - self.assertTrue('Temp URL invalid' in resp.body) - - def test_unknown_not_allowed(self): - method = 'UNKNOWN' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'REQUEST_METHOD': 'UNKNOWN', - 'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 401) - self.assertTrue('Temp URL invalid' in resp.body) - - def test_changed_path_invalid(self): - method = 'GET' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path + '2', - environ={'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 401) - self.assertTrue('Temp URL invalid' in resp.body) - - def test_changed_sig_invalid(self): - method = 'GET' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - if sig[-1] != '0': - sig = sig[:-1] + '0' - else: - sig = sig[:-1] + '1' - req = self._make_request(path, - environ={'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 401) - self.assertTrue('Temp URL invalid' in resp.body) - - def test_changed_expires_invalid(self): - method = 'GET' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % - (sig, expires + 1)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 401) - self.assertTrue('Temp URL invalid' in resp.body) - - def test_different_key_invalid(self): - method = 'GET' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key + '2') - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 401) - self.assertTrue('Temp URL invalid' in resp.body) - - def test_removed_incoming_header(self): - self.tempurl = tempurl.filter_factory({ - 'incoming_remove_headers': 'x-remove-this'})(self.auth) - method = 'GET' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, headers={'x-remove-this': 'value'}, - environ={'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 404) - self.assertTrue('x-remove-this' not in self.app.request.headers) - - def test_removed_incoming_headers_match(self): - self.tempurl = tempurl.filter_factory({ - 'incoming_remove_headers': 'x-remove-this-*', - 'incoming_allow_headers': 'x-remove-this-except-this'})(self.auth) - method = 'GET' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - headers={'x-remove-this-one': 'value1', - 'x-remove-this-except-this': 'value2'}, - environ={'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 404) - self.assertTrue('x-remove-this-one' not in self.app.request.headers) - self.assertEquals( - self.app.request.headers['x-remove-this-except-this'], 'value2') - - def test_removed_outgoing_header(self): - self.tempurl = tempurl.filter_factory({ - 'outgoing_remove_headers': 'x-test-header-one-a'})(self.auth) - method = 'GET' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 404) - self.assertTrue('x-test-header-one-a' not in resp.headers) - self.assertEquals(resp.headers['x-test-header-two-a'], 'value2') - - def test_removed_outgoing_headers_match(self): - self.tempurl = tempurl.filter_factory({ - 'outgoing_remove_headers': 'x-test-header-two-*', - 'outgoing_allow_headers': 'x-test-header-two-b'})(self.auth) - method = 'GET' - expires = int(time() + 86400) - path = '/v1/a/c/o' - key = 'abc' - hmac_body = '%s\n%s\n%s' % (method, expires, path) - sig = hmac.new(key, hmac_body, sha1).hexdigest() - req = self._make_request(path, - environ={'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)}) - req.environ['swift.cache'].set('temp-url-key/a', key) - resp = req.get_response(self.tempurl) - self.assertEquals(resp.status_int, 404) - self.assertEquals(resp.headers['x-test-header-one-a'], 'value1') - self.assertTrue('x-test-header-two-a' not in resp.headers) - self.assertEquals(resp.headers['x-test-header-two-b'], 'value3') - - def test_get_account(self): - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'HEAD', 'PATH_INFO': '/v1/a/c/o'}), 'a') - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c/o'}), 'a') - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'PUT', 'PATH_INFO': '/v1/a/c/o'}), 'a') - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'POST', 'PATH_INFO': '/v1/a/c/o'}), None) - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'DELETE', 'PATH_INFO': '/v1/a/c/o'}), None) - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'UNKNOWN', 'PATH_INFO': '/v1/a/c/o'}), None) - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c/'}), None) - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c//////'}), None) - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c///o///'}), 'a') - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c'}), None) - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a//o'}), None) - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1//c/o'}), None) - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '//a/c/o'}), None) - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v2/a/c/o'}), None) - - def test_get_temp_url_info(self): - s = 'f5d5051bddf5df7e27c628818738334f' - e = int(time() + 86400) - self.assertEquals(self.tempurl._get_temp_url_info({'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (s, e)}), (s, e)) - self.assertEquals(self.tempurl._get_temp_url_info({}), (None, None)) - self.assertEquals(self.tempurl._get_temp_url_info({'QUERY_STRING': - 'temp_url_expires=%s' % e}), (None, e)) - self.assertEquals(self.tempurl._get_temp_url_info({'QUERY_STRING': - 'temp_url_sig=%s' % s}), (s, None)) - self.assertEquals(self.tempurl._get_temp_url_info({'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=bad' % s}), (s, 0)) - e = int(time() - 1) - self.assertEquals(self.tempurl._get_temp_url_info({'QUERY_STRING': - 'temp_url_sig=%s&temp_url_expires=%s' % (s, e)}), (s, 0)) - - def test_get_key_memcache(self): - self.app.status_headers_body_iter = iter([('404 Not Found', {}, '')]) - self.assertEquals( - self.tempurl._get_key({}, 'a'), None) - self.app.status_headers_body_iter = iter([('404 Not Found', {}, '')]) - self.assertEquals( - self.tempurl._get_key({'swift.cache': None}, 'a'), None) - mc = FakeMemcache() - self.app.status_headers_body_iter = iter([('404 Not Found', {}, '')]) - self.assertEquals( - self.tempurl._get_key({'swift.cache': mc}, 'a'), None) - mc.set('temp-url-key/a', 'abc') - self.assertEquals( - self.tempurl._get_key({'swift.cache': mc}, 'a'), 'abc') - - def test_get_key_from_source(self): - self.app.status_headers_body_iter = \ - iter([('200 Ok', {'x-account-meta-temp-url-key': 'abc'}, '')]) - mc = FakeMemcache() - self.assertEquals( - self.tempurl._get_key({'swift.cache': mc}, 'a'), 'abc') - self.assertEquals(mc.get('temp-url-key/a'), 'abc') - - def test_get_hmac(self): - self.assertEquals(self.tempurl._get_hmac( - {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c/o'}, - 1, 'abc'), - '026d7f7cc25256450423c7ad03fc9f5ffc1dab6d') - self.assertEquals(self.tempurl._get_hmac( - {'REQUEST_METHOD': 'HEAD', 'PATH_INFO': '/v1/a/c/o'}, - 1, 'abc', request_method='GET'), - '026d7f7cc25256450423c7ad03fc9f5ffc1dab6d') - - def test_invalid(self): - - def _start_response(status, headers, exc_info=None): - self.assertTrue(status, '401 Unauthorized') - - self.assertTrue('Temp URL invalid' in - ''.join(self.tempurl._invalid({'REQUEST_METHOD': 'GET'}, - _start_response))) - self.assertEquals('', - ''.join(self.tempurl._invalid({'REQUEST_METHOD': 'HEAD'}, - _start_response))) - - def test_clean_incoming_headers(self): - irh = '' - iah = '' - env = {'HTTP_TEST_HEADER': 'value'} - tempurl.TempURL(None, {'incoming_remove_headers': irh, - 'incoming_allow_headers': iah})._clean_incoming_headers(env) - self.assertTrue('HTTP_TEST_HEADER' in env) - - irh = 'test-header' - iah = '' - env = {'HTTP_TEST_HEADER': 'value'} - tempurl.TempURL(None, {'incoming_remove_headers': irh, - 'incoming_allow_headers': iah})._clean_incoming_headers(env) - self.assertTrue('HTTP_TEST_HEADER' not in env) - - irh = 'test-header-*' - iah = '' - env = {'HTTP_TEST_HEADER_ONE': 'value', - 'HTTP_TEST_HEADER_TWO': 'value'} - tempurl.TempURL(None, {'incoming_remove_headers': irh, - 'incoming_allow_headers': iah})._clean_incoming_headers(env) - self.assertTrue('HTTP_TEST_HEADER_ONE' not in env) - self.assertTrue('HTTP_TEST_HEADER_TWO' not in env) - - irh = 'test-header-*' - iah = 'test-header-two' - env = {'HTTP_TEST_HEADER_ONE': 'value', - 'HTTP_TEST_HEADER_TWO': 'value'} - tempurl.TempURL(None, {'incoming_remove_headers': irh, - 'incoming_allow_headers': iah})._clean_incoming_headers(env) - self.assertTrue('HTTP_TEST_HEADER_ONE' not in env) - self.assertTrue('HTTP_TEST_HEADER_TWO' in env) - - irh = 'test-header-* test-other-header' - iah = 'test-header-two test-header-yes-*' - env = {'HTTP_TEST_HEADER_ONE': 'value', - 'HTTP_TEST_HEADER_TWO': 'value', - 'HTTP_TEST_OTHER_HEADER': 'value', - 'HTTP_TEST_HEADER_YES': 'value', - 'HTTP_TEST_HEADER_YES_THIS': 'value'} - tempurl.TempURL(None, {'incoming_remove_headers': irh, - 'incoming_allow_headers': iah})._clean_incoming_headers(env) - self.assertTrue('HTTP_TEST_HEADER_ONE' not in env) - self.assertTrue('HTTP_TEST_HEADER_TWO' in env) - self.assertTrue('HTTP_TEST_OTHER_HEADER' not in env) - self.assertTrue('HTTP_TEST_HEADER_YES' not in env) - self.assertTrue('HTTP_TEST_HEADER_YES_THIS' in env) - - def test_clean_outgoing_headers(self): - orh = '' - oah = '' - hdrs = {'test-header': 'value'} - hdrs = dict(tempurl.TempURL(None, - {'outgoing_remove_headers': orh, 'outgoing_allow_headers': oah} - )._clean_outgoing_headers(hdrs.iteritems())) - self.assertTrue('test-header' in hdrs) - - orh = 'test-header' - oah = '' - hdrs = {'test-header': 'value'} - hdrs = dict(tempurl.TempURL(None, - {'outgoing_remove_headers': orh, 'outgoing_allow_headers': oah} - )._clean_outgoing_headers(hdrs.iteritems())) - self.assertTrue('test-header' not in hdrs) - - orh = 'test-header-*' - oah = '' - hdrs = {'test-header-one': 'value', - 'test-header-two': 'value'} - hdrs = dict(tempurl.TempURL(None, - {'outgoing_remove_headers': orh, 'outgoing_allow_headers': oah} - )._clean_outgoing_headers(hdrs.iteritems())) - self.assertTrue('test-header-one' not in hdrs) - self.assertTrue('test-header-two' not in hdrs) - - orh = 'test-header-*' - oah = 'test-header-two' - hdrs = {'test-header-one': 'value', - 'test-header-two': 'value'} - hdrs = dict(tempurl.TempURL(None, - {'outgoing_remove_headers': orh, 'outgoing_allow_headers': oah} - )._clean_outgoing_headers(hdrs.iteritems())) - self.assertTrue('test-header-one' not in hdrs) - self.assertTrue('test-header-two' in hdrs) - - orh = 'test-header-* test-other-header' - oah = 'test-header-two test-header-yes-*' - hdrs = {'test-header-one': 'value', - 'test-header-two': 'value', - 'test-other-header': 'value', - 'test-header-yes': 'value', - 'test-header-yes-this': 'value'} - hdrs = dict(tempurl.TempURL(None, - {'outgoing_remove_headers': orh, 'outgoing_allow_headers': oah} - )._clean_outgoing_headers(hdrs.iteritems())) - self.assertTrue('test-header-one' not in hdrs) - self.assertTrue('test-header-two' in hdrs) - self.assertTrue('test-other-header' not in hdrs) - self.assertTrue('test-header-yes' not in hdrs) - self.assertTrue('test-header-yes-this' in hdrs) - - -if __name__ == '__main__': - unittest.main()