For ACL strings: Shortened .ref to just .r, though .ref, .referer, and .referrer are all accepted. Updated 'Creating Your Own Auth Middleware' to describe how the DevAuth server works and suggestions for creating one's own. Added reseller_prefix (optional) implementation. Used urlparse in referrer_allowed. Fixed bug where group names would get lowercased by clean_acl. Changed .r:any to .r:*. Allowed .r:*.example.com to mean .r:.example.com. Made proxy log just the first authenticated group (the user) alongside the token. Moved proxy callback to clean_acl before the length check of the metadata. Cleaned up redundant logic in first proxy swift.authorize callback. Bit better docs. More and updated tests.

This commit is contained in:
gholt
2010-09-08 22:37:27 -07:00
parent 85b8d97086
commit d2ec027e22
15 changed files with 209 additions and 134 deletions

5
bin/st
View File

@@ -1417,9 +1417,8 @@ Example:
'general documentation for what this means).') 'general documentation for what this means).')
parser.add_option('-r', '--read-acl', dest='read_acl', parser.add_option('-r', '--read-acl', dest='read_acl',
help='Sets the Read ACL with post container commands. ' help='Sets the Read ACL with post container commands. '
'Quick summary of ACL syntax: .ref:any, ' 'Quick summary of ACL syntax: .r:*, .r:-.example.com, '
'.ref:-.example.com, .ref:www.example.com, account1, ' '.r:www.example.com, account1, account2:user2')
'account2:user2')
parser.add_option('-w', '--write-acl', dest='write_acl', parser.add_option('-w', '--write-acl', dest='write_acl',
help='Sets the Write ACL with post container commands. ' help='Sets the Write ACL with post container commands. '
'Quick summary of ACL syntax: account1, account2:user2') 'Quick summary of ACL syntax: account1, account2:user2')

View File

@@ -1,10 +1,10 @@
=============== ==========================
Auth Middleware Auth Server and Middleware
=============== ==========================
--------------------------------- --------------------------------------------
Creating Your Own Auth Middleware Creating Your Own Auth Server and Middleware
--------------------------------- --------------------------------------------
The included swift/common/middleware/auth.py is a good minimal example of how The included swift/common/middleware/auth.py is a good minimal example of how
to create auth middleware. The main points are that the auth middleware can to create auth middleware. The main points are that the auth middleware can
@@ -26,6 +26,39 @@ specific information, it just passes it along. Convention has
environ['REMOTE_USER'] set to the authenticated user string but often more environ['REMOTE_USER'] set to the authenticated user string but often more
information is needed than just that. information is needed than just that.
The included DevAuth will set the REMOTE_USER to a comma separated list of
groups the user belongs to. The first group will be the "user's group", a group
that only the user belongs to. The second group will be the "account's group",
a group that includes all users for that auth account (different than the
storage account). The third group is optional and is the storage account
string. If the user does not have admin access to the account, the third group
will be omitted.
It is highly recommended that authentication server implementers prefix their
group names and tokens with a configurable reseller prefix (`AUTH_` by default
with the included DevAuth). This prefix will allow deconflicting with other
authentication servers that might be using the same Swift cluster.
The only other restriction is that no group name should begin with a period '.'
as that is reserved for internal Swift use (such as the .r for referrer
designations as you'll see later). This shouldn't be an issue if a reseller
prefix is in use and does not begin with a period.
Example Authentication with DevAuth:
* Token AUTH_tkabcd is given to the DevAuth middleware in a request's
X-Auth-Token header.
* The DevAuth middleware makes a validate token AUTH_tkabcd call to the
external DevAuth server.
* The external DevAuth server validates the token AUTH_tkabcd and discovers
it matches the "tester" user within the "test" account for the storage
account "AUTH_storage_xyz".
* The external DevAuth server responds with "X-Auth-Groups:
AUTH_test:tester,AUTH_test,AUTH_storage_xyz"
* Now this user will have full access (via authorization procedures later)
to the AUTH_storage_xyz Swift storage account and access to anything with
ACLs specifying at least one of those three groups returned.
Authorization is performed through callbacks by the Swift Proxy server to the Authorization is performed through callbacks by the Swift Proxy server to the
WSGI environment's swift.authorize value, if one is set. The swift.authorize WSGI environment's swift.authorize value, if one is set. The swift.authorize
value should simply be a function that takes a webob.Request as an argument and value should simply be a function that takes a webob.Request as an argument and

View File

@@ -37,6 +37,10 @@ use = egg:swift#proxy
[filter:auth] [filter:auth]
use = egg:swift#auth use = egg:swift#auth
# The reseller prefix, if set, will verify a token begins with this prefix
# before even attempting to validate it with the external reseller. Usefull if
# multiple auth systems are in use for one Swift cluster.
# reseller_prefix =
# ip = 127.0.0.1 # ip = 127.0.0.1
# port = 11000 # port = 11000
# ssl = false # ssl = false

View File

@@ -94,6 +94,9 @@ class AuthController(object):
def __init__(self, conf, ring=None): def __init__(self, conf, ring=None):
self.logger = get_logger(conf) self.logger = get_logger(conf)
self.swift_dir = conf.get('swift_dir', '/etc/swift') self.swift_dir = conf.get('swift_dir', '/etc/swift')
self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip()
if self.reseller_prefix and self.reseller_prefix[-1] != '_':
self.reseller_prefix += '_'
self.default_cluster_url = \ self.default_cluster_url = \
conf.get('default_cluster_url', 'http://127.0.0.1:8080/v1') conf.get('default_cluster_url', 'http://127.0.0.1:8080/v1')
self.token_life = int(conf.get('token_life', 86400)) self.token_life = int(conf.get('token_life', 86400))
@@ -147,7 +150,7 @@ class AuthController(object):
begin = time() begin = time()
orig_account_name = account_name orig_account_name = account_name
if not account_name: if not account_name:
account_name = str(uuid4()) account_name = '%s%s' % (self.reseller_prefix, uuid4().hex)
partition, nodes = self.account_ring.get_nodes(account_name) partition, nodes = self.account_ring.get_nodes(account_name)
headers = {'X-Timestamp': normalize_timestamp(time()), headers = {'X-Timestamp': normalize_timestamp(time()),
'x-cf-trans-id': 'tx' + str(uuid4())} 'x-cf-trans-id': 'tx' + str(uuid4())}
@@ -358,7 +361,9 @@ class AuthController(object):
validation = self.validate_token(token) validation = self.validate_token(token)
if not validation: if not validation:
return HTTPNotFound() return HTTPNotFound()
groups = ['%s:%s' % (validation[1], validation[2]), validation[1]] groups = [
'%s%s:%s' % (self.reseller_prefix, validation[1], validation[2]),
'%s%s' % (self.reseller_prefix, validation[1])]
if validation[3]: # admin access to a cfaccount if validation[3]: # admin access to a cfaccount
groups.append(validation[3]) groups.append(validation[3])
return HTTPNoContent(headers={'X-Auth-TTL': validation[0], return HTTPNoContent(headers={'X-Auth-TTL': validation[0],
@@ -482,7 +487,7 @@ class AuthController(object):
if row: if row:
token = row[0] token = row[0]
else: else:
token = 'tk' + str(uuid4()) token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
conn.execute(''' conn.execute('''
INSERT INTO token INSERT INTO token
(token, created, account, user, cfaccount) (token, created, account, user, cfaccount)

View File

@@ -13,6 +13,9 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from urlparse import urlparse
def clean_acl(name, value): def clean_acl(name, value):
""" """
Returns a cleaned ACL header value, validating that it meets the formatting Returns a cleaned ACL header value, validating that it meets the formatting
@@ -27,30 +30,35 @@ def clean_acl(name, value):
The referrer designation format is:: The referrer designation format is::
.ref:[-]value .r:[-]value
The value can be "any" to specify any referrer host is allowed access, a The ``.r`` can also be ``.ref``, ``.referer``, or ``.referrer``; though it
specific host name like "www.example.com", or if it has a leading period will be shortened to just ``.r`` for decreased character count usage.
"." it is a domain name specification, like ".example.com". The leading
minus sign "-" indicates referrer hosts that should be denied access. The value can be ``*`` to specify any referrer host is allowed access, a
specific host name like ``www.example.com``, or if it has a leading period
``.`` or leading ``*.`` it is a domain name specification, like
``.example.com`` or ``*.example.com``. The leading minus sign ``-``
indicates referrer hosts that should be denied access.
Referrer access is applied in the order they are specified. For example, Referrer access is applied in the order they are specified. For example,
.ref:.example.com,.ref:-thief.example.com would allow all hosts ending with .r:.example.com,.r:-thief.example.com would allow all hosts ending with
.example.com except for the specific host thief.example.com. .example.com except for the specific host thief.example.com.
Example valid ACLs:: Example valid ACLs::
.ref:any .r:*
.ref:any,.ref:-.thief.com .r:*,.r:-.thief.com
.ref:any,.ref:-.thief.com,bobs_account,sues_account:sue .r:*,.r:.example.com,.r:-thief.example.com
.r:*,.r:-.thief.com,bobs_account,sues_account:sue
bobs_account,sues_account:sue bobs_account,sues_account:sue
Example invalid ACLs:: Example invalid ACLs::
.ref: .r:
.ref:- .r:-
Also, .ref designations aren't allowed in headers whose names include the Also, .r designations aren't allowed in headers whose names include the
word 'write'. word 'write'.
ACLs that are "messy" will be cleaned up. Examples: ACLs that are "messy" will be cleaned up. Examples:
@@ -58,10 +66,11 @@ def clean_acl(name, value):
====================== ====================== ====================== ======================
Original Cleaned Original Cleaned
---------------------- ---------------------- ---------------------- ----------------------
bob, sue bob,sue ``bob, sue`` ``bob,sue``
bob , sue bob,sue ``bob , sue`` ``bob,sue``
bob,,,sue bob,sue ``bob,,,sue`` ``bob,sue``
.ref : any .ref:any ``.referrer : *`` ``.r:*``
``.ref:*.example.com`` ``.r:.example.com``
====================== ====================== ====================== ======================
:param name: The name of the header being cleaned, such as X-Container-Read :param name: The name of the header being cleaned, such as X-Container-Read
@@ -71,30 +80,34 @@ def clean_acl(name, value):
:raises ValueError: If the value does not meet the ACL formatting :raises ValueError: If the value does not meet the ACL formatting
requirements; the error message will indicate why. requirements; the error message will indicate why.
""" """
name = name.lower()
values = [] values = []
for raw_value in value.lower().split(','): for raw_value in value.split(','):
raw_value = raw_value.strip() raw_value = raw_value.strip()
if raw_value: if raw_value:
if ':' not in raw_value: if ':' not in raw_value:
values.append(raw_value) values.append(raw_value)
else: else:
first, second = (v.strip() for v in raw_value.split(':', 1)) first, second = (v.strip() for v in raw_value.split(':', 1))
if first != '.ref': if not first or first[0] != '.':
values.append(raw_value) values.append(raw_value)
elif 'write' in name: elif first in ('.r', '.ref', '.referer', '.referrer'):
raise ValueError('Referrers not allowed in write ACLs: %s' if 'write' in name:
% repr(raw_value)) raise ValueError('Referrers not allowed in write ACL: '
elif not second: '%s' % repr(raw_value))
raise ValueError('No value after referrer designation in ' negate = False
'%s' % repr(raw_value)) if second and second[0] == '-':
else: negate = True
if second[0] == '-':
second = second[1:].strip() second = second[1:].strip()
if not second: if second and second != '*' and second[0] == '*':
raise ValueError('No value after referrer deny ' second = second[1:].strip()
'designation in %s' % repr(raw_value)) if not second or second == '.':
second = '-' + second raise ValueError('No host/domain value after referrer '
values.append('%s:%s' % (first, second)) 'designation in ACL: %s' % repr(raw_value))
values.append('.r:%s%s' % (negate and '-' or '', second))
else:
raise ValueError('Unknown designator %s in ACL: %s' %
(repr(first), repr(raw_value)))
return ','.join(values) return ','.join(values)
@@ -106,15 +119,15 @@ def parse_acl(acl_string):
:param acl_string: The standard Swift ACL string to parse. :param acl_string: The standard Swift ACL string to parse.
:returns: A tuple of (referrers, groups) where referrers is a list of :returns: A tuple of (referrers, groups) where referrers is a list of
referrer designations (without the leading .ref:) and groups is a referrer designations (without the leading .r:) and groups is a
list of groups to allow access. list of groups to allow access.
""" """
referrers = [] referrers = []
groups = [] groups = []
if acl_string: if acl_string:
for value in acl_string.split(','): for value in acl_string.split(','):
if value.startswith('.ref:'): if value.startswith('.r:'):
referrers.append(value[len('.ref:'):]) referrers.append(value[len('.r:'):])
else: else:
groups.append(value) groups.append(value)
return referrers, groups return referrers, groups
@@ -134,24 +147,14 @@ def referrer_allowed(referrer, referrer_acl):
""" """
allow = False allow = False
if referrer_acl: if referrer_acl:
if not referrer: rhost = urlparse(referrer or '').hostname or 'unknown'
rhost = 'unknown'
else:
parts = referrer.split('//', 1)
if len(parts) == 2:
rhost = parts[1].split('/', 1)[0]
if '@' in rhost:
rhost = rhost.rsplit('@', 1)[1]
rhost = rhost.split(':', 1)[0].lower()
else:
rhost = 'unknown'
for mhost in referrer_acl: for mhost in referrer_acl:
if mhost[0] == '-': if mhost[0] == '-':
mhost = mhost[1:] mhost = mhost[1:]
if mhost == rhost or \ if mhost == rhost or \
(mhost[0] == '.' and rhost.endswith(mhost)): (mhost[0] == '.' and rhost.endswith(mhost)):
allow = False allow = False
elif mhost == 'any' or mhost == rhost or \ elif mhost == '*' or mhost == rhost or \
(mhost[0] == '.' and rhost.endswith(mhost)): (mhost[0] == '.' and rhost.endswith(mhost)):
allow = True allow = True
return allow return allow

View File

@@ -29,6 +29,9 @@ class DevAuth(object):
def __init__(self, app, conf): def __init__(self, app, conf):
self.app = app self.app = app
self.conf = conf self.conf = conf
self.reseller_prefix = conf.get('reseller_prefix', '').strip()
if self.reseller_prefix and self.reseller_prefix[-1] != '_':
self.reseller_prefix += '_'
self.auth_host = conf.get('ip', '127.0.0.1') self.auth_host = conf.get('ip', '127.0.0.1')
self.auth_port = int(conf.get('port', 11000)) self.auth_port = int(conf.get('port', 11000))
self.ssl = \ self.ssl = \
@@ -44,7 +47,7 @@ class DevAuth(object):
""" """
groups = None groups = None
token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN')) token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
if token: if token and token.startswith(self.reseller_prefix):
memcache_client = cache_from_env(env) memcache_client = cache_from_env(env)
key = 'devauth/%s' % token key = 'devauth/%s' % token
cached_auth_data = memcache_client.get(key) cached_auth_data = memcache_client.get(key)
@@ -68,6 +71,10 @@ class DevAuth(object):
env['REMOTE_USER'] = groups env['REMOTE_USER'] = groups
env['swift.authorize'] = self.authorize env['swift.authorize'] = self.authorize
env['swift.clean_acl'] = clean_acl env['swift.clean_acl'] = clean_acl
# We know the proxy logs the token, so we augment it just a bit to also
# log the authenticated user.
user = groups and groups.split(',', 1)[0] or ''
env['HTTP_X_AUTH_TOKEN'] = '%s,%s' % (user, token)
return self.app(env, start_response) return self.app(env, start_response)
def authorize(self, req): def authorize(self, req):

View File

@@ -882,6 +882,7 @@ class ContainerController(Controller):
@public @public
def PUT(self, req): def PUT(self, req):
"""HTTP PUT request handler.""" """HTTP PUT request handler."""
self.clean_acls(req)
error_response = check_metadata(req, 'container') error_response = check_metadata(req, 'container')
if error_response: if error_response:
return error_response return error_response
@@ -899,7 +900,6 @@ class ContainerController(Controller):
self.account_name, self.container_name) self.account_name, self.container_name)
headers = {'X-Timestamp': normalize_timestamp(time.time()), headers = {'X-Timestamp': normalize_timestamp(time.time()),
'x-cf-trans-id': self.trans_id} 'x-cf-trans-id': self.trans_id}
self.clean_acls(req)
headers.update(value for value in req.headers.iteritems() headers.update(value for value in req.headers.iteritems()
if value[0].lower() in self.pass_through_headers or if value[0].lower() in self.pass_through_headers or
value[0].lower().startswith('x-container-meta-')) value[0].lower().startswith('x-container-meta-'))
@@ -948,6 +948,7 @@ class ContainerController(Controller):
@public @public
def POST(self, req): def POST(self, req):
"""HTTP POST request handler.""" """HTTP POST request handler."""
self.clean_acls(req)
error_response = check_metadata(req, 'container') error_response = check_metadata(req, 'container')
if error_response: if error_response:
return error_response return error_response
@@ -958,7 +959,6 @@ class ContainerController(Controller):
self.account_name, self.container_name) self.account_name, self.container_name)
headers = {'X-Timestamp': normalize_timestamp(time.time()), headers = {'X-Timestamp': normalize_timestamp(time.time()),
'x-cf-trans-id': self.trans_id} 'x-cf-trans-id': self.trans_id}
self.clean_acls(req)
headers.update(value for value in req.headers.iteritems() headers.update(value for value in req.headers.iteritems()
if value[0].lower() in self.pass_through_headers or if value[0].lower() in self.pass_through_headers or
value[0].lower().startswith('x-container-meta-')) value[0].lower().startswith('x-container-meta-'))
@@ -1273,12 +1273,14 @@ class BaseApplication(object):
# controller's method indicates it'd like to gather more # controller's method indicates it'd like to gather more
# information and try again later. # information and try again later.
resp = req.environ['swift.authorize'](req) resp = req.environ['swift.authorize'](req)
if resp: if not resp:
if not getattr(handler, 'delay_denial', None) and \ # No resp means authorized, no delayed recheck required.
'swift.authorize' in req.environ:
return resp
else:
del req.environ['swift.authorize'] del req.environ['swift.authorize']
else:
# Response indicates denial, but we might delay the denial
# and recheck later. If not delayed, return the error now.
if not getattr(handler, 'delay_denial', None):
return resp
return handler(req) return handler(req)
except Exception: except Exception:
self.logger.exception('ERROR Unhandled exception in request') self.logger.exception('ERROR Unhandled exception in request')
@@ -1331,8 +1333,7 @@ class Application(BaseApplication):
status_int, status_int,
req.referer or '-', req.referer or '-',
req.user_agent or '-', req.user_agent or '-',
'%s:%s' % (req.remote_user or '', req.headers.get('x-auth-token', '-'),
req.headers.get('x-auth-token', '-')),
getattr(req, 'bytes_transferred', 0) or '-', getattr(req, 'bytes_transferred', 0) or '-',
getattr(response, 'bytes_transferred', 0) or '-', getattr(response, 'bytes_transferred', 0) or '-',
req.headers.get('etag', '-'), req.headers.get('etag', '-'),

View File

@@ -2,6 +2,7 @@
auth_host = 127.0.0.1 auth_host = 127.0.0.1
auth_port = 11000 auth_port = 11000
auth_ssl = no auth_ssl = no
auth_prefix = AUTH
# Primary functional test account (needs admin access to the account) # Primary functional test account (needs admin access to the account)
account = test account = test

View File

@@ -10,6 +10,9 @@ from swift.common.client import get_auth, http_connection
swift_test_auth = os.environ.get('SWIFT_TEST_AUTH') swift_test_auth = os.environ.get('SWIFT_TEST_AUTH')
swift_test_auth_prefix = os.environ.get('SWIFT_TEST_AUTH_PREFIX')
if swift_test_auth_prefix and swift_test_auth_prefix[-1] != '_':
swift_test_auth_prefix += '_'
swift_test_user = [os.environ.get('SWIFT_TEST_USER'), None, None] swift_test_user = [os.environ.get('SWIFT_TEST_USER'), None, None]
swift_test_key = [os.environ.get('SWIFT_TEST_KEY'), None, None] swift_test_key = [os.environ.get('SWIFT_TEST_KEY'), None, None]
@@ -32,6 +35,9 @@ if not all([swift_test_auth, swift_test_user[0], swift_test_key[0]]):
if conf.get('auth_ssl', 'no').lower() in ('yes', 'true', 'on', '1'): if conf.get('auth_ssl', 'no').lower() in ('yes', 'true', 'on', '1'):
swift_test_auth = 'https' swift_test_auth = 'https'
swift_test_auth += '://%(auth_host)s:%(auth_port)s/v1.0' % conf swift_test_auth += '://%(auth_host)s:%(auth_port)s/v1.0' % conf
swift_test_auth_prefix = conf.get('auth_prefix', 'AUTH')
if swift_test_auth_prefix and swift_test_auth_prefix[-1] != '_':
swift_test_auth_prefix += '_'
swift_test_user[0] = '%(account)s:%(username)s' % conf swift_test_user[0] = '%(account)s:%(username)s' % conf
swift_test_key[0] = conf['password'] swift_test_key[0] = conf['password']
try: try:

View File

@@ -9,7 +9,7 @@ from swift.common.constraints import MAX_META_COUNT, MAX_META_NAME_LENGTH, \
MAX_META_OVERALL_SIZE, MAX_META_VALUE_LENGTH MAX_META_OVERALL_SIZE, MAX_META_VALUE_LENGTH
from swift_testing import check_response, retry, skip, skip2, skip3, \ from swift_testing import check_response, retry, skip, skip2, skip3, \
swift_test_user swift_test_auth_prefix, swift_test_user
class TestContainer(unittest.TestCase): class TestContainer(unittest.TestCase):
@@ -334,7 +334,7 @@ class TestContainer(unittest.TestCase):
def post(url, token, parsed, conn): def post(url, token, parsed, conn):
conn.request('POST', parsed.path + '/' + self.name, '', conn.request('POST', parsed.path + '/' + self.name, '',
{'X-Auth-Token': token, {'X-Auth-Token': token,
'X-Container-Read': '.ref:any'}) 'X-Container-Read': '.r:*'})
return check_response(conn) return check_response(conn)
resp = retry(post) resp = retry(post)
resp.read() resp.read()
@@ -377,10 +377,10 @@ class TestContainer(unittest.TestCase):
self.assertEquals(resp.status, 403) self.assertEquals(resp.status, 403)
# Make the container accessible by the second account # Make the container accessible by the second account
def post(url, token, parsed, conn): def post(url, token, parsed, conn):
conn.request('POST', parsed.path + '/' + self.name, '', conn.request('POST', parsed.path + '/' + self.name, '', {
{'X-Auth-Token': token, 'X-Auth-Token': token,
'X-Container-Read': 'test2', 'X-Container-Read': '%stest2' % swift_test_auth_prefix,
'X-Container-Write': 'test2'}) 'X-Container-Write': '%stest2' % swift_test_auth_prefix})
return check_response(conn) return check_response(conn)
resp = retry(post) resp = retry(post)
resp.read() resp.read()
@@ -427,7 +427,7 @@ class TestContainer(unittest.TestCase):
def post(url, token, parsed, conn): def post(url, token, parsed, conn):
conn.request('POST', parsed.path + '/' + self.name, '', conn.request('POST', parsed.path + '/' + self.name, '',
{'X-Auth-Token': token, {'X-Auth-Token': token,
'X-Container-Read': '.ref:any'}) 'X-Container-Read': '.r:*'})
return check_response(conn) return check_response(conn)
resp = retry(post) resp = retry(post)
resp.read() resp.read()
@@ -446,9 +446,9 @@ class TestContainer(unittest.TestCase):
self.assertEquals(resp.status, 403) self.assertEquals(resp.status, 403)
# Now make the container also writeable by the second account # Now make the container also writeable by the second account
def post(url, token, parsed, conn): def post(url, token, parsed, conn):
conn.request('POST', parsed.path + '/' + self.name, '', conn.request('POST', parsed.path + '/' + self.name, '', {
{'X-Auth-Token': token, 'X-Auth-Token': token,
'X-Container-Write': 'test2'}) 'X-Container-Write': '%stest2' % swift_test_auth_prefix})
return check_response(conn) return check_response(conn)
resp = retry(post) resp = retry(post)
resp.read() resp.read()
@@ -485,7 +485,9 @@ class TestContainer(unittest.TestCase):
# Make the container accessible by the third account # Make the container accessible by the third account
def post(url, token, parsed, conn): def post(url, token, parsed, conn):
conn.request('POST', parsed.path + '/' + self.name, '', conn.request('POST', parsed.path + '/' + self.name, '',
{'X-Auth-Token': token, 'X-Container-Read': swift_test_user[2]}) {'X-Auth-Token': token,
'X-Container-Read': '%s%s' %
(swift_test_auth_prefix, swift_test_user[2])})
return check_response(conn) return check_response(conn)
resp = retry(post) resp = retry(post)
resp.read() resp.read()
@@ -506,7 +508,8 @@ class TestContainer(unittest.TestCase):
def post(url, token, parsed, conn): def post(url, token, parsed, conn):
conn.request('POST', parsed.path + '/' + self.name, '', conn.request('POST', parsed.path + '/' + self.name, '',
{'X-Auth-Token': token, {'X-Auth-Token': token,
'X-Container-Write': swift_test_user[2]}) 'X-Container-Write': '%s%s' %
(swift_test_auth_prefix, swift_test_user[2])})
return check_response(conn) return check_response(conn)
resp = retry(post) resp = retry(post)
resp.read() resp.read()

View File

@@ -65,7 +65,7 @@ class TestObject(unittest.TestCase):
def post(url, token, parsed, conn): def post(url, token, parsed, conn):
conn.request('POST', parsed.path + '/' + self.container, '', conn.request('POST', parsed.path + '/' + self.container, '',
{'X-Auth-Token': token, {'X-Auth-Token': token,
'X-Container-Read': '.ref:any'}) 'X-Container-Read': '.r:*'})
return check_response(conn) return check_response(conn)
resp = retry(post) resp = retry(post)
resp.read() resp.read()

View File

@@ -21,56 +21,71 @@ from swift.common.middleware import acl
class TestACL(unittest.TestCase): class TestACL(unittest.TestCase):
def test_clean_acl(self): def test_clean_acl(self):
value = acl.clean_acl('header', '.ref:any') value = acl.clean_acl('header', '.r:*')
self.assertEquals(value, '.ref:any') self.assertEquals(value, '.r:*')
value = acl.clean_acl('header', '.ref:specific.host') value = acl.clean_acl('header', '.r:specific.host')
self.assertEquals(value, '.ref:specific.host') self.assertEquals(value, '.r:specific.host')
value = acl.clean_acl('header', '.ref:.ending.with') value = acl.clean_acl('header', '.r:.ending.with')
self.assertEquals(value, '.ref:.ending.with') self.assertEquals(value, '.r:.ending.with')
value = acl.clean_acl('header', '.ref:one,.ref:two') value = acl.clean_acl('header', '.r:*.ending.with')
self.assertEquals(value, '.ref:one,.ref:two') self.assertEquals(value, '.r:.ending.with')
value = acl.clean_acl('header', '.ref:any,.ref:-specific.host') value = acl.clean_acl('header', '.r:-*.ending.with')
self.assertEquals(value, '.ref:any,.ref:-specific.host') self.assertEquals(value, '.r:-.ending.with')
value = acl.clean_acl('header', '.ref:any,.ref:-.ending.with') value = acl.clean_acl('header', '.r:one,.r:two')
self.assertEquals(value, '.ref:any,.ref:-.ending.with') self.assertEquals(value, '.r:one,.r:two')
value = acl.clean_acl('header', '.ref:one,.ref:-two') value = acl.clean_acl('header', '.r:*,.r:-specific.host')
self.assertEquals(value, '.ref:one,.ref:-two') self.assertEquals(value, '.r:*,.r:-specific.host')
value = acl.clean_acl('header', value = acl.clean_acl('header', '.r:*,.r:-.ending.with')
'.ref:one,.ref:-two,account,account:user') self.assertEquals(value, '.r:*,.r:-.ending.with')
self.assertEquals(value, '.ref:one,.ref:-two,account,account:user') value = acl.clean_acl('header', '.r:one,.r:-two')
self.assertEquals(value, '.r:one,.r:-two')
value = acl.clean_acl('header', '.r:one,.r:-two,account,account:user')
self.assertEquals(value, '.r:one,.r:-two,account,account:user')
value = acl.clean_acl('header', 'TEST_account')
self.assertEquals(value, 'TEST_account')
value = acl.clean_acl('header', '.ref:*')
self.assertEquals(value, '.r:*')
value = acl.clean_acl('header', '.referer:*')
self.assertEquals(value, '.r:*')
value = acl.clean_acl('header', '.referrer:*')
self.assertEquals(value, '.r:*')
value = acl.clean_acl('header', value = acl.clean_acl('header',
' .ref : one , ,, .ref:two , .ref : - three ') ' .r : one , ,, .r:two , .r : - three ')
self.assertEquals(value, '.ref:one,.ref:two,.ref:-three') self.assertEquals(value, '.r:one,.r:two,.r:-three')
self.assertRaises(ValueError, acl.clean_acl, 'header', '.ref:') self.assertRaises(ValueError, acl.clean_acl, 'header', '.unknown:test')
self.assertRaises(ValueError, acl.clean_acl, 'header', ' .ref : ') self.assertRaises(ValueError, acl.clean_acl, 'header', '.r:')
self.assertRaises(ValueError, acl.clean_acl, 'header', '.r:*.')
self.assertRaises(ValueError, acl.clean_acl, 'header', '.r : * . ')
self.assertRaises(ValueError, acl.clean_acl, 'header', '.r:-*.')
self.assertRaises(ValueError, acl.clean_acl, 'header', '.r : - * . ')
self.assertRaises(ValueError, acl.clean_acl, 'header', ' .r : ')
self.assertRaises(ValueError, acl.clean_acl, 'header', 'user , .r : ')
self.assertRaises(ValueError, acl.clean_acl, 'header', '.r:-')
self.assertRaises(ValueError, acl.clean_acl, 'header', ' .r : - ')
self.assertRaises(ValueError, acl.clean_acl, 'header', self.assertRaises(ValueError, acl.clean_acl, 'header',
'user , .ref : ') 'user , .r : - ')
self.assertRaises(ValueError, acl.clean_acl, 'header', '.ref:-') self.assertRaises(ValueError, acl.clean_acl, 'write-header', '.r:r')
self.assertRaises(ValueError, acl.clean_acl, 'header', ' .ref : - ')
self.assertRaises(ValueError, acl.clean_acl, 'header',
'user , .ref : - ')
self.assertRaises(ValueError, acl.clean_acl, 'write-header', '.ref:r')
def test_parse_acl(self): def test_parse_acl(self):
self.assertEquals(acl.parse_acl(None), ([], [])) self.assertEquals(acl.parse_acl(None), ([], []))
self.assertEquals(acl.parse_acl(''), ([], [])) self.assertEquals(acl.parse_acl(''), ([], []))
self.assertEquals(acl.parse_acl('.ref:ref1'), (['ref1'], [])) self.assertEquals(acl.parse_acl('.r:ref1'), (['ref1'], []))
self.assertEquals(acl.parse_acl('.ref:-ref1'), (['-ref1'], [])) self.assertEquals(acl.parse_acl('.r:-ref1'), (['-ref1'], []))
self.assertEquals(acl.parse_acl('account:user'), self.assertEquals(acl.parse_acl('account:user'),
([], ['account:user'])) ([], ['account:user']))
self.assertEquals(acl.parse_acl('account'), ([], ['account'])) self.assertEquals(acl.parse_acl('account'), ([], ['account']))
self.assertEquals(acl.parse_acl('acc1,acc2:usr2,.ref:ref3,.ref:-ref4'), self.assertEquals(acl.parse_acl('acc1,acc2:usr2,.r:ref3,.r:-ref4'),
(['ref3', '-ref4'], ['acc1', 'acc2:usr2'])) (['ref3', '-ref4'], ['acc1', 'acc2:usr2']))
self.assertEquals(acl.parse_acl( self.assertEquals(acl.parse_acl(
'acc1,acc2:usr2,.ref:ref3,acc3,acc4:usr4,.ref:ref5,.ref:-ref6'), 'acc1,acc2:usr2,.r:ref3,acc3,acc4:usr4,.r:ref5,.r:-ref6'),
(['ref3', 'ref5', '-ref6'], (['ref3', 'ref5', '-ref6'],
['acc1', 'acc2:usr2', 'acc3', 'acc4:usr4'])) ['acc1', 'acc2:usr2', 'acc3', 'acc4:usr4']))
def test_referrer_allowed(self): def test_referrer_allowed(self):
self.assert_(not acl.referrer_allowed('host', None)) self.assert_(not acl.referrer_allowed('host', None))
self.assert_(not acl.referrer_allowed('host', [])) self.assert_(not acl.referrer_allowed('host', []))
self.assert_(acl.referrer_allowed(None, ['any'])) self.assert_(acl.referrer_allowed(None, ['*']))
self.assert_(acl.referrer_allowed('', ['any'])) self.assert_(acl.referrer_allowed('', ['*']))
self.assert_(not acl.referrer_allowed(None, ['specific.host'])) self.assert_(not acl.referrer_allowed(None, ['specific.host']))
self.assert_(not acl.referrer_allowed('', ['specific.host'])) self.assert_(not acl.referrer_allowed('', ['specific.host']))
self.assert_(acl.referrer_allowed('http://www.example.com/index.html', self.assert_(acl.referrer_allowed('http://www.example.com/index.html',
@@ -93,7 +108,7 @@ class TestACL(unittest.TestCase):
self.assert_(not acl.referrer_allowed('http://thief.example.com', self.assert_(not acl.referrer_allowed('http://thief.example.com',
['.example.com', '-thief.example.com'])) ['.example.com', '-thief.example.com']))
self.assert_(not acl.referrer_allowed('http://thief.example.com', self.assert_(not acl.referrer_allowed('http://thief.example.com',
['any', '-thief.example.com'])) ['*', '-thief.example.com']))
self.assert_(acl.referrer_allowed('http://www.example.com', self.assert_(acl.referrer_allowed('http://www.example.com',
['.other.com', 'www.example.com'])) ['.other.com', 'www.example.com']))
self.assert_(acl.referrer_allowed('http://www.example.com', self.assert_(acl.referrer_allowed('http://www.example.com',
@@ -104,7 +119,7 @@ class TestACL(unittest.TestCase):
['.example.com'])) ['.example.com']))
self.assert_(not acl.referrer_allowed('../index.html', self.assert_(not acl.referrer_allowed('../index.html',
['.example.com'])) ['.example.com']))
self.assert_(acl.referrer_allowed('www.example.com', ['any'])) self.assert_(acl.referrer_allowed('www.example.com', ['*']))
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -253,31 +253,31 @@ class TestAuth(unittest.TestCase):
self.assert_(resp.startswith('403'), resp) self.assert_(resp.startswith('403'), resp)
req = Request.blank('/v1/cfa') req = Request.blank('/v1/cfa')
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
req.acl = '.ref:any' req.acl = '.r:*'
self.assertEquals(self.test_auth.authorize(req), None) self.assertEquals(self.test_auth.authorize(req), None)
req = Request.blank('/v1/cfa') req = Request.blank('/v1/cfa')
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
req.acl = '.ref:.example.com' req.acl = '.r:.example.com'
resp = str(self.test_auth.authorize(req)) resp = str(self.test_auth.authorize(req))
self.assert_(resp.startswith('403'), resp) self.assert_(resp.startswith('403'), resp)
req = Request.blank('/v1/cfa') req = Request.blank('/v1/cfa')
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
req.referrer = 'http://www.example.com/index.html' req.referer = 'http://www.example.com/index.html'
req.acl = '.ref:.example.com' req.acl = '.r:.example.com'
self.assertEquals(self.test_auth.authorize(req), None) self.assertEquals(self.test_auth.authorize(req), None)
req = Request.blank('/v1/cfa') req = Request.blank('/v1/cfa')
resp = str(self.test_auth.authorize(req)) resp = str(self.test_auth.authorize(req))
self.assert_(resp.startswith('401'), resp) self.assert_(resp.startswith('401'), resp)
req = Request.blank('/v1/cfa') req = Request.blank('/v1/cfa')
req.acl = '.ref:any' req.acl = '.r:*'
self.assertEquals(self.test_auth.authorize(req), None) self.assertEquals(self.test_auth.authorize(req), None)
req = Request.blank('/v1/cfa') req = Request.blank('/v1/cfa')
req.acl = '.ref:.example.com' req.acl = '.r:.example.com'
resp = str(self.test_auth.authorize(req)) resp = str(self.test_auth.authorize(req))
self.assert_(resp.startswith('401'), resp) self.assert_(resp.startswith('401'), resp)
req = Request.blank('/v1/cfa') req = Request.blank('/v1/cfa')
req.referrer = 'http://www.example.com/index.html' req.referer = 'http://www.example.com/index.html'
req.acl = '.ref:.example.com' req.acl = '.r:.example.com'
self.assertEquals(self.test_auth.authorize(req), None) self.assertEquals(self.test_auth.authorize(req), None)

View File

@@ -67,14 +67,13 @@ class TestContainerController(unittest.TestCase):
self.assert_('x-container-write' not in response.headers) self.assert_('x-container-write' not in response.headers)
# Ensure POSTing acls works # Ensure POSTing acls works
req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'POST'}, req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'POST'},
headers={'X-Timestamp': '1', 'X-Container-Read': '.ref:any', headers={'X-Timestamp': '1', 'X-Container-Read': '.r:*',
'X-Container-Write': 'account:user'}) 'X-Container-Write': 'account:user'})
self.controller.POST(req) self.controller.POST(req)
req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'HEAD'}) req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'HEAD'})
response = self.controller.HEAD(req) response = self.controller.HEAD(req)
self.assert_(response.status.startswith('204')) self.assert_(response.status.startswith('204'))
self.assertEquals(response.headers.get('x-container-read'), self.assertEquals(response.headers.get('x-container-read'), '.r:*')
'.ref:any')
self.assertEquals(response.headers.get('x-container-write'), self.assertEquals(response.headers.get('x-container-write'),
'account:user') 'account:user')
# Ensure we can clear acls on POST # Ensure we can clear acls on POST
@@ -89,14 +88,13 @@ class TestContainerController(unittest.TestCase):
self.assert_('x-container-write' not in response.headers) self.assert_('x-container-write' not in response.headers)
# Ensure PUTing acls works # Ensure PUTing acls works
req = Request.blank('/sda1/p/a/c2', environ={'REQUEST_METHOD': 'PUT'}, req = Request.blank('/sda1/p/a/c2', environ={'REQUEST_METHOD': 'PUT'},
headers={'X-Timestamp': '4', 'X-Container-Read': '.ref:any', headers={'X-Timestamp': '4', 'X-Container-Read': '.r:*',
'X-Container-Write': 'account:user'}) 'X-Container-Write': 'account:user'})
self.controller.PUT(req) self.controller.PUT(req)
req = Request.blank('/sda1/p/a/c2', environ={'REQUEST_METHOD': 'HEAD'}) req = Request.blank('/sda1/p/a/c2', environ={'REQUEST_METHOD': 'HEAD'})
response = self.controller.HEAD(req) response = self.controller.HEAD(req)
self.assert_(response.status.startswith('204')) self.assert_(response.status.startswith('204'))
self.assertEquals(response.headers.get('x-container-read'), self.assertEquals(response.headers.get('x-container-read'), '.r:*')
'.ref:any')
self.assertEquals(response.headers.get('x-container-write'), self.assertEquals(response.headers.get('x-container-write'),
'account:user') 'account:user')

View File

@@ -1962,7 +1962,7 @@ class TestContainerController(unittest.TestCase):
controller = proxy_server.ContainerController(self.app, 'account', controller = proxy_server.ContainerController(self.app, 'account',
'container') 'container')
req = Request.blank('/a/c', environ={'REQUEST_METHOD': 'POST'}, req = Request.blank('/a/c', environ={'REQUEST_METHOD': 'POST'},
headers={'X-Container-Read': '.ref:any'}) headers={'X-Container-Read': '.r:*'})
req.environ['swift.clean_acl'] = clean_acl req.environ['swift.clean_acl'] = clean_acl
self.app.update_request(req) self.app.update_request(req)
res = controller.POST(req) res = controller.POST(req)
@@ -1973,7 +1973,7 @@ class TestContainerController(unittest.TestCase):
controller = proxy_server.ContainerController(self.app, 'account', controller = proxy_server.ContainerController(self.app, 'account',
'container') 'container')
req = Request.blank('/a/c', environ={'REQUEST_METHOD': 'POST'}, req = Request.blank('/a/c', environ={'REQUEST_METHOD': 'POST'},
headers={'X-Container-Write': '.ref:any'}) headers={'X-Container-Write': '.r:*'})
req.environ['swift.clean_acl'] = clean_acl req.environ['swift.clean_acl'] = clean_acl
self.app.update_request(req) self.app.update_request(req)
res = controller.POST(req) res = controller.POST(req)
@@ -1989,7 +1989,7 @@ class TestContainerController(unittest.TestCase):
controller = proxy_server.ContainerController(self.app, 'account', controller = proxy_server.ContainerController(self.app, 'account',
'container') 'container')
req = Request.blank('/a/c', environ={'REQUEST_METHOD': 'PUT'}, req = Request.blank('/a/c', environ={'REQUEST_METHOD': 'PUT'},
headers={'X-Container-Read': '.ref:any'}) headers={'X-Container-Read': '.r:*'})
req.environ['swift.clean_acl'] = clean_acl req.environ['swift.clean_acl'] = clean_acl
self.app.update_request(req) self.app.update_request(req)
res = controller.PUT(req) res = controller.PUT(req)
@@ -2000,7 +2000,7 @@ class TestContainerController(unittest.TestCase):
controller = proxy_server.ContainerController(self.app, 'account', controller = proxy_server.ContainerController(self.app, 'account',
'container') 'container')
req = Request.blank('/a/c', environ={'REQUEST_METHOD': 'PUT'}, req = Request.blank('/a/c', environ={'REQUEST_METHOD': 'PUT'},
headers={'X-Container-Write': '.ref:any'}) headers={'X-Container-Write': '.r:*'})
req.environ['swift.clean_acl'] = clean_acl req.environ['swift.clean_acl'] = clean_acl
self.app.update_request(req) self.app.update_request(req)
res = controller.PUT(req) res = controller.PUT(req)