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

Support following headers for PUT Object Copy.
- x-amz-copy-source-if-match
- x-amz-copy-source-if-none-match
- x-amz-copy-source-if-modified-since
- x-amz-copy-source-if-unmodified-since

And I'll support those headers for Upload Part Copy in next patch.

Change-Id: I0dbb37310a8d252b292eef41f7b777f9dbae69d6
This commit is contained in:
Naoto Nishizono
2014-12-24 20:03:11 +09:00
parent 14fc9f04d7
commit ac1130be82
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
"""
def __init__(self, req, container, obj):
def __init__(self, req, container, obj, headers):
self.req = req
self.container = self.req.container_name if container is None \
else container
self.obj = self.req.object_name if obj is None else obj
self.method = req.environ['REQUEST_METHOD']
self.user_id = self.req.user_id
self.headers = self.req.headers if headers is None else headers
def _check_copy_source(self, app):
if 'X-Amz-Copy-Source' in self.req.headers:
@@ -117,7 +118,7 @@ class BaseAclHandler(object):
return self._handle_acl(app, method)
def _handle_acl(self, app, sw_method, container=None, obj=None,
permission=None):
permission=None, headers=None):
"""
General acl handling method.
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
sw_method = sw_method or self.req.environ['REQUEST_METHOD']
resource = 'object' if obj else 'container'
headers = self.headers if headers is None else headers
if not container:
return
@@ -143,7 +145,8 @@ class BaseAclHandler(object):
if resource == 'object':
resp = self.req.get_acl_response(app, 'HEAD',
container, obj)
container, obj,
headers)
acl = resp.object_acl
elif resource == 'container':
resp = self.req.get_acl_response(app, 'HEAD',
@@ -182,8 +185,6 @@ class ObjectAclHandler(BaseAclHandler):
ObjectAclHandler: Handler for ObjectController
"""
def PUT(self, app):
self._check_copy_source(app)
b_resp = self._handle_acl(app, 'HEAD', obj='')
req_acl = ACL.from_headers(self.req.headers,
b_resp.bucket_acl.owner,
@@ -269,8 +270,9 @@ class MultiUploadAclHandler(BaseAclHandler):
-------------------------------------------------
"""
def __init__(self, req, container, obj):
super(MultiUploadAclHandler, self).__init__(req, container, obj)
def __init__(self, req, container, obj, headers):
super(MultiUploadAclHandler, self).__init__(req, container, obj,
headers)
self.container = self.container[:-len(MULTIUPLOAD_SUFFIX)]
def handle_acl(self, app, method):
@@ -357,6 +359,9 @@ ACL_MAP = {
# GET Object
('GET', 'GET', 'object'):
{'Permission': 'READ'},
# PUT Object Copy
('PUT', 'HEAD', 'object'):
{'Permission': 'READ'},
# Initiate Multipart Upload
('POST', 'PUT', 'container'):
{'Permission': 'WRITE'},

View File

@@ -54,6 +54,7 @@ class ObjectController(Controller):
"""
Handle PUT Object and PUT Object (Copy) request
"""
req.check_copy_source(self.app)
resp = req.get_response(self.app)
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:
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):
raw_path_info = self.environ.get('RAW_PATH_INFO', self.path)
if self.bucket_in_host:
@@ -704,7 +727,7 @@ class S3AclRequest(Request):
Wrap up get_response call to hook with acl handling method.
"""
acl_handler = get_acl_handler(self.controller_name)(
self, container, obj)
self, container, obj, headers)
resp = acl_handler.handle_acl(app, method)
# possible to skip recalling get_resposne_acl if resp is not

View File

@@ -253,6 +253,8 @@ class TestSwift3Obj(Swift3TestCase):
etag = '7dfa07a8e59ddbcd1dc84d4c4f82aea1'
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',
swob.HTTPCreated,
{'etag': etag},
@@ -278,6 +280,138 @@ class TestSwift3Obj(Swift3TestCase):
self.assertEquals(headers['X-Copy-From'], '/some/source')
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
def test_object_DELETE_error(self):
code = self._test_method_error('DELETE', '/bucket/object',