def test_admin_setup(self): # PUTs for account and 16 .hash's self.test_origin.app = FakeApp(iter([("204 No Content", {}, "") for i in xrange(201)])) resp = Request.blank( "/origin/.prep", environ={"REQUEST_METHOD": "PUT"}, headers={"X-Origin-Admin-User": "******", "X-Origin-Admin-Key": "unittest"}, ).get_response(self.test_origin) self.assertEquals(resp.status_int, 204) self.assertEquals(self.test_origin.app.calls, 101) self.test_origin.app = FakeApp(iter([("404 Not Found", {}, "")])) req = Request.blank( "/origin/.prep", environ={"REQUEST_METHOD": "PUT"}, headers={"X-Origin-Admin-User": "******", "X-Origin-Admin-Key": "unittest"}, ) self.assertRaises(Exception, req.get_response, self.test_origin) self.test_origin.app = FakeApp(iter([("204 No Content", {}, ""), ("404 Not Found", {}, "")])) req = Request.blank( "/origin/.prep", environ={"REQUEST_METHOD": "PUT"}, headers={"X-Origin-Admin-User": "******", "X-Origin-Admin-Key": "unittest"}, ) self.assertRaises(Exception, req.get_response, self.test_origin)
def test_bucket_acl_PUT(self): elem = Element('AccessControlPolicy') owner = SubElement(elem, 'Owner') SubElement(owner, 'ID').text = 'id' acl = SubElement(elem, 'AccessControlList') grant = SubElement(acl, 'Grant') grantee = SubElement(grant, 'Grantee', nsmap={'xsi': XMLNS_XSI}) grantee.set('{%s}type' % XMLNS_XSI, 'Group') SubElement(grantee, 'URI').text = \ 'http://acs.amazonaws.com/groups/global/AllUsers' SubElement(grant, 'Permission').text = 'READ' xml = tostring(elem) req = Request.blank('/bucket?acl', environ={'REQUEST_METHOD': 'PUT'}, headers={'Authorization': 'AWS test:tester:hmac'}, body=xml) status, headers, body = self.call_swift3(req) self.assertEquals(status.split()[0], '200') req = Request.blank('/bucket?acl', environ={'REQUEST_METHOD': 'PUT', 'wsgi.input': StringIO(xml)}, headers={'Authorization': 'AWS test:tester:hmac', 'Transfer-Encoding': 'chunked'}) self.assertIsNone(req.content_length) self.assertIsNone(req.message_length()) status, headers, body = self.call_swift3(req) self.assertEquals(status.split()[0], '200')
def test_proxy_client_logging(self): app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {}) app.access_logger = FakeLogger() req = Request.blank('/', environ={ 'REQUEST_METHOD': 'GET', 'REMOTE_ADDR': '1.2.3.4', 'HTTP_X_FORWARDED_FOR': '4.5.6.7,8.9.10.11'}) resp = app(req.environ, start_response) # exhaust generator [x for x in resp] log_parts = self._log_parts(app) self.assertEquals(log_parts[0], '4.5.6.7') # client ip self.assertEquals(log_parts[1], '1.2.3.4') # remote addr app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {}) app.access_logger = FakeLogger() req = Request.blank('/', environ={ 'REQUEST_METHOD': 'GET', 'REMOTE_ADDR': '1.2.3.4', 'HTTP_X_CLUSTER_CLIENT_IP': '4.5.6.7'}) resp = app(req.environ, start_response) # exhaust generator [x for x in resp] log_parts = self._log_parts(app) self.assertEquals(log_parts[0], '4.5.6.7') # client ip self.assertEquals(log_parts[1], '1.2.3.4') # remote addr
def test_origin_db_post_fail(self): self.test_origin.app = FakeApp( iter([("204 No Content", {}, ""), ("404 Not Found", {}, "")]) # call to _get_cdn_data # put to .hash ) req = Request.blank("http://origin_db.com:8080/v1/acc/cont", environ={"REQUEST_METHOD": "PUT"}) resp = req.get_response(self.test_origin) self.assertEquals(resp.status_int, 500) self.test_origin.app = FakeApp( iter( [ ("204 No Content", {}, ""), # call to _get_cdn_data ("204 No Content", {}, ""), # put to .hash ("404 Not Found", {}, ""), # HEAD check to list container ("404 Not Found", {}, ""), # PUT to list container ] ) ) req = Request.blank("http://origin_db.com:8080/v1/acc/cont", environ={"REQUEST_METHOD": "PUT"}) resp = req.get_response(self.test_origin) self.assertEquals(resp.status_int, 500) self.test_origin.app = FakeApp( iter( [ ("204 No Content", {}, ""), # call to _get_cdn_data ("204 No Content", {}, ""), # put to .hash ("204 No Content", {}, ""), # HEAD check to list container ("404 Not Found", {}, ""), # PUT to list container ] ) ) req = Request.blank("http://origin_db.com:8080/v1/acc/cont", environ={"REQUEST_METHOD": "PUT"}) resp = req.get_response(self.test_origin) self.assertEquals(resp.status_int, 500)
def test_extract_tar_works(self): # On systems where $TMPDIR is long (like OS X), we need to do this # or else every upload will fail due to the path being too long. self.app.max_pathlen += len(self.testdir) for compress_format in ['', 'gz', 'bz2']: base_name = 'base_works_%s' % compress_format dir_tree = [ {base_name: [{'sub_dir1': ['sub1_file1', 'sub1_file2']}, {'sub_dir2': ['sub2_file1', u'test obj \u2661']}, 'sub_file1', {'sub_dir3': [{'sub4_dir1': '../sub4 file1'}]}, {'sub_dir4': None}, ]}] build_dir_tree(self.testdir, dir_tree) mode = 'w' extension = '' if compress_format: mode += ':' + compress_format extension += '.' + compress_format tar = tarfile.open(name=os.path.join(self.testdir, 'tar_works.tar' + extension), mode=mode) tar.add(os.path.join(self.testdir, base_name)) tar.close() req = Request.blank('/tar_works/acc/cont/') req.environ['wsgi.input'] = open( os.path.join(self.testdir, 'tar_works.tar' + extension)) req.headers['transfer-encoding'] = 'chunked' resp_body = self.handle_extract_and_iter(req, compress_format) resp_data = utils.json.loads(resp_body) self.assertEquals(resp_data['Number Files Created'], 6) # test out xml req = Request.blank('/tar_works/acc/cont/') req.environ['wsgi.input'] = open( os.path.join(self.testdir, 'tar_works.tar' + extension)) req.headers['transfer-encoding'] = 'chunked' resp_body = self.handle_extract_and_iter( req, compress_format, 'application/xml') self.assert_('<response_status>201 Created</response_status>' in resp_body) self.assert_('<number_files_created>6</number_files_created>' in resp_body) # test out nonexistent format req = Request.blank('/tar_works/acc/cont/?extract-archive=tar', headers={'Accept': 'good_xml'}) req.environ['REQUEST_METHOD'] = 'PUT' req.environ['wsgi.input'] = open( os.path.join(self.testdir, 'tar_works.tar' + extension)) req.headers['transfer-encoding'] = 'chunked' def fake_start_response(*args, **kwargs): pass app_iter = self.bulk(req.environ, fake_start_response) resp_body = ''.join([i for i in app_iter]) self.assert_('Response Status: 406' in resp_body)
def test_get_account_info_cache(self): # Works with fake apps that return ints in the headers cached = {'status': 404, 'bytes': 3333, 'total_object_count': 10} req = Request.blank("/v1/account/cont", environ={'swift.cache': FakeCache(cached)}) resp = get_account_info(req.environ, FakeApp()) self.assertEqual(resp['bytes'], 3333) self.assertEqual(resp['total_object_count'], 10) self.assertEqual(resp['status'], 404) # Works with strings too, like you get when parsing HTTP headers # that came in through a socket from the account server cached = {'status': 404, 'bytes': '3333', 'container_count': '234', 'total_object_count': '10', 'meta': {}} req = Request.blank("/v1/account/cont", environ={'swift.cache': FakeCache(cached)}) resp = get_account_info(req.environ, FakeApp()) self.assertEqual(resp['status'], 404) self.assertEqual(resp['bytes'], 3333) self.assertEqual(resp['container_count'], 234) self.assertEqual(resp['meta'], {}) self.assertEqual(resp['total_object_count'], 10)
def test_policy_index(self): # Policy index can be specified by X-Backend-Storage-Policy-Index # in the request header for object API app = proxy_logging.ProxyLoggingMiddleware(FakeApp(policy_idx='1'), {}) app.access_logger = FakeLogger() req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}) resp = app(req.environ, start_response) ''.join(resp) log_parts = self._log_parts(app) self.assertEquals(log_parts[20], '1') # Policy index can be specified by X-Backend-Storage-Policy-Index # in the response header for container API app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {}) app.access_logger = FakeLogger() req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': 'GET'}) def fake_call(app, env, start_response): start_response(app.response_str, [('Content-Type', 'text/plain'), ('Content-Length', str(sum(map(len, app.body)))), ('X-Backend-Storage-Policy-Index', '1')]) while env['wsgi.input'].read(5): pass return app.body with mock.patch.object(FakeApp, '__call__', fake_call): resp = app(req.environ, start_response) ''.join(resp) log_parts = self._log_parts(app) self.assertEquals(log_parts[20], '1')
def test_check_signature_multi_bytes_secret_failure(self): # Test v2 check_signature with multi bytes invalid secret req = Request.blank('/photos/puppy.jpg', headers={ 'Host': 'johnsmith.s3.amazonaws.com', 'Date': 'Tue, 27 Mar 2007 19:36:42 +0000', 'Authorization': ('AWS AKIAIOSFODNN7EXAMPLE:' 'bWq2s1WEIj+Ydj0vQ697zp+IXMU='), }) sigv2_req = S3Request(req.environ, storage_domain='s3.amazonaws.com') # This is a failure case with utf-8 non-ascii multi-bytes charactor # but we expect to return just False instead of exceptions self.assertFalse(sigv2_req.check_signature( u'\u30c9\u30e9\u30b4\u30f3')) # Test v4 check_signature with multi bytes invalid secret amz_date_header = self.get_v4_amz_date_header() req = Request.blank('/photos/puppy.jpg', headers={ 'Authorization': 'AWS4-HMAC-SHA256 ' 'Credential=test/%s/us-east-1/s3/aws4_request, ' 'SignedHeaders=host;x-amz-content-sha256;x-amz-date,' 'Signature=X' % amz_date_header.split('T', 1)[0], 'X-Amz-Content-SHA256': '0123456789', 'X-Amz-Date': amz_date_header }) sigv4_req = SigV4Request( req.environ, storage_domain='s3.amazonaws.com') self.assertFalse(sigv4_req.check_signature( u'\u30c9\u30e9\u30b4\u30f3'))
def test_proxy_client_logging(self): app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {}) app.access_logger = FakeLogger() req = Request.blank( "/", environ={"REQUEST_METHOD": "GET", "REMOTE_ADDR": "1.2.3.4", "HTTP_X_FORWARDED_FOR": "4.5.6.7,8.9.10.11"}, ) resp = app(req.environ, start_response) # exhaust generator [x for x in resp] log_parts = self._log_parts(app) self.assertEquals(log_parts[0], "4.5.6.7") # client ip self.assertEquals(log_parts[1], "1.2.3.4") # remote addr app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {}) app.access_logger = FakeLogger() req = Request.blank( "/", environ={"REQUEST_METHOD": "GET", "REMOTE_ADDR": "1.2.3.4", "HTTP_X_CLUSTER_CLIENT_IP": "4.5.6.7"} ) resp = app(req.environ, start_response) # exhaust generator [x for x in resp] log_parts = self._log_parts(app) self.assertEquals(log_parts[0], "4.5.6.7") # client ip self.assertEquals(log_parts[1], "1.2.3.4") # remote addr
def test_PUT_encryption_override(self): # set crypto override to disable encryption. # simulate another middleware wanting to set footers other_footers = { 'Etag': 'other etag', 'X-Object-Sysmeta-Other': 'other sysmeta', 'X-Object-Sysmeta-Container-Update-Override-Etag': 'other override'} body = 'FAKE APP' env = {'REQUEST_METHOD': 'PUT', 'swift.crypto.override': True, 'swift.callback.update_footers': lambda footers: footers.update(other_footers)} hdrs = {'content-type': 'text/plain', 'content-length': str(len(body))} req = Request.blank('/v1/a/c/o', environ=env, body=body, headers=hdrs) self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {}) resp = req.get_response(self.encrypter) self.assertEqual('201 Created', resp.status) # verify that other middleware's footers made it to app req_hdrs = self.app.headers[0] for k, v in other_footers.items(): self.assertEqual(v, req_hdrs[k]) # verify object is NOT encrypted by getting direct from the app get_req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'GET'}) self.assertEqual(body, get_req.get_response(self.app).body)
def test_to_swift_req_subrequest_proxy_access_log(self): container = 'bucket' obj = 'obj' method = 'GET' # force_swift_request_proxy_log is True req = Request.blank('/%s/%s' % (container, obj), environ={'REQUEST_METHOD': method, 'swift.proxy_access_log_made': True}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header()}) with patch.object(Request, 'get_response') as m_swift_resp, \ patch.object(Request, 'remote_user', 'authorized'): m_swift_resp.return_value = FakeSwiftResponse() s3_req = S3AclRequest( req.environ, MagicMock(), force_request_log=True) sw_req = s3_req.to_swift_req(method, container, obj) self.assertFalse(sw_req.environ['swift.proxy_access_log_made']) # force_swift_request_proxy_log is False req = Request.blank('/%s/%s' % (container, obj), environ={'REQUEST_METHOD': method, 'swift.proxy_access_log_made': True}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header()}) with patch.object(Request, 'get_response') as m_swift_resp, \ patch.object(Request, 'remote_user', 'authorized'): m_swift_resp.return_value = FakeSwiftResponse() s3_req = S3AclRequest( req.environ, MagicMock(), force_request_log=False) sw_req = s3_req.to_swift_req(method, container, obj) self.assertTrue(sw_req.environ['swift.proxy_access_log_made'])
def test_handle_multipart_put_bad_data(self): bad_data = json.dumps([{"path": "/cont/object", "etag": "etagoftheobj", "size_bytes": "lala"}]) req = Request.blank( "/test_good/AUTH_test/c/man?multipart-manifest=put", environ={"REQUEST_METHOD": "PUT"}, body=bad_data ) self.assertRaises(HTTPException, self.slo.handle_multipart_put, req) for bad_data in [ json.dumps([{"path": "/cont", "etag": "etagoftheobj", "size_bytes": 100}]), json.dumps("asdf"), json.dumps(None), json.dumps(5), "not json", "1234", None, "", json.dumps({"path": None}), json.dumps([{"path": "/c/o", "etag": None, "size_bytes": 12}]), json.dumps([{"path": "/c/o", "etag": "asdf", "size_bytes": "sd"}]), json.dumps([{"path": 12, "etag": "etagoftheobj", "size_bytes": 100}]), json.dumps([{"path": u"/cont/object\u2661", "etag": "etagoftheobj", "size_bytes": 100}]), json.dumps([{"path": 12, "size_bytes": 100}]), json.dumps([{"path": 12, "size_bytes": 100}]), json.dumps([{"path": None, "etag": "etagoftheobj", "size_bytes": 100}]), ]: req = Request.blank( "/test_good/AUTH_test/c/man?multipart-manifest=put", environ={"REQUEST_METHOD": "PUT"}, body=bad_data ) self.assertRaises(HTTPException, self.slo.handle_multipart_put, req)
def test_check_object_creation_copy(self): headers = {'Content-Length': '0', 'X-Copy-From': 'c/o2', 'Content-Type': 'text/plain'} self.assertEquals(constraints.check_object_creation(Request.blank( '/', headers=headers), 'object_name'), None) headers = {'Content-Length': '1', 'X-Copy-From': 'c/o2', 'Content-Type': 'text/plain'} self.assertEquals(constraints.check_object_creation(Request.blank( '/', headers=headers), 'object_name').status_int, HTTP_BAD_REQUEST) headers = {'Transfer-Encoding': 'chunked', 'X-Copy-From': 'c/o2', 'Content-Type': 'text/plain'} self.assertEquals(constraints.check_object_creation(Request.blank( '/', headers=headers), 'object_name'), None) # a content-length header is always required headers = {'X-Copy-From': 'c/o2', 'Content-Type': 'text/plain'} self.assertEquals(constraints.check_object_creation(Request.blank( '/', headers=headers), 'object_name').status_int, HTTP_LENGTH_REQUIRED)
def test_get_account_info_cache(self): # The original test that we prefer to preserve cached = {'status': 404, 'bytes': 3333, 'total_object_count': 10} req = Request.blank("/v1/account/cont", environ={'swift.cache': FakeCache(cached)}) with patch('swift.proxy.controllers.base.' '_prepare_pre_auth_info_request', FakeRequest): resp = get_account_info(req.environ, 'xxx') self.assertEquals(resp['bytes'], 3333) self.assertEquals(resp['total_object_count'], 10) self.assertEquals(resp['status'], 404) # Here is a more realistic test cached = {'status': 404, 'bytes': '3333', 'container_count': '234', 'total_object_count': '10', 'meta': {}} req = Request.blank("/v1/account/cont", environ={'swift.cache': FakeCache(cached)}) with patch('swift.proxy.controllers.base.' '_prepare_pre_auth_info_request', FakeRequest): resp = get_account_info(req.environ, 'xxx') self.assertEquals(resp['status'], 404) self.assertEquals(resp['bytes'], '3333') self.assertEquals(resp['container_count'], 234) self.assertEquals(resp['meta'], {}) self.assertEquals(resp['total_object_count'], '10')
def test_GETorHEAD_base(self): base = Controller(self.app) req = Request.blank('/v1/a/c/o/with/slashes') with patch('swift.proxy.controllers.base.' 'http_connect', fake_http_connect(200)): resp = base.GETorHEAD_base(req, 'object', FakeRing(), 'part', '/a/c/o/with/slashes') self.assertTrue('swift.object/a/c/o/with/slashes' in resp.environ) self.assertEqual( resp.environ['swift.object/a/c/o/with/slashes']['status'], 200) req = Request.blank('/v1/a/c/o') with patch('swift.proxy.controllers.base.' 'http_connect', fake_http_connect(200)): resp = base.GETorHEAD_base(req, 'object', FakeRing(), 'part', '/a/c/o') self.assertTrue('swift.object/a/c/o' in resp.environ) self.assertEqual(resp.environ['swift.object/a/c/o']['status'], 200) req = Request.blank('/v1/a/c') with patch('swift.proxy.controllers.base.' 'http_connect', fake_http_connect(200)): resp = base.GETorHEAD_base(req, 'container', FakeRing(), 'part', '/a/c') self.assertTrue('swift.container/a/c' in resp.environ) self.assertEqual(resp.environ['swift.container/a/c']['status'], 200) req = Request.blank('/v1/a') with patch('swift.proxy.controllers.base.' 'http_connect', fake_http_connect(200)): resp = base.GETorHEAD_base(req, 'account', FakeRing(), 'part', '/a') self.assertTrue('swift.account/a' in resp.environ) self.assertEqual(resp.environ['swift.account/a']['status'], 200)
def test_parse_account_that_looks_like_version(self): """ Demonstrate the failure mode for accounts that looks like versions, if you can make _parse_path better and this is the *only* test that fails you can delete it ;) """ bad_accounts = ( 'v3.0', 'verybaddaccountwithnoprefix', ) for bad_account in bad_accounts: req = Request.blank('/endpoints/%s/c/o' % bad_account) self.assertRaises(ValueError, self.list_endpoints._parse_path, req) even_worse_accounts = { 'v1': 1.0, 'v2.0': 2.0, } for bad_account, guessed_version in even_worse_accounts.items(): req = Request.blank('/endpoints/%s/c/o' % bad_account) version, account, container, obj = \ self.list_endpoints._parse_path(req) self.assertEqual(version, guessed_version) self.assertEqual(account, 'c') self.assertEqual(container, 'o') self.assertEqual(obj, None)
def test_get_account_info_cache(self): # The original test that we prefer to preserve cached = {'status': 404, 'bytes': 3333, 'total_object_count': 10} req = Request.blank("/v1/account/cont", environ={'swift.cache': FakeCache(cached)}) resp = get_account_info(req.environ, FakeApp()) self.assertEqual(resp['bytes'], 3333) self.assertEqual(resp['total_object_count'], 10) self.assertEqual(resp['status'], 404) # Here is a more realistic test cached = {'status': 404, 'bytes': '3333', 'container_count': '234', 'total_object_count': '10', 'meta': {}} req = Request.blank("/v1/account/cont", environ={'swift.cache': FakeCache(cached)}) resp = get_account_info(req.environ, FakeApp()) self.assertEqual(resp['status'], 404) self.assertEqual(resp['bytes'], '3333') self.assertEqual(resp['container_count'], 234) self.assertEqual(resp['meta'], {}) self.assertEqual(resp['total_object_count'], '10')
def test_policy_index(self): # Policy index can be specified by X-Backend-Storage-Policy-Index # in the request header for object API app = proxy_logging.ProxyLoggingMiddleware(FakeApp(policy_idx="1"), {}) app.access_logger = FakeLogger() req = Request.blank("/v1/a/c/o", environ={"REQUEST_METHOD": "PUT"}) resp = app(req.environ, start_response) "".join(resp) log_parts = self._log_parts(app) self.assertEqual(log_parts[20], "1") # Policy index can be specified by X-Backend-Storage-Policy-Index # in the response header for container API app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {}) app.access_logger = FakeLogger() req = Request.blank("/v1/a/c", environ={"REQUEST_METHOD": "GET"}) def fake_call(app, env, start_response): start_response( app.response_str, [ ("Content-Type", "text/plain"), ("Content-Length", str(sum(map(len, app.body)))), ("X-Backend-Storage-Policy-Index", "1"), ], ) while env["wsgi.input"].read(5): pass return app.body with mock.patch.object(FakeApp, "__call__", fake_call): resp = app(req.environ, start_response) "".join(resp) log_parts = self._log_parts(app) self.assertEqual(log_parts[20], "1")
def test_bucket_GET_max_keys(self): class FakeApp(object): def __call__(self, env, start_response): self.query_string = env['QUERY_STRING'] start_response('200 OK', []) return '[]' fake_app = FakeApp() local_app = swift3.filter_factory({})(fake_app) bucket_name = 'junk' req = Request.blank('/%s' % bucket_name, environ={'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'max-keys=5'}, headers={'Authorization': 'AWS test:tester:hmac'}) resp = local_app(req.environ, lambda *args: None) dom = xml.dom.minidom.parseString("".join(resp)) self.assertEquals(dom.getElementsByTagName('MaxKeys')[0]. childNodes[0].nodeValue, '5') args = dict(cgi.parse_qsl(fake_app.query_string)) self.assert_(args['limit'] == '6') req = Request.blank('/%s' % bucket_name, environ={'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'max-keys=5000'}, headers={'Authorization': 'AWS test:tester:hmac'}) resp = local_app(req.environ, lambda *args: None) dom = xml.dom.minidom.parseString("".join(resp)) self.assertEquals(dom.getElementsByTagName('MaxKeys')[0]. childNodes[0].nodeValue, '1000') args = dict(cgi.parse_qsl(fake_app.query_string)) self.assertEquals(args['limit'], '1001')
def create_container(self, req, container_path): """ Checks if the container exists and if not try to create it. :params container_path: an unquoted path to a container to be created :returns: True if created container, False if container exists :raises: CreateContainerError when unable to create container """ new_env = req.environ.copy() new_env['PATH_INFO'] = container_path new_env['swift.source'] = 'EA' new_env['REQUEST_METHOD'] = 'HEAD' head_cont_req = Request.blank(container_path, environ=new_env) resp = head_cont_req.get_response(self.app) if resp.is_success: return False if resp.status_int == 404: new_env = req.environ.copy() new_env['PATH_INFO'] = container_path new_env['swift.source'] = 'EA' new_env['REQUEST_METHOD'] = 'PUT' create_cont_req = Request.blank(container_path, environ=new_env) resp = create_cont_req.get_response(self.app) if resp.is_success: return True raise CreateContainerError( "Create Container Failed: " + container_path, resp.status_int, resp.status)
def test_upload_size(self): # Using default policy app = proxy_logging.ProxyLoggingMiddleware(FakeApp(), {"log_headers": "yes"}) app.access_logger = FakeLogger() req = Request.blank("/v1/a/c/o/foo", environ={"REQUEST_METHOD": "PUT", "wsgi.input": BytesIO(b"some stuff")}) resp = app(req.environ, start_response) # exhaust generator [x for x in resp] log_parts = self._log_parts(app) self.assertEqual(log_parts[11], str(len("FAKE APP"))) self.assertEqual(log_parts[10], str(len("some stuff"))) self.assertUpdateStats( [ ("object.PUT.200.xfer", len("some stuff") + len("FAKE APP")), ("object.policy.0.PUT.200.xfer", len("some stuff") + len("FAKE APP")), ], app, ) # Using a non-existent policy app = proxy_logging.ProxyLoggingMiddleware(FakeApp(policy_idx="-1"), {"log_headers": "yes"}) app.access_logger = FakeLogger() req = Request.blank("/v1/a/c/o/foo", environ={"REQUEST_METHOD": "PUT", "wsgi.input": BytesIO(b"some stuff")}) resp = app(req.environ, start_response) # exhaust generator [x for x in resp] log_parts = self._log_parts(app) self.assertEqual(log_parts[11], str(len("FAKE APP"))) self.assertEqual(log_parts[10], str(len("some stuff"))) self.assertUpdateStats([("object.PUT.200.xfer", len("some stuff") + len("FAKE APP"))], app)
def test_PUT_encryption_override(self): # set crypto override to disable encryption. # simulate another middleware wanting to set footers other_footers = { "Etag": "other etag", "X-Object-Sysmeta-Other": "other sysmeta", "X-Object-Sysmeta-Container-Update-Override-Etag": "other override", } body = "FAKE APP" env = { "REQUEST_METHOD": "PUT", "swift.crypto.override": True, "swift.callback.update_footers": lambda footers: footers.update(other_footers), } hdrs = {"content-type": "text/plain", "content-length": str(len(body))} req = Request.blank("/v1/a/c/o", environ=env, body=body, headers=hdrs) self.app.register("PUT", "/v1/a/c/o", HTTPCreated, {}) resp = req.get_response(self.encrypter) self.assertEqual("201 Created", resp.status) # verify that other middleware's footers made it to app req_hdrs = self.app.headers[0] for k, v in other_footers.items(): self.assertEqual(v, req_hdrs[k]) # verify object is NOT encrypted by getting direct from the app get_req = Request.blank("/v1/a/c/o", environ={"REQUEST_METHOD": "GET"}) self.assertEqual(body, get_req.get_response(self.app).body)
def test_bucket_PUT(self): req = Request.blank('/bucket', environ={'REQUEST_METHOD': 'PUT'}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header()}) status, headers, body = self.call_s3api(req) self.assertEqual(body, '') self.assertEqual(status.split()[0], '200') self.assertEqual(headers['Location'], '/bucket') # Apparently some clients will include a chunked transfer-encoding # even with no body req = Request.blank('/bucket', environ={'REQUEST_METHOD': 'PUT'}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header(), 'Transfer-Encoding': 'chunked'}) status, headers, body = self.call_s3api(req) self.assertEqual(body, '') self.assertEqual(status.split()[0], '200') self.assertEqual(headers['Location'], '/bucket') with UnreadableInput(self) as fake_input: req = Request.blank( '/bucket', environ={'REQUEST_METHOD': 'PUT', 'wsgi.input': fake_input}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header()}) status, headers, body = self.call_s3api(req) self.assertEqual(body, '') self.assertEqual(status.split()[0], '200') self.assertEqual(headers['Location'], '/bucket')
def test_bucket_GET_v2_with_delimiter_max_keys(self): bucket_name = 'junk' req = Request.blank( '/%s?list-type=2&delimiter=a&max-keys=2' % bucket_name, environ={'REQUEST_METHOD': 'GET'}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header()}) status, headers, body = self.call_s3api(req) self.assertEqual(status.split()[0], '200') elem = fromstring(body, 'ListBucketResult') next_token = elem.find('./NextContinuationToken') self.assertIsNotNone(next_token) self.assertEqual(elem.find('./MaxKeys').text, '2') self.assertEqual(elem.find('./IsTruncated').text, 'true') req = Request.blank( '/%s?list-type=2&delimiter=a&max-keys=2&continuation-token=%s' % (bucket_name, next_token.text), environ={'REQUEST_METHOD': 'GET'}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header()}) status, headers, body = self.call_s3api(req) self.assertEqual(status.split()[0], '200') elem = fromstring(body, 'ListBucketResult') names = [o.find('./Key').text for o in elem.iterchildren('Contents')] self.assertEqual(names[0], 'lily')
def test_bucket_GET_v2_fetch_owner(self): bucket_name = 'junk' req = Request.blank('/%s?list-type=2' % bucket_name, environ={'REQUEST_METHOD': 'GET'}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header()}) status, headers, body = self.call_s3api(req) self.assertEqual(status.split()[0], '200') elem = fromstring(body, 'ListBucketResult') name = elem.find('./Name').text self.assertEqual(name, bucket_name) objects = elem.iterchildren('Contents') for o in objects: self.assertIsNone(o.find('./Owner')) req = Request.blank('/%s?list-type=2&fetch-owner=true' % bucket_name, environ={'REQUEST_METHOD': 'GET'}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header()}) status, headers, body = self.call_s3api(req) self.assertEqual(status.split()[0], '200') elem = fromstring(body, 'ListBucketResult') name = elem.find('./Name').text self.assertEqual(name, bucket_name) objects = elem.iterchildren('Contents') for o in objects: self.assertIsNotNone(o.find('./Owner'))
def test_bucket_GET_v2_is_truncated(self): bucket_name = 'junk' req = Request.blank( '/%s?list-type=2&max-keys=%d' % (bucket_name, len(self.objects)), environ={'REQUEST_METHOD': 'GET'}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header()}) status, headers, body = self.call_s3api(req) elem = fromstring(body, 'ListBucketResult') self.assertEqual(elem.find('./KeyCount').text, str(len(self.objects))) self.assertEqual(elem.find('./IsTruncated').text, 'false') req = Request.blank( '/%s?list-type=2&max-keys=%d' % (bucket_name, len(self.objects) - 1), environ={'REQUEST_METHOD': 'GET'}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header()}) status, headers, body = self.call_s3api(req) elem = fromstring(body, 'ListBucketResult') self.assertIsNotNone(elem.find('./NextContinuationToken')) self.assertEqual(elem.find('./KeyCount').text, str(len(self.objects) - 1)) self.assertEqual(elem.find('./IsTruncated').text, 'true') req = Request.blank('/subdirs?list-type=2&delimiter=/&max-keys=2', environ={'REQUEST_METHOD': 'GET'}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header()}) status, headers, body = self.call_s3api(req) elem = fromstring(body, 'ListBucketResult') self.assertIsNotNone(elem.find('./NextContinuationToken')) self.assertEqual(elem.find('./KeyCount').text, '2') self.assertEqual(elem.find('./IsTruncated').text, 'true')
def test_bucket_GET_max_keys(self): bucket_name = 'junk' req = Request.blank('/%s?max-keys=5' % bucket_name, environ={'REQUEST_METHOD': 'GET'}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header()}) status, headers, body = self.call_s3api(req) elem = fromstring(body, 'ListBucketResult') self.assertEqual(elem.find('./MaxKeys').text, '5') _, path = self.swift.calls[-1] _, query_string = path.split('?') args = dict(cgi.parse_qsl(query_string)) self.assertEqual(args['limit'], '6') req = Request.blank('/%s?max-keys=5000' % bucket_name, environ={'REQUEST_METHOD': 'GET'}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header()}) status, headers, body = self.call_s3api(req) elem = fromstring(body, 'ListBucketResult') self.assertEqual(elem.find('./MaxKeys').text, '5000') _, path = self.swift.calls[-1] _, query_string = path.split('?') args = dict(cgi.parse_qsl(query_string)) self.assertEqual(args['limit'], '1001')
def test_sysmeta_replaced_by_PUT(self): path = '/v1/a/c/o' env = {'REQUEST_METHOD': 'PUT'} hdrs = dict(self.original_sysmeta_headers_1) hdrs.update(self.original_sysmeta_headers_2) hdrs.update(self.original_meta_headers_1) hdrs.update(self.original_meta_headers_2) req = Request.blank(path, environ=env, headers=hdrs, body='x') resp = req.get_response(self.app) self._assertStatus(resp, 201) env = {'REQUEST_METHOD': 'PUT'} hdrs = dict(self.changed_sysmeta_headers) hdrs.update(self.new_sysmeta_headers) hdrs.update(self.changed_meta_headers) hdrs.update(self.new_meta_headers) hdrs.update(self.bad_headers) req = Request.blank(path, environ=env, headers=hdrs, body='x') resp = req.get_response(self.app) self._assertStatus(resp, 201) req = Request.blank(path, environ={}) resp = req.get_response(self.app) self._assertStatus(resp, 200) self._assertInHeaders(resp, self.changed_sysmeta_headers) self._assertInHeaders(resp, self.new_sysmeta_headers) self._assertNotInHeaders(resp, self.original_sysmeta_headers_2) self._assertInHeaders(resp, self.changed_meta_headers) self._assertInHeaders(resp, self.new_meta_headers) self._assertNotInHeaders(resp, self.original_meta_headers_2)
def test_put_and_get_range(self): # put object headers = { 'x-timestamp': Timestamp(time.time()).internal, 'content-type': 'application/octet-stream', } body = 'test body' req = Request.blank(self._get_path(), method='PUT', headers=headers) req.body = body resp = req.get_response(self.app) self.assertEqual(resp.status_int, 201) # get object with range req = Request.blank(self._get_path()) req.range = 'bytes=0-4' resp = req.get_response(self.app) self.assertEqual(resp.status_int, 206) self.assertEqual(resp.body, 'test ') # get object with offset req = Request.blank(self._get_path()) req.range = 'bytes=4-' resp = req.get_response(self.app) self.assertEqual(resp.status_int, 206) self.assertEqual(resp.body, ' body')
def test_headers_to_sign_sigv4(self): environ = { 'REQUEST_METHOD': 'GET'} # host and x-amz-date x_amz_date = self.get_v4_amz_date_header() headers = { 'Authorization': 'AWS4-HMAC-SHA256 ' 'Credential=test/20130524/US/s3/aws4_request, ' 'SignedHeaders=host;x-amz-content-sha256;x-amz-date,' 'Signature=X', 'X-Amz-Content-SHA256': '0123456789', 'Date': self.get_date_header(), 'X-Amz-Date': x_amz_date} req = Request.blank('/', environ=environ, headers=headers) sigv4_req = SigV4Request(req.environ) headers_to_sign = sigv4_req._headers_to_sign() self.assertEqual(['host', 'x-amz-content-sha256', 'x-amz-date'], sorted(headers_to_sign.keys())) self.assertEqual(headers_to_sign['host'], 'localhost:80') self.assertEqual(headers_to_sign['x-amz-date'], x_amz_date) self.assertEqual(headers_to_sign['x-amz-content-sha256'], '0123456789') # no x-amz-date headers = { 'Authorization': 'AWS4-HMAC-SHA256 ' 'Credential=test/20130524/US/s3/aws4_request, ' 'SignedHeaders=host;x-amz-content-sha256,' 'Signature=X', 'X-Amz-Content-SHA256': '0123456789', 'Date': self.get_date_header()} req = Request.blank('/', environ=environ, headers=headers) sigv4_req = SigV4Request(req.environ) headers_to_sign = sigv4_req._headers_to_sign() self.assertEqual(['host', 'x-amz-content-sha256'], sorted(headers_to_sign.keys())) self.assertEqual(headers_to_sign['host'], 'localhost:80') self.assertEqual(headers_to_sign['x-amz-content-sha256'], '0123456789') # SignedHeaders says, host and x-amz-date included but there is not # X-Amz-Date header headers = { 'Authorization': 'AWS4-HMAC-SHA256 ' 'Credential=test/20130524/US/s3/aws4_request, ' 'SignedHeaders=host;x-amz-content-sha256;x-amz-date,' 'Signature=X', 'X-Amz-Content-SHA256': '0123456789', 'Date': self.get_date_header()} req = Request.blank('/', environ=environ, headers=headers) with self.assertRaises(SignatureDoesNotMatch): sigv4_req = SigV4Request(req.environ) sigv4_req._headers_to_sign()
def test_bulk_delete_no_body(self): req = Request.blank('/unauth/AUTH_acc/') resp_body = self.handle_delete_and_iter(req) self.assertTrue('411 Length Required' in resp_body)
def test_GET_not_found(self): req = Request.blank('/v1/a/c/o') self.storage.object_fetch = Mock(side_effect=exc.NoSuchObject) resp = req.get_response(self.app) self.assertEqual(resp.status_int, 404)
def test_PUT_if_none_match_not_star(self): req = Request.blank('/v1/a/c/o', method='PUT') req.headers['if-none-match'] = 'foo' req.headers['content-length'] = '0' resp = req.get_response(self.app) self.assertEqual(resp.status_int, 400)
def test_PUT_requires_length(self): req = Request.blank('/v1/a/c/o', method='PUT') resp = req.get_response(self.app) self.assertEqual(resp.status_int, 411)
def test_bulk_delete_bad_file_over_twice_max_length(self): body = '/c/f\nc/' + ('123456' * self.bulk.max_path_length) + '\n' req = Request.blank('/delete_works/AUTH_Acc', body=body) req.method = 'POST' resp_body = self.handle_delete_and_iter(req) self.assertTrue('400 Bad Request' in resp_body)
def test_bulk_delete_bad_path(self): req = Request.blank('/delete_cont_fail/') resp_body = self.handle_delete_and_iter(req) self.assertTrue('404 Not Found' in resp_body)
def test_bulk_delete_no_files_in_body(self): req = Request.blank('/unauth/AUTH_acc/', body=' ') resp_body = self.handle_delete_and_iter(req) self.assertTrue('400 Bad Request' in resp_body)
def test_bad_container(self): req = Request.blank('/invalid/', body='') resp_body = self.handle_extract_and_iter(req, '') self.assertTrue('404 Not Found' in resp_body)
def test_extract_metadata(self): self.app.register('HEAD', '/v1/a/c?extract-archive=tar', HTTPNoContent, {}, None) self.app.register('PUT', '/v1/a/c/obj1?extract-archive=tar', HTTPCreated, {}, None) self.app.register('PUT', '/v1/a/c/obj2?extract-archive=tar', HTTPCreated, {}, None) # It's a real pain to instantiate TarInfo objects directly; they # really want to come from a file on disk or a tarball. So, we write # out some files and add pax headers to them as they get placed into # the tarball. with open(os.path.join(self.testdir, "obj1"), "w") as fh1: fh1.write("obj1 contents\n") with open(os.path.join(self.testdir, "obj2"), "w") as fh2: fh2.write("obj2 contents\n") tar_ball = StringIO() tar_file = tarfile.TarFile.open(fileobj=tar_ball, mode="w", format=tarfile.PAX_FORMAT) # With GNU tar 1.27.1 or later (possibly 1.27 as well), a file with # extended attribute user.thingy = dingy gets put into the tarfile # with pax_headers containing key/value pair # (SCHILY.xattr.user.thingy, dingy), both unicode strings (py2: type # unicode, not type str). # # With BSD tar (libarchive), you get key/value pair # (LIBARCHIVE.xattr.user.thingy, dingy), which strikes me as # gratuitous incompatibility. # # Still, we'll support uploads with both. Just heap more code on the # problem until you can forget it's under there. with open(os.path.join(self.testdir, "obj1")) as fh1: tar_info1 = tar_file.gettarinfo(fileobj=fh1, arcname="obj1") tar_info1.pax_headers[u'SCHILY.xattr.user.mime_type'] = \ u'application/food-diary' tar_info1.pax_headers[u'SCHILY.xattr.user.meta.lunch'] = \ u'sopa de albóndigas' tar_info1.pax_headers[ u'SCHILY.xattr.user.meta.afternoon-snack'] = \ u'gigantic bucket of coffee' tar_file.addfile(tar_info1, fh1) with open(os.path.join(self.testdir, "obj2")) as fh2: tar_info2 = tar_file.gettarinfo(fileobj=fh2, arcname="obj2") tar_info2.pax_headers[ u'LIBARCHIVE.xattr.user.meta.muppet'] = u'bert' tar_info2.pax_headers[ u'LIBARCHIVE.xattr.user.meta.cat'] = u'fluffy' tar_info2.pax_headers[ u'LIBARCHIVE.xattr.user.notmeta'] = u'skipped' tar_file.addfile(tar_info2, fh2) tar_ball.seek(0) req = Request.blank('/v1/a/c?extract-archive=tar') req.environ['REQUEST_METHOD'] = 'PUT' req.environ['wsgi.input'] = tar_ball req.headers['transfer-encoding'] = 'chunked' req.headers['accept'] = 'application/json;q=1.0' resp = req.get_response(self.bulk) self.assertEqual(resp.status_int, 200) # sanity check to make sure the upload worked upload_status = utils.json.loads(resp.body) self.assertEqual(upload_status['Number Files Created'], 2) put1_headers = HeaderKeyDict(self.app.calls_with_headers[1][2]) self.assertEqual(put1_headers.get('Content-Type'), 'application/food-diary') self.assertEqual(put1_headers.get('X-Object-Meta-Lunch'), 'sopa de alb\xc3\xb3ndigas') self.assertEqual(put1_headers.get('X-Object-Meta-Afternoon-Snack'), 'gigantic bucket of coffee') put2_headers = HeaderKeyDict(self.app.calls_with_headers[2][2]) self.assertEqual(put2_headers.get('X-Object-Meta-Muppet'), 'bert') self.assertEqual(put2_headers.get('X-Object-Meta-Cat'), 'fluffy') self.assertEqual(put2_headers.get('Content-Type'), None) self.assertEqual(put2_headers.get('X-Object-Meta-Blah'), None)
def test_content_length_required(self): req = Request.blank('/create_cont_fail/acc/cont') resp_body = self.handle_extract_and_iter(req, '') self.assertTrue('411 Length Required' in resp_body)
def test_cache_middleware(self): req = Request.blank('/something', environ={'REQUEST_METHOD': 'GET'}) resp = self.app(req.environ, start_response) self.assertTrue('swift.cache' in resp) self.assertTrue(isinstance(resp['swift.cache'], MemcacheRing))
def test_extract_tar_works(self): # On systems where $TMPDIR is long (like OS X), we need to do this # or else every upload will fail due to the path being too long. self.app.max_pathlen += len(self.testdir) for compress_format in ['', 'gz', 'bz2']: base_name = 'base_works_%s' % compress_format dir_tree = [{ base_name: [ { 'sub_dir1': ['sub1_file1', 'sub1_file2'] }, { 'sub_dir2': ['sub2_file1', u'test obj \u2661'] }, 'sub_file1', { 'sub_dir3': [{ 'sub4_dir1': '../sub4 file1' }] }, { 'sub_dir4': None }, ] }] build_dir_tree(self.testdir, dir_tree) mode = 'w' extension = '' if compress_format: mode += ':' + compress_format extension += '.' + compress_format tar = tarfile.open(name=os.path.join(self.testdir, 'tar_works.tar' + extension), mode=mode) tar.add(os.path.join(self.testdir, base_name)) tar.close() req = Request.blank('/tar_works/acc/cont/') req.environ['wsgi.input'] = open( os.path.join(self.testdir, 'tar_works.tar' + extension)) req.headers['transfer-encoding'] = 'chunked' resp_body = self.handle_extract_and_iter(req, compress_format) resp_data = utils.json.loads(resp_body) self.assertEquals(resp_data['Number Files Created'], 6) # test out xml req = Request.blank('/tar_works/acc/cont/') req.environ['wsgi.input'] = open( os.path.join(self.testdir, 'tar_works.tar' + extension)) req.headers['transfer-encoding'] = 'chunked' resp_body = self.handle_extract_and_iter(req, compress_format, 'application/xml') self.assert_( '<response_status>201 Created</response_status>' in resp_body) self.assert_( '<number_files_created>6</number_files_created>' in resp_body) # test out nonexistent format req = Request.blank('/tar_works/acc/cont/?extract-archive=tar', headers={'Accept': 'good_xml'}) req.environ['REQUEST_METHOD'] = 'PUT' req.environ['wsgi.input'] = open( os.path.join(self.testdir, 'tar_works.tar' + extension)) req.headers['transfer-encoding'] = 'chunked' def fake_start_response(*args, **kwargs): pass app_iter = self.bulk(req.environ, fake_start_response) resp_body = ''.join([i for i in app_iter]) self.assert_('Response Status: 406' in resp_body)
def test_PUT_req(self): body_key = os.urandom(32) object_key = fetch_crypto_keys()['object'] plaintext = b'FAKE APP' plaintext_etag = md5hex(plaintext) ciphertext = encrypt(plaintext, body_key, FAKE_IV) ciphertext_etag = md5hex(ciphertext) env = {'REQUEST_METHOD': 'PUT', CRYPTO_KEY_CALLBACK: fetch_crypto_keys} hdrs = { 'etag': plaintext_etag, 'content-type': 'text/plain', 'content-length': str(len(plaintext)), 'x-object-meta-etag': 'not to be confused with the Etag!', 'x-object-meta-test': 'encrypt me', 'x-object-sysmeta-test': 'do not encrypt me' } req = Request.blank('/v1/a/c/o', environ=env, body=plaintext, headers=hdrs) self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {}) with mock.patch( 'swift.common.middleware.crypto.crypto_utils.' 'Crypto.create_random_key', return_value=body_key): resp = req.get_response(self.encrypter) self.assertEqual('201 Created', resp.status) self.assertEqual(plaintext_etag, resp.headers['Etag']) # verify metadata items self.assertEqual(1, len(self.app.calls), self.app.calls) self.assertEqual('PUT', self.app.calls[0][0]) req_hdrs = self.app.headers[0] # verify body crypto meta actual = req_hdrs['X-Object-Sysmeta-Crypto-Body-Meta'] actual = json.loads(urlparse.unquote_plus(actual)) self.assertEqual(Crypto().cipher, actual['cipher']) self.assertEqual(FAKE_IV, base64.b64decode(actual['iv'])) # verify wrapped body key expected_wrapped_key = encrypt(body_key, object_key, FAKE_IV) self.assertEqual(expected_wrapped_key, base64.b64decode(actual['body_key']['key'])) self.assertEqual(FAKE_IV, base64.b64decode(actual['body_key']['iv'])) self.assertEqual(fetch_crypto_keys()['id'], actual['key_id']) # verify etag self.assertEqual(ciphertext_etag, req_hdrs['Etag']) encrypted_etag, _junk, etag_meta = \ req_hdrs['X-Object-Sysmeta-Crypto-Etag'].partition('; swift_meta=') # verify crypto_meta was appended to this etag self.assertTrue(etag_meta) actual_meta = json.loads(urlparse.unquote_plus(etag_meta)) self.assertEqual(Crypto().cipher, actual_meta['cipher']) # verify encrypted version of plaintext etag actual = base64.b64decode(encrypted_etag) etag_iv = base64.b64decode(actual_meta['iv']) enc_etag = encrypt(plaintext_etag.encode('ascii'), object_key, etag_iv) self.assertEqual(enc_etag, actual) # verify etag MAC for conditional requests actual_hmac = base64.b64decode( req_hdrs['X-Object-Sysmeta-Crypto-Etag-Mac']) exp_hmac = hmac.new(object_key, plaintext_etag.encode('ascii'), hashlib.sha256).digest() self.assertEqual(actual_hmac, exp_hmac) # verify encrypted etag for container update self.assertIn('X-Object-Sysmeta-Container-Update-Override-Etag', req_hdrs) parts = req_hdrs[ 'X-Object-Sysmeta-Container-Update-Override-Etag'].rsplit(';', 1) self.assertEqual(2, len(parts)) # extract crypto_meta from end of etag for container update param = parts[1].strip() crypto_meta_tag = 'swift_meta=' self.assertTrue(param.startswith(crypto_meta_tag), param) actual_meta = json.loads( urlparse.unquote_plus(param[len(crypto_meta_tag):])) self.assertEqual(Crypto().cipher, actual_meta['cipher']) self.assertEqual(fetch_crypto_keys()['id'], actual_meta['key_id']) cont_key = fetch_crypto_keys()['container'] cont_etag_iv = base64.b64decode(actual_meta['iv']) self.assertEqual(FAKE_IV, cont_etag_iv) exp_etag = encrypt(plaintext_etag.encode('ascii'), cont_key, cont_etag_iv) self.assertEqual(exp_etag, base64.b64decode(parts[0])) # content-type is not encrypted self.assertEqual('text/plain', req_hdrs['Content-Type']) # user meta is encrypted self._verify_user_metadata(req_hdrs, 'Test', 'encrypt me', object_key) self._verify_user_metadata(req_hdrs, 'Etag', 'not to be confused with the Etag!', object_key) # sysmeta is not encrypted self.assertEqual('do not encrypt me', req_hdrs['X-Object-Sysmeta-Test']) # verify object is encrypted by getting direct from the app get_req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'GET'}) resp = get_req.get_response(self.app) self.assertEqual(ciphertext, resp.body) self.assertEqual(ciphertext_etag, resp.headers['Etag'])
def _make_request(self, path, **kwargs): req = Request.blank(path, **kwargs) req.environ['swift.cache'] = FakeMemcache() return req
def _test_PUT_with_other_footers(self, override_etag): # verify handling of another middleware's footer callback body_key = os.urandom(32) object_key = fetch_crypto_keys()['object'] plaintext = b'FAKE APP' plaintext_etag = md5hex(plaintext) ciphertext = encrypt(plaintext, body_key, FAKE_IV) ciphertext_etag = md5hex(ciphertext) other_footers = { 'Etag': plaintext_etag, 'X-Object-Sysmeta-Other': 'other sysmeta', 'X-Object-Sysmeta-Container-Update-Override-Size': 'other override', 'X-Object-Sysmeta-Container-Update-Override-Etag': override_etag } env = { 'REQUEST_METHOD': 'PUT', CRYPTO_KEY_CALLBACK: fetch_crypto_keys, 'swift.callback.update_footers': lambda footers: footers.update(other_footers) } hdrs = { 'content-type': 'text/plain', 'content-length': str(len(plaintext)), 'Etag': 'correct etag is in footers' } req = Request.blank('/v1/a/c/o', environ=env, body=plaintext, headers=hdrs) self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {}) with mock.patch( 'swift.common.middleware.crypto.crypto_utils.' 'Crypto.create_random_key', lambda *args: body_key): resp = req.get_response(self.encrypter) self.assertEqual('201 Created', resp.status) self.assertEqual(plaintext_etag, resp.headers['Etag']) # verify metadata items self.assertEqual(1, len(self.app.calls), self.app.calls) self.assertEqual('PUT', self.app.calls[0][0]) req_hdrs = self.app.headers[0] # verify that other middleware's footers made it to app, including any # container update overrides but nothing Etag-related other_footers.pop('Etag') other_footers.pop('X-Object-Sysmeta-Container-Update-Override-Etag') for k, v in other_footers.items(): self.assertEqual(v, req_hdrs[k]) # verify encryption footers are ok encrypted_etag, _junk, etag_meta = \ req_hdrs['X-Object-Sysmeta-Crypto-Etag'].partition('; swift_meta=') self.assertTrue(etag_meta) actual_meta = json.loads(urlparse.unquote_plus(etag_meta)) self.assertEqual(Crypto().cipher, actual_meta['cipher']) self.assertEqual(ciphertext_etag, req_hdrs['Etag']) actual = base64.b64decode(encrypted_etag) etag_iv = base64.b64decode(actual_meta['iv']) exp_etag = encrypt(plaintext_etag.encode('ascii'), object_key, etag_iv) self.assertEqual(exp_etag, actual) # verify encrypted etag for container update self.assertIn('X-Object-Sysmeta-Container-Update-Override-Etag', req_hdrs) parts = req_hdrs[ 'X-Object-Sysmeta-Container-Update-Override-Etag'].rsplit(';', 1) self.assertEqual(2, len(parts)) # extract crypto_meta from end of etag for container update param = parts[1].strip() crypto_meta_tag = 'swift_meta=' self.assertTrue(param.startswith(crypto_meta_tag), param) actual_meta = json.loads( urlparse.unquote_plus(param[len(crypto_meta_tag):])) self.assertEqual(Crypto().cipher, actual_meta['cipher']) cont_key = fetch_crypto_keys()['container'] cont_etag_iv = base64.b64decode(actual_meta['iv']) self.assertEqual(FAKE_IV, cont_etag_iv) exp_etag = encrypt(override_etag.encode('ascii'), cont_key, cont_etag_iv) self.assertEqual(exp_etag, base64.b64decode(parts[0])) # verify body crypto meta actual = req_hdrs['X-Object-Sysmeta-Crypto-Body-Meta'] actual = json.loads(urlparse.unquote_plus(actual)) self.assertEqual(Crypto().cipher, actual['cipher']) self.assertEqual(FAKE_IV, base64.b64decode(actual['iv'])) # verify wrapped body key expected_wrapped_key = encrypt(body_key, object_key, FAKE_IV) self.assertEqual(expected_wrapped_key, base64.b64decode(actual['body_key']['key'])) self.assertEqual(FAKE_IV, base64.b64decode(actual['body_key']['iv'])) self.assertEqual(fetch_crypto_keys()['id'], actual['key_id'])
def make_request(self, method, path, headers, acceptable_statuses, body_file=None, params=None): """Makes a request to Swift with retries. :param method: HTTP method of request. :param path: Path of request. :param headers: Headers to be sent with request. :param acceptable_statuses: List of acceptable statuses for request. :param body_file: Body file to be passed along with request, defaults to None. :param params: A dict of params to be set in request query string, defaults to None. :returns: Response object on success. :raises UnexpectedResponse: Exception raised when make_request() fails to get a response with an acceptable status :raises Exception: Exception is raised when code fails in an unexpected way. """ headers = dict(headers) headers['user-agent'] = self.user_agent headers.setdefault('x-backend-allow-reserved-names', 'true') for attempt in range(self.request_tries): resp = exc_type = exc_value = exc_traceback = None req = Request.blank(path, environ={'REQUEST_METHOD': method}, headers=headers) if body_file is not None: if hasattr(body_file, 'seek'): body_file.seek(0) req.body_file = body_file if params: req.params = params try: resp = req.get_response(self.app) except (Exception, Timeout): exc_type, exc_value, exc_traceback = exc_info() else: if resp.status_int in acceptable_statuses or \ resp.status_int // 100 in acceptable_statuses: return resp elif not is_server_error(resp.status_int): # No sense retrying when we expect the same result break # sleep only between tries, not after each one if attempt < self.request_tries - 1: if resp: # always close any resp.app_iter before we discard it with closing_if_possible(resp.app_iter): # for non 2XX requests it's safe and useful to drain # the response body so we log the correct status code if resp.status_int // 100 != 2: for iter_body in resp.app_iter: pass sleep(2**(attempt + 1)) if resp: msg = 'Unexpected response: %s' % resp.status if resp.status_int // 100 != 2 and resp.body: # provide additional context (and drain the response body) for # non 2XX responses msg += ' (%s)' % resp.body raise UnexpectedResponse(msg, resp) if exc_type: # To make pep8 tool happy, in place of raise t, v, tb: six.reraise(exc_type, exc_value, exc_traceback)
def do_test(host): req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}, headers={'Host': host}) return app(req.environ, start_response)
def test_PUT_nothing_read(self): # simulate an artificial scenario of a downstream filter/app not # actually reading the input stream from encrypter. class NonReadingApp(object): def __call__(self, env, start_response): # note: no read from wsgi.input req = Request(env) env['swift.callback.update_footers'](req.headers) call_headers.append(req.headers) resp = HTTPCreated(req=req, headers={'Etag': 'response etag'}) return resp(env, start_response) env = {'REQUEST_METHOD': 'PUT', CRYPTO_KEY_CALLBACK: fetch_crypto_keys} hdrs = { 'content-type': 'text/plain', 'content-length': 0, 'etag': 'etag from client' } req = Request.blank('/v1/a/c/o', environ=env, body='', headers=hdrs) call_headers = [] resp = req.get_response(encrypter.Encrypter(NonReadingApp(), {})) self.assertEqual('201 Created', resp.status) self.assertEqual('response etag', resp.headers['Etag']) self.assertEqual(1, len(call_headers)) self.assertEqual('etag from client', call_headers[0]['etag']) # verify no encryption footers for k in call_headers[0]: self.assertFalse(k.lower().startswith('x-object-sysmeta-crypto-')) # check that an upstream footer callback gets called other_footers = { 'Etag': EMPTY_ETAG, 'X-Object-Sysmeta-Other': 'other sysmeta', 'X-Object-Sysmeta-Container-Update-Override-Etag': 'other override' } env.update({ 'swift.callback.update_footers': lambda footers: footers.update(other_footers) }) req = Request.blank('/v1/a/c/o', environ=env, body='', headers=hdrs) call_headers = [] resp = req.get_response(encrypter.Encrypter(NonReadingApp(), {})) self.assertEqual('201 Created', resp.status) self.assertEqual('response etag', resp.headers['Etag']) self.assertEqual(1, len(call_headers)) # verify encrypted override etag for container update. self.assertIn('X-Object-Sysmeta-Container-Update-Override-Etag', call_headers[0]) parts = call_headers[0][ 'X-Object-Sysmeta-Container-Update-Override-Etag'].rsplit(';', 1) self.assertEqual(2, len(parts)) cont_key = fetch_crypto_keys()['container'] param = parts[1].strip() crypto_meta_tag = 'swift_meta=' self.assertTrue(param.startswith(crypto_meta_tag), param) actual_meta = json.loads( urlparse.unquote_plus(param[len(crypto_meta_tag):])) self.assertEqual(Crypto().cipher, actual_meta['cipher']) self.assertEqual(fetch_crypto_keys()['id'], actual_meta['key_id']) cont_etag_iv = base64.b64decode(actual_meta['iv']) self.assertEqual(FAKE_IV, cont_etag_iv) self.assertEqual(encrypt(b'other override', cont_key, cont_etag_iv), base64.b64decode(parts[0])) # verify that other middleware's footers made it to app other_footers.pop('X-Object-Sysmeta-Container-Update-Override-Etag') for k, v in other_footers.items(): self.assertEqual(v, call_headers[0][k]) # verify no encryption footers for k in call_headers[0]: self.assertFalse(k.lower().startswith('x-object-sysmeta-crypto-')) # if upstream footer override etag is for an empty body then check that # it is not encrypted other_footers = { 'Etag': EMPTY_ETAG, 'X-Object-Sysmeta-Container-Update-Override-Etag': EMPTY_ETAG } env.update({ 'swift.callback.update_footers': lambda footers: footers.update(other_footers) }) req = Request.blank('/v1/a/c/o', environ=env, body='', headers=hdrs) call_headers = [] resp = req.get_response(encrypter.Encrypter(NonReadingApp(), {})) self.assertEqual('201 Created', resp.status) self.assertEqual('response etag', resp.headers['Etag']) self.assertEqual(1, len(call_headers)) # verify that other middleware's footers made it to app for k, v in other_footers.items(): self.assertEqual(v, call_headers[0][k]) # verify no encryption footers for k in call_headers[0]: self.assertFalse(k.lower().startswith('x-object-sysmeta-crypto-')) # if upstream footer override etag is an empty string then check that # it is not encrypted other_footers = { 'Etag': EMPTY_ETAG, 'X-Object-Sysmeta-Container-Update-Override-Etag': '' } env.update({ 'swift.callback.update_footers': lambda footers: footers.update(other_footers) }) req = Request.blank('/v1/a/c/o', environ=env, body='', headers=hdrs) call_headers = [] resp = req.get_response(encrypter.Encrypter(NonReadingApp(), {})) self.assertEqual('201 Created', resp.status) self.assertEqual('response etag', resp.headers['Etag']) self.assertEqual(1, len(call_headers)) # verify that other middleware's footers made it to app for k, v in other_footers.items(): self.assertEqual(v, call_headers[0][k]) # verify no encryption footers for k in call_headers[0]: self.assertFalse(k.lower().startswith('x-object-sysmeta-crypto-'))
def test_cname_matching_ending_not_domain(self): req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}, headers={'Host': 'foo.com'}) resp = self.app(req.environ, start_response) self.assertEqual(resp, ['CNAME lookup failed to resolve to a valid domain'])
def test_PUT_app_exception(self): app = encrypter.Encrypter(FakeAppThatExcepts(HTTPException), {}) req = Request.blank('/', environ={'REQUEST_METHOD': 'PUT'}) with self.assertRaises(HTTPException) as catcher: req.get_response(app) self.assertEqual(FakeAppThatExcepts.MESSAGE, catcher.exception.body)
def test_something_weird(self): req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}, headers={'Host': 'mysite.com'}) resp = self.app(req.environ, start_response) self.assertEqual(resp, ['CNAME lookup failed to resolve to a valid domain'])
def do_test(lookup_back): req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}, headers={'Host': 'c.a.example.com'}) module = 'swift.common.middleware.cname_lookup.lookup_cname' with mock.patch(module, lambda d, r: (0, lookup_back)): return app(req.environ, start_response)
def _make_request(self, **kwargs): req = Request.blank("/v1/AUTH_account/cont", **kwargs) return req
def test_caching(self): fail_to_resolve = ['CNAME lookup failed to resolve to a valid domain'] class memcache_stub(object): def __init__(self): self.cache = {} def get(self, key): return self.cache.get(key, None) def set(self, key, value, *a, **kw): self.cache[key] = value module = 'swift.common.middleware.cname_lookup.lookup_cname' dns_module = 'dns.resolver.Resolver.query' memcache = memcache_stub() with mock.patch(module) as m: m.return_value = (3600, 'c.example.com') req = Request.blank('/', environ={'REQUEST_METHOD': 'GET', 'swift.cache': memcache}, headers={'Host': 'mysite2.com'}) resp = self.app(req.environ, start_response) self.assertEqual(resp, ['FAKE APP']) self.assertEqual(m.call_count, 1) self.assertEqual(memcache.cache.get('cname-mysite2.com'), 'c.example.com') req = Request.blank('/', environ={'REQUEST_METHOD': 'GET', 'swift.cache': memcache}, headers={'Host': 'mysite2.com'}) resp = self.app(req.environ, start_response) self.assertEqual(resp, ['FAKE APP']) self.assertEqual(m.call_count, 1) self.assertEqual(memcache.cache.get('cname-mysite2.com'), 'c.example.com') for exc, num in ((dns.resolver.NXDOMAIN(), 3), (dns.resolver.NoAnswer(), 4)): with mock.patch(dns_module) as m: m.side_effect = exc req = Request.blank('/', environ={'REQUEST_METHOD': 'GET', 'swift.cache': memcache}, headers={'Host': 'mysite%d.com' % num}) resp = self.app(req.environ, start_response) self.assertEqual(resp, fail_to_resolve) self.assertEqual(m.call_count, 1) self.assertEqual(memcache.cache.get('cname-mysite3.com'), False) req = Request.blank('/', environ={'REQUEST_METHOD': 'GET', 'swift.cache': memcache}, headers={'Host': 'mysite%d.com' % num}) resp = self.app(req.environ, start_response) self.assertEqual(resp, fail_to_resolve) self.assertEqual(m.call_count, 1) self.assertEqual( memcache.cache.get('cname-mysite%d.com' % num), False) with mock.patch(dns_module) as m: m.side_effect = dns.exception.DNSException() req = Request.blank('/', environ={'REQUEST_METHOD': 'GET', 'swift.cache': memcache}, headers={'Host': 'mysite5.com'}) resp = self.app(req.environ, start_response) self.assertEqual(resp, fail_to_resolve) self.assertEqual(m.call_count, 1) self.assertFalse('cname-mysite5.com' in memcache.cache) req = Request.blank('/', environ={'REQUEST_METHOD': 'GET', 'swift.cache': memcache}, headers={'Host': 'mysite5.com'}) resp = self.app(req.environ, start_response) self.assertEqual(resp, fail_to_resolve) self.assertEqual(m.call_count, 2) self.assertFalse('cname-mysite5.com' in memcache.cache)
def test_domain_remap_extra_subdomains(self): req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}, headers={'Host': 'x.y.c.AUTH_a.example.com'}) resp = self.app(req.environ, start_response) self.assertEqual(resp, ['Bad domain in host header'])
def test_object_HEAD_error(self): # HEAD does not return the body even an error response in the # specifications of the REST API. # So, check the response code for error test of HEAD. req = Request.blank('/bucket/object', environ={'REQUEST_METHOD': 'HEAD'}, headers={ 'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header() }) self.swift.register('HEAD', '/v1/AUTH_test/bucket/object', swob.HTTPUnauthorized, {}, None) status, headers, body = self.call_swift3(req) self.assertEqual(status.split()[0], '403') self.assertEqual(body, '') # sanity req = Request.blank('/bucket/object', environ={'REQUEST_METHOD': 'HEAD'}, headers={ 'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header() }) self.swift.register('HEAD', '/v1/AUTH_test/bucket/object', swob.HTTPForbidden, {}, None) status, headers, body = self.call_swift3(req) self.assertEqual(status.split()[0], '403') self.assertEqual(body, '') # sanity req = Request.blank('/bucket/object', environ={'REQUEST_METHOD': 'HEAD'}, headers={ 'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header() }) self.swift.register('HEAD', '/v1/AUTH_test/bucket/object', swob.HTTPNotFound, {}, None) status, headers, body = self.call_swift3(req) self.assertEqual(status.split()[0], '404') self.assertEqual(body, '') # sanity req = Request.blank('/bucket/object', environ={'REQUEST_METHOD': 'HEAD'}, headers={ 'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header() }) self.swift.register('HEAD', '/v1/AUTH_test/bucket/object', swob.HTTPPreconditionFailed, {}, None) status, headers, body = self.call_swift3(req) self.assertEqual(status.split()[0], '412') self.assertEqual(body, '') # sanity req = Request.blank('/bucket/object', environ={'REQUEST_METHOD': 'HEAD'}, headers={ 'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header() }) self.swift.register('HEAD', '/v1/AUTH_test/bucket/object', swob.HTTPServerError, {}, None) status, headers, body = self.call_swift3(req) self.assertEqual(status.split()[0], '500') self.assertEqual(body, '') # sanity req = Request.blank('/bucket/object', environ={'REQUEST_METHOD': 'HEAD'}, headers={ 'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header() }) self.swift.register('HEAD', '/v1/AUTH_test/bucket/object', swob.HTTPServiceUnavailable, {}, None) status, headers, body = self.call_swift3(req) self.assertEqual(status.split()[0], '500') self.assertEqual(body, '') # sanity
def test_check_metadata_empty_name(self): headers = {'X-Object-Meta-': 'Value'} resp = constraints.check_metadata(Request.blank('/', headers=headers), 'object') self.assertEqual(resp.status_int, HTTP_BAD_REQUEST) self.assertIn('Metadata name cannot be empty', resp.body)
def test_domain_remap_account_container_with_path_root(self): req = Request.blank('/v1', environ={'REQUEST_METHOD': 'GET'}, headers={'Host': 'c.AUTH_a.example.com'}) resp = self.app(req.environ, start_response) self.assertEqual(resp, '/v1/AUTH_a/c')
def test_check_metadata_good(self): headers = {'X-Object-Meta-Name': 'Value'} self.assertIsNone( constraints.check_metadata(Request.blank('/', headers=headers), 'object'))
def test_domain_remap_account_matching_ending_not_domain(self): req = Request.blank('/dontchange', environ={'REQUEST_METHOD': 'GET'}, headers={'Host': 'c.aexample.com'}) resp = self.app(req.environ, start_response) self.assertEqual(resp, '/dontchange')