WIP - acls and test cases
This patch is a functional work in progress. It * introduces get_acls based on real acl values in swift * implements a 'private' set acl * adds err_responses for MissingContentLength, BadDigest * adds a POST handler for BucketController (that only returns 501, for now) the get_acl function in particular isn't pretty, so this is a work in progress. Committing as it is passing 13 more testcases than previous attempts.
This commit is contained in:
parent
0fce82b6bf
commit
14fdcc209b
1
AUTHORS
1
AUTHORS
@ -9,4 +9,5 @@ Josh Kearney <josh@jk0.org>
|
|||||||
Michael Barton <mike@weirdlooking.com>
|
Michael Barton <mike@weirdlooking.com>
|
||||||
Rainer Toebbicke <Rainer.Toebbicke@cern.ch>
|
Rainer Toebbicke <Rainer.Toebbicke@cern.ch>
|
||||||
Scott Simpson <sasimpson@gmail.com>
|
Scott Simpson <sasimpson@gmail.com>
|
||||||
|
Tom Fifield <fifieldt@unimelb.edu.au>
|
||||||
Victor Rodionov <vito.ordaz@gmail.com>
|
Victor Rodionov <vito.ordaz@gmail.com>
|
||||||
|
@ -66,7 +66,7 @@ from swift.common.wsgi import WSGIContext
|
|||||||
from swift.common.http import HTTP_OK, HTTP_CREATED, HTTP_ACCEPTED, \
|
from swift.common.http import HTTP_OK, HTTP_CREATED, HTTP_ACCEPTED, \
|
||||||
HTTP_NO_CONTENT, HTTP_BAD_REQUEST, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, \
|
HTTP_NO_CONTENT, HTTP_BAD_REQUEST, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, \
|
||||||
HTTP_NOT_FOUND, HTTP_CONFLICT, HTTP_UNPROCESSABLE_ENTITY, is_success, \
|
HTTP_NOT_FOUND, HTTP_CONFLICT, HTTP_UNPROCESSABLE_ENTITY, is_success, \
|
||||||
HTTP_NOT_IMPLEMENTED
|
HTTP_NOT_IMPLEMENTED, HTTP_LENGTH_REQUIRED
|
||||||
|
|
||||||
|
|
||||||
MAX_BUCKET_LISTING = 1000
|
MAX_BUCKET_LISTING = 1000
|
||||||
@ -94,6 +94,8 @@ def get_err_response(code):
|
|||||||
(HTTP_BAD_REQUEST, 'Could not parse the specified URI'),
|
(HTTP_BAD_REQUEST, 'Could not parse the specified URI'),
|
||||||
'InvalidDigest':
|
'InvalidDigest':
|
||||||
(HTTP_BAD_REQUEST, 'The Content-MD5 you specified was invalid'),
|
(HTTP_BAD_REQUEST, 'The Content-MD5 you specified was invalid'),
|
||||||
|
'BadDigest':
|
||||||
|
(HTTP_BAD_REQUEST, 'The Content-Length you specified was invalid'),
|
||||||
'NoSuchBucket':
|
'NoSuchBucket':
|
||||||
(HTTP_NOT_FOUND, 'The specified bucket does not exist'),
|
(HTTP_NOT_FOUND, 'The specified bucket does not exist'),
|
||||||
'SignatureDoesNotMatch':
|
'SignatureDoesNotMatch':
|
||||||
@ -106,7 +108,9 @@ def get_err_response(code):
|
|||||||
(HTTP_NOT_FOUND, 'The resource you requested does not exist'),
|
(HTTP_NOT_FOUND, 'The resource you requested does not exist'),
|
||||||
'Unsupported':
|
'Unsupported':
|
||||||
(HTTP_NOT_IMPLEMENTED, 'The feature you requested is not yet'\
|
(HTTP_NOT_IMPLEMENTED, 'The feature you requested is not yet'\
|
||||||
' implemented')}
|
' implemented'),
|
||||||
|
'MissingContentLength':
|
||||||
|
(HTTP_LENGTH_REQUIRED, 'Length Required')}
|
||||||
|
|
||||||
resp = Response(content_type='text/xml')
|
resp = Response(content_type='text/xml')
|
||||||
resp.status = error_table[code][0]
|
resp.status = error_table[code][0]
|
||||||
@ -117,22 +121,119 @@ def get_err_response(code):
|
|||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
def get_acl(account_name):
|
def get_acl(account_name, headers):
|
||||||
|
"""
|
||||||
|
Attempts to construct an S3 ACL based on what is found in the swift headers
|
||||||
|
"""
|
||||||
|
|
||||||
|
acl = 'private' # default to private
|
||||||
|
|
||||||
|
if 'x-container-read' in headers:
|
||||||
|
if headers['x-container-read'] == ".r:*" or\
|
||||||
|
".r:*," in headers['x-container-read'] or \
|
||||||
|
",*," in headers['x-container-read']:
|
||||||
|
acl = 'public-read'
|
||||||
|
if 'x-container-write' in headers:
|
||||||
|
if headers['x-container-write'] == ".r:*" or\
|
||||||
|
".r:*," in headers['x-container-write'] or \
|
||||||
|
",*," in headers['x-container-write']:
|
||||||
|
if acl == 'public-read':
|
||||||
|
acl = 'public-read-write'
|
||||||
|
else:
|
||||||
|
acl = 'public-write'
|
||||||
|
|
||||||
|
if acl =='private':
|
||||||
body = ('<AccessControlPolicy>'
|
body = ('<AccessControlPolicy>'
|
||||||
'<Owner>'
|
'<Owner>'
|
||||||
'<ID>%s</ID>'
|
'<ID>%s</ID>'
|
||||||
|
'<DisplayName>%s</DisplayName>'
|
||||||
'</Owner>'
|
'</Owner>'
|
||||||
'<AccessControlList>'
|
'<AccessControlList>'
|
||||||
'<Grant>'
|
'<Grant>'
|
||||||
'<Grantee xmlns:xsi="http://www.w3.org/2001/'\
|
'<Grantee xmlns:xsi="http://www.w3.org/2001/'\
|
||||||
'XMLSchema-instance" xsi:type="CanonicalUser">'
|
'XMLSchema-instance" xsi:type="CanonicalUser">'
|
||||||
'<ID>%s</ID>'
|
'<ID>%s</ID>'
|
||||||
|
'<DisplayName>%s</DisplayName>'
|
||||||
'</Grantee>'
|
'</Grantee>'
|
||||||
'<Permission>FULL_CONTROL</Permission>'
|
'<Permission>FULL_CONTROL</Permission>'
|
||||||
'</Grant>'
|
'</Grant>'
|
||||||
'</AccessControlList>'
|
'</AccessControlList>'
|
||||||
'</AccessControlPolicy>' %
|
'</AccessControlPolicy>' %
|
||||||
(account_name, account_name))
|
(account_name, account_name, account_name, account_name))
|
||||||
|
elif acl == 'public-read':
|
||||||
|
body = ('<AccessControlPolicy>'
|
||||||
|
'<Owner>'
|
||||||
|
'<ID>%s</ID>'
|
||||||
|
'<DisplayName>%s</DisplayName>'
|
||||||
|
'</Owner>'
|
||||||
|
'<AccessControlList>'
|
||||||
|
'<Grant>'
|
||||||
|
'<Grantee xmlns:xsi="http://www.w3.org/2001/'\
|
||||||
|
'XMLSchema-instance" xsi:type="CanonicalUser">'
|
||||||
|
'<ID>%s</ID>'
|
||||||
|
'<DisplayName>%s</DisplayName>'
|
||||||
|
'</Grantee>'
|
||||||
|
'<Permission>FULL_CONTROL</Permission>'
|
||||||
|
'</Grant>'
|
||||||
|
'<Grant>'
|
||||||
|
'<Grantee xmlns:xsi="http://www.w3.org/2001/'\
|
||||||
|
'XMLSchema-instance" xsi:type="Group">'
|
||||||
|
'</Grantee>'
|
||||||
|
'<Permission>READ</Permission>'
|
||||||
|
'</Grant>'
|
||||||
|
'</AccessControlList>'
|
||||||
|
'</AccessControlPolicy>' %
|
||||||
|
(account_name, account_name, account_name, account_name))
|
||||||
|
elif acl == 'public-read-write':
|
||||||
|
body = ('<AccessControlPolicy>'
|
||||||
|
'<Owner>'
|
||||||
|
'<ID>%s</ID>'
|
||||||
|
'<DisplayName>%s</DisplayName>'
|
||||||
|
'</Owner>'
|
||||||
|
'<AccessControlList>'
|
||||||
|
'<Grant>'
|
||||||
|
'<Grantee xmlns:xsi="http://www.w3.org/2001/'\
|
||||||
|
'XMLSchema-instance" xsi:type="CanonicalUser">'
|
||||||
|
'<ID>%s</ID>'
|
||||||
|
'<DisplayName>%s</DisplayName>'
|
||||||
|
'</Grantee>'
|
||||||
|
'<Permission>FULL_CONTROL</Permission>'
|
||||||
|
'</Grant>'
|
||||||
|
'<Grant>'
|
||||||
|
'<Grantee xmlns:xsi="http://www.w3.org/2001/'\
|
||||||
|
'XMLSchema-instance" xsi:type="Group">'
|
||||||
|
'</Grantee>'
|
||||||
|
'<Permission>READ</Permission>'
|
||||||
|
'</Grant>'
|
||||||
|
'</AccessControlList>'
|
||||||
|
'<AccessControlList>'
|
||||||
|
'<Grant>'
|
||||||
|
'<Grantee xmlns:xsi="http://www.w3.org/2001/'\
|
||||||
|
'XMLSchema-instance" xsi:type="Group">'
|
||||||
|
'</Grantee>'
|
||||||
|
'<Permission>WRITE</Permission>'
|
||||||
|
'</Grant>'
|
||||||
|
'</AccessControlList>'
|
||||||
|
'</AccessControlPolicy>' %
|
||||||
|
(account_name, account_name, account_name, account_name))
|
||||||
|
else:
|
||||||
|
body = ('<AccessControlPolicy>'
|
||||||
|
'<Owner>'
|
||||||
|
'<ID>%s</ID>'
|
||||||
|
'<DisplayName>%s</DisplayName>'
|
||||||
|
'</Owner>'
|
||||||
|
'<AccessControlList>'
|
||||||
|
'<Grant>'
|
||||||
|
'<Grantee xmlns:xsi="http://www.w3.org/2001/'\
|
||||||
|
'XMLSchema-instance" xsi:type="CanonicalUser">'
|
||||||
|
'<ID>%s</ID>'
|
||||||
|
'<DisplayName>%s</DisplayName>'
|
||||||
|
'</Grantee>'
|
||||||
|
'<Permission>FULL_CONTROL</Permission>'
|
||||||
|
'</Grant>'
|
||||||
|
'</AccessControlList>'
|
||||||
|
'</AccessControlPolicy>' %
|
||||||
|
(account_name, account_name, account_name, account_name))
|
||||||
return Response(body=body, content_type="text/plain")
|
return Response(body=body, content_type="text/plain")
|
||||||
|
|
||||||
|
|
||||||
@ -174,15 +275,20 @@ def swift_acl_translate(acl, group='', user=''):
|
|||||||
"""
|
"""
|
||||||
swift_acl = {}
|
swift_acl = {}
|
||||||
swift_acl['public-read'] = [['HTTP_X_CONTAINER_READ', '.r:*,.rlistings']]
|
swift_acl['public-read'] = [['HTTP_X_CONTAINER_READ', '.r:*,.rlistings']]
|
||||||
|
# Swift does not support public write: https://answers.launchpad.net/swift/+question/169541
|
||||||
swift_acl['public-read-write'] = [['HTTP_X_CONTAINER_WRITE', '.r:*'],\
|
swift_acl['public-read-write'] = [['HTTP_X_CONTAINER_WRITE', '.r:*'],\
|
||||||
['HTTP_X_CONTAINER_READ', '.r:*,.rlistings']]
|
['HTTP_X_CONTAINER_READ', '.r:*,.rlistings']]
|
||||||
|
|
||||||
#TODO: if there's a way to get group and user, this should work for private:
|
#TODO: if there's a way to get group and user, this should work for private:
|
||||||
swift_acl['private'] = [['HTTP_X_CONTAINER_WRITE'], [ group + ':' + user], \
|
#swift_acl['private'] = [['HTTP_X_CONTAINER_WRITE', group + ':' + user], \
|
||||||
['HTTP_X_CONTAINER_READ', group + ':' + user]]
|
# ['HTTP_X_CONTAINER_READ', group + ':' + user]]
|
||||||
|
swift_acl['private'] = [['HTTP_X_CONTAINER_WRITE', '.'], \
|
||||||
|
['HTTP_X_CONTAINER_READ', '.']]
|
||||||
|
|
||||||
if acl == 'private' or 'authenticated-read':
|
if acl == 'authenticated-read':
|
||||||
return "Unsupported"
|
return "Unsupported"
|
||||||
|
elif acl not in swift_acl:
|
||||||
|
return "InvalidArgument"
|
||||||
|
|
||||||
return swift_acl[acl]
|
return swift_acl[acl]
|
||||||
|
|
||||||
@ -254,10 +360,8 @@ class BucketController(WSGIContext):
|
|||||||
max_keys = min(int(args.get('max-keys', MAX_BUCKET_LISTING)),
|
max_keys = min(int(args.get('max-keys', MAX_BUCKET_LISTING)),
|
||||||
MAX_BUCKET_LISTING)
|
MAX_BUCKET_LISTING)
|
||||||
|
|
||||||
if 'acl' in args:
|
if 'acl' not in args:
|
||||||
"""acl request sent with format=json etc confuses swift"""
|
"""acl request sent with format=json etc confuses swift"""
|
||||||
return get_acl(self.account_name)
|
|
||||||
else:
|
|
||||||
env['QUERY_STRING'] = 'format=json&limit=%s' % (max_keys + 1)
|
env['QUERY_STRING'] = 'format=json&limit=%s' % (max_keys + 1)
|
||||||
if 'marker' in args:
|
if 'marker' in args:
|
||||||
env['QUERY_STRING'] += '&marker=%s' % quote(args['marker'])
|
env['QUERY_STRING'] += '&marker=%s' % quote(args['marker'])
|
||||||
@ -267,6 +371,10 @@ class BucketController(WSGIContext):
|
|||||||
env['QUERY_STRING'] += '&delimiter=%s' % quote(args['delimiter'])
|
env['QUERY_STRING'] += '&delimiter=%s' % quote(args['delimiter'])
|
||||||
body_iter = self._app_call(env)
|
body_iter = self._app_call(env)
|
||||||
status = self._get_status_int()
|
status = self._get_status_int()
|
||||||
|
headers = dict(self._response_headers)
|
||||||
|
|
||||||
|
if 'acl' in args:
|
||||||
|
return get_acl(self.account_name, headers)
|
||||||
|
|
||||||
if status != HTTP_OK:
|
if status != HTTP_OK:
|
||||||
if status == HTTP_UNAUTHORIZED:
|
if status == HTTP_UNAUTHORIZED:
|
||||||
@ -299,9 +407,10 @@ class BucketController(WSGIContext):
|
|||||||
xml_escape(self.container_name),
|
xml_escape(self.container_name),
|
||||||
"".join(['<Contents><Key>%s</Key><LastModified>%sZ</LastModif'\
|
"".join(['<Contents><Key>%s</Key><LastModified>%sZ</LastModif'\
|
||||||
'ied><ETag>%s</ETag><Size>%s</Size><StorageClass>STA'\
|
'ied><ETag>%s</ETag><Size>%s</Size><StorageClass>STA'\
|
||||||
'NDARD</StorageClass></Contents>' %
|
'NDARD</StorageClass><Owner><ID>%s</ID><DisplayName>'\
|
||||||
|
'%s</DisplayName></Owner></Contents>' %
|
||||||
(xml_escape(i['name']), i['last_modified'], i['hash'],
|
(xml_escape(i['name']), i['last_modified'], i['hash'],
|
||||||
i['bytes'])
|
i['bytes'], self.account_name, self.account_name)
|
||||||
for i in objects[:max_keys] if 'subdir' not in i]),
|
for i in objects[:max_keys] if 'subdir' not in i]),
|
||||||
"".join(['<CommonPrefixes><Prefix>%s</Prefix></CommonPrefixes>'
|
"".join(['<CommonPrefixes><Prefix>%s</Prefix></CommonPrefixes>'
|
||||||
% xml_escape(i['subdir'])
|
% xml_escape(i['subdir'])
|
||||||
@ -321,6 +430,8 @@ class BucketController(WSGIContext):
|
|||||||
translated_acl = swift_acl_translate(value)
|
translated_acl = swift_acl_translate(value)
|
||||||
if translated_acl == 'Unsupported':
|
if translated_acl == 'Unsupported':
|
||||||
return get_err_response('Unsupported')
|
return get_err_response('Unsupported')
|
||||||
|
elif translated_acl == 'InvalidArgument':
|
||||||
|
return get_err_response('InvalidArgument')
|
||||||
|
|
||||||
for header,acl in swift_acl_translate(value):
|
for header,acl in swift_acl_translate(value):
|
||||||
env[header] = acl
|
env[header] = acl
|
||||||
@ -365,6 +476,13 @@ class BucketController(WSGIContext):
|
|||||||
resp.status = HTTP_NO_CONTENT
|
resp.status = HTTP_NO_CONTENT
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
def POST(self, env, start_response):
|
||||||
|
"""
|
||||||
|
Handle POST Bucket request
|
||||||
|
"""
|
||||||
|
|
||||||
|
return get_err_response('Unsupported')
|
||||||
|
|
||||||
|
|
||||||
class ObjectController(WSGIContext):
|
class ObjectController(WSGIContext):
|
||||||
"""
|
"""
|
||||||
@ -400,7 +518,7 @@ class ObjectController(WSGIContext):
|
|||||||
else:
|
else:
|
||||||
args = {}
|
args = {}
|
||||||
if 'acl' in args:
|
if 'acl' in args:
|
||||||
return get_acl(self.account_name)
|
return get_acl(self.account_name, headers)
|
||||||
|
|
||||||
new_hdrs = {}
|
new_hdrs = {}
|
||||||
for key, val in headers.iteritems():
|
for key, val in headers.iteritems():
|
||||||
|
Loading…
x
Reference in New Issue
Block a user