Merge "Support x-amz-copy-source-if-* for PUT Object Copy"

This commit is contained in:
Jenkins
2015-01-08 03:06:39 +00:00
committed by Gerrit Code Review
4 changed files with 171 additions and 8 deletions

View File

@@ -93,13 +93,14 @@ class BaseAclHandler(object):
""" """
BaseAclHandler: Handling ACL for basic requests mapped on ACL_MAP BaseAclHandler: Handling ACL for basic requests mapped on ACL_MAP
""" """
def __init__(self, req, container, obj): def __init__(self, req, container, obj, headers):
self.req = req self.req = req
self.container = self.req.container_name if container is None \ self.container = self.req.container_name if container is None \
else container else container
self.obj = self.req.object_name if obj is None else obj self.obj = self.req.object_name if obj is None else obj
self.method = req.environ['REQUEST_METHOD'] self.method = req.environ['REQUEST_METHOD']
self.user_id = self.req.user_id self.user_id = self.req.user_id
self.headers = self.req.headers if headers is None else headers
def _check_copy_source(self, app): def _check_copy_source(self, app):
if 'X-Amz-Copy-Source' in self.req.headers: if 'X-Amz-Copy-Source' in self.req.headers:
@@ -117,7 +118,7 @@ class BaseAclHandler(object):
return self._handle_acl(app, method) return self._handle_acl(app, method)
def _handle_acl(self, app, sw_method, container=None, obj=None, def _handle_acl(self, app, sw_method, container=None, obj=None,
permission=None): permission=None, headers=None):
""" """
General acl handling method. General acl handling method.
This method expects to call Request._get_response() in outside of This method expects to call Request._get_response() in outside of
@@ -129,6 +130,7 @@ class BaseAclHandler(object):
obj = self.obj if obj is None else obj obj = self.obj if obj is None else obj
sw_method = sw_method or self.req.environ['REQUEST_METHOD'] sw_method = sw_method or self.req.environ['REQUEST_METHOD']
resource = 'object' if obj else 'container' resource = 'object' if obj else 'container'
headers = self.headers if headers is None else headers
if not container: if not container:
return return
@@ -143,7 +145,8 @@ class BaseAclHandler(object):
if resource == 'object': if resource == 'object':
resp = self.req.get_acl_response(app, 'HEAD', resp = self.req.get_acl_response(app, 'HEAD',
container, obj) container, obj,
headers)
acl = resp.object_acl acl = resp.object_acl
elif resource == 'container': elif resource == 'container':
resp = self.req.get_acl_response(app, 'HEAD', resp = self.req.get_acl_response(app, 'HEAD',
@@ -182,8 +185,6 @@ class ObjectAclHandler(BaseAclHandler):
ObjectAclHandler: Handler for ObjectController ObjectAclHandler: Handler for ObjectController
""" """
def PUT(self, app): def PUT(self, app):
self._check_copy_source(app)
b_resp = self._handle_acl(app, 'HEAD', obj='') b_resp = self._handle_acl(app, 'HEAD', obj='')
req_acl = ACL.from_headers(self.req.headers, req_acl = ACL.from_headers(self.req.headers,
b_resp.bucket_acl.owner, b_resp.bucket_acl.owner,
@@ -269,8 +270,9 @@ class MultiUploadAclHandler(BaseAclHandler):
------------------------------------------------- -------------------------------------------------
""" """
def __init__(self, req, container, obj): def __init__(self, req, container, obj, headers):
super(MultiUploadAclHandler, self).__init__(req, container, obj) super(MultiUploadAclHandler, self).__init__(req, container, obj,
headers)
self.container = self.container[:-len(MULTIUPLOAD_SUFFIX)] self.container = self.container[:-len(MULTIUPLOAD_SUFFIX)]
def handle_acl(self, app, method): def handle_acl(self, app, method):
@@ -357,6 +359,9 @@ ACL_MAP = {
# GET Object # GET Object
('GET', 'GET', 'object'): ('GET', 'GET', 'object'):
{'Permission': 'READ'}, {'Permission': 'READ'},
# PUT Object Copy
('PUT', 'HEAD', 'object'):
{'Permission': 'READ'},
# Initiate Multipart Upload # Initiate Multipart Upload
('POST', 'PUT', 'container'): ('POST', 'PUT', 'container'):
{'Permission': 'WRITE'}, {'Permission': 'WRITE'},

View File

@@ -54,6 +54,7 @@ class ObjectController(Controller):
""" """
Handle PUT Object and PUT Object (Copy) request Handle PUT Object and PUT Object (Copy) request
""" """
req.check_copy_source(self.app)
resp = req.get_response(self.app) resp = req.get_response(self.app)
if 'X-Amz-Copy-Source' in req.headers: if 'X-Amz-Copy-Source' in req.headers:

View File

@@ -292,6 +292,29 @@ class Request(swob.Request):
if self.environ['HTTP_CONTENT_MD5'] != digest: if self.environ['HTTP_CONTENT_MD5'] != digest:
raise InvalidDigest(content_md5=self.environ['HTTP_CONTENT_MD5']) raise InvalidDigest(content_md5=self.environ['HTTP_CONTENT_MD5'])
def _copy_source_headers(self):
env = {}
for key, value in self.environ.items():
if key.startswith('HTTP_X_AMZ_COPY_SOURCE_'):
env[key.replace('X_AMZ_COPY_SOURCE_', '')] = value
return swob.HeaderEnvironProxy(env)
def check_copy_source(self, app):
if 'X-Amz-Copy-Source' in self.headers:
src_path = self.headers['X-Amz-Copy-Source']
src_path = src_path if src_path.startswith('/') else \
('/' + src_path)
src_bucket, src_obj = split_path(src_path, 0, 2, True)
headers = swob.HeaderKeyDict()
headers.update(self._copy_source_headers())
src_resp = self.get_response(app, 'HEAD', src_bucket, src_obj,
headers=headers)
if src_resp.status_int == 304: # pylint: disable-msg=E1101
raise PreconditionFailed()
def _canonical_uri(self): def _canonical_uri(self):
raw_path_info = self.environ.get('RAW_PATH_INFO', self.path) raw_path_info = self.environ.get('RAW_PATH_INFO', self.path)
if self.bucket_in_host: if self.bucket_in_host:
@@ -704,7 +727,7 @@ class S3AclRequest(Request):
Wrap up get_response call to hook with acl handling method. Wrap up get_response call to hook with acl handling method.
""" """
acl_handler = get_acl_handler(self.controller_name)( acl_handler = get_acl_handler(self.controller_name)(
self, container, obj) self, container, obj, headers)
resp = acl_handler.handle_acl(app, method) resp = acl_handler.handle_acl(app, method)
# possible to skip recalling get_resposne_acl if resp is not # possible to skip recalling get_resposne_acl if resp is not

View File

@@ -253,6 +253,8 @@ class TestSwift3Obj(Swift3TestCase):
etag = '7dfa07a8e59ddbcd1dc84d4c4f82aea1' etag = '7dfa07a8e59ddbcd1dc84d4c4f82aea1'
content_md5 = etag.decode('hex').encode('base64').strip() content_md5 = etag.decode('hex').encode('base64').strip()
self.swift.register('HEAD', '/v1/AUTH_test/some/source',
swob.HTTPOk, {}, None)
self.swift.register('PUT', '/v1/AUTH_test/bucket/object', self.swift.register('PUT', '/v1/AUTH_test/bucket/object',
swob.HTTPCreated, swob.HTTPCreated,
{'etag': etag}, {'etag': etag},
@@ -278,6 +280,138 @@ class TestSwift3Obj(Swift3TestCase):
self.assertEquals(headers['X-Copy-From'], '/some/source') self.assertEquals(headers['X-Copy-From'], '/some/source')
self.assertEquals(headers['Content-Length'], '0') self.assertEquals(headers['Content-Length'], '0')
def _test_object_PUT_copy_headers(self, head_resp, put_header):
account = 'test:tester'
grants = [Grant(User(account), 'FULL_CONTROL')]
head_headers = \
encode_acl('object',
ACL(Owner(account, account), grants))
self.swift.register('HEAD', '/v1/AUTH_test/some/source',
head_resp, head_headers, None)
self.swift.register('PUT', '/v1/AUTH_test/bucket/object',
swob.HTTPCreated, {}, None)
put_headers = {'Authorization': 'AWS test:tester:hmac',
'X-Amz-Copy-Source': '/some/source'}
put_headers.update(put_header)
req = Request.blank('/bucket/object',
environ={'REQUEST_METHOD': 'PUT'},
headers=put_headers)
req.date = datetime.now()
req.content_type = 'text/plain'
return self.call_swift3(req)
@s3acl
def test_object_PUT_copy_headers_error(self):
etag = '7dfa07a8e59ddbcd1dc84d4c4f82aea1'
last_modified_since = 'Fri, 01 Apr 2014 12:00:00 GMT'
header = {'X-Amz-Copy-Source-If-Match': etag}
status, header, body = \
self._test_object_PUT_copy_headers(swob.HTTPPreconditionFailed,
header)
self.assertEquals(self._get_error_code(body), 'PreconditionFailed')
header = {'X-Amz-Copy-Source-If-None-Match': etag}
status, header, body = \
self._test_object_PUT_copy_headers(swob.HTTPNotModified,
header)
self.assertEquals(self._get_error_code(body), 'PreconditionFailed')
header = {'X-Amz-Copy-Source-If-Modified-Since': last_modified_since}
status, header, body = \
self._test_object_PUT_copy_headers(swob.HTTPNotModified,
header)
self.assertEquals(self._get_error_code(body), 'PreconditionFailed')
header = \
{'X-Amz-Copy-Source-If-Unmodified-Since': last_modified_since}
status, header, body = \
self._test_object_PUT_copy_headers(swob.HTTPPreconditionFailed,
header)
self.assertEquals(self._get_error_code(body), 'PreconditionFailed')
def test_object_PUT_copy_headers_with_match(self):
etag = '7dfa07a8e59ddbcd1dc84d4c4f82aea1'
last_modified_since = 'Fri, 01 Apr 2014 12:00:00 GMT'
header = {'X-Amz-Copy-Source-If-Match': etag,
'X-Amz-Copy-Source-If-Modified-Since': last_modified_since}
status, header, body = \
self._test_object_PUT_copy_headers(swob.HTTPOk, header)
self.assertEquals(status.split()[0], '200')
self.assertEquals(len(self.swift.calls_with_headers), 2)
_, _, headers = self.swift.calls_with_headers[-1]
self.assertTrue(headers.get('If-Match') is None)
self.assertTrue(headers.get('If-Modified-Since') is None)
_, _, headers = self.swift.calls_with_headers[0]
self.assertEquals(headers['If-Match'], etag)
self.assertEquals(headers['If-Modified-Since'], last_modified_since)
@s3acl(s3acl_only=True)
def test_object_PUT_copy_headers_with_match_and_s3acl(self):
etag = '7dfa07a8e59ddbcd1dc84d4c4f82aea1'
last_modified_since = 'Fri, 01 Apr 2014 12:00:00 GMT'
header = {'X-Amz-Copy-Source-If-Match': etag,
'X-Amz-Copy-Source-If-Modified-Since': last_modified_since}
status, header, body = \
self._test_object_PUT_copy_headers(swob.HTTPOk, header)
self.assertEquals(status.split()[0], '200')
self.assertEquals(len(self.swift.calls_with_headers), 3)
# After the check of the copy source in the case of s3acl is valid,
# Swift3 check the bucket write permissions of the destination.
_, _, headers = self.swift.calls_with_headers[-2]
self.assertTrue(headers.get('If-Match') is None)
self.assertTrue(headers.get('If-Modified-Since') is None)
_, _, headers = self.swift.calls_with_headers[-1]
self.assertTrue(headers.get('If-Match') is None)
self.assertTrue(headers.get('If-Modified-Since') is None)
_, _, headers = self.swift.calls_with_headers[0]
self.assertEquals(headers['If-Match'], etag)
self.assertEquals(headers['If-Modified-Since'], last_modified_since)
def test_object_PUT_copy_headers_with_not_match(self):
etag = '7dfa07a8e59ddbcd1dc84d4c4f82aea1'
last_modified_since = 'Fri, 01 Apr 2014 12:00:00 GMT'
header = {'X-Amz-Copy-Source-If-None-Match': etag,
'X-Amz-Copy-Source-If-Unmodified-Since': last_modified_since}
status, header, body = \
self._test_object_PUT_copy_headers(swob.HTTPOk, header)
self.assertEquals(status.split()[0], '200')
self.assertEquals(len(self.swift.calls_with_headers), 2)
_, _, headers = self.swift.calls_with_headers[-1]
self.assertTrue(headers.get('If-None-Match') is None)
self.assertTrue(headers.get('If-Unmodified-Since') is None)
_, _, headers = self.swift.calls_with_headers[0]
self.assertEquals(headers['If-None-Match'], etag)
self.assertEquals(headers['If-Unmodified-Since'], last_modified_since)
@s3acl(s3acl_only=True)
def test_object_PUT_copy_headers_with_not_match_and_s3acl(self):
etag = '7dfa07a8e59ddbcd1dc84d4c4f82aea1'
last_modified_since = 'Fri, 01 Apr 2014 12:00:00 GMT'
header = {'X-Amz-Copy-Source-If-None-Match': etag,
'X-Amz-Copy-Source-If-Unmodified-Since': last_modified_since}
status, header, body = \
self._test_object_PUT_copy_headers(swob.HTTPOk, header)
self.assertEquals(status.split()[0], '200')
# After the check of the copy source in the case of s3acl is valid,
# Swift3 check the bucket write permissions of the destination.
self.assertEquals(len(self.swift.calls_with_headers), 3)
_, _, headers = self.swift.calls_with_headers[-1]
self.assertTrue(headers.get('If-None-Match') is None)
self.assertTrue(headers.get('If-Unmodified-Since') is None)
_, _, headers = self.swift.calls_with_headers[0]
self.assertEquals(headers['If-None-Match'], etag)
self.assertEquals(headers['If-Unmodified-Since'], last_modified_since)
@s3acl @s3acl
def test_object_POST_error(self): def test_object_POST_error(self):
code = self._test_method_error('POST', '/bucket/object', None) code = self._test_method_error('POST', '/bucket/object', None)