py3: port tempauth

Note that the user DB is stored in-memory as native strings, so we do
some crazy-decoding to make comparisons for auth decisions. Seems to
keep the config handling mostly sane, though.

I maybe need to look harder at container ACLs?

Change-Id: Ia58698c9b30d2211eeee8ecb3bbdd1c26fa4034d
This commit is contained in:
Tim Burke
2018-09-17 15:22:55 -07:00
parent 16fe18ae3b
commit c90d34bd02
4 changed files with 40 additions and 26 deletions

View File

@@ -182,7 +182,7 @@ import base64
from eventlet import Timeout from eventlet import Timeout
import six import six
from swift.common.swob import Response, Request from swift.common.swob import Response, Request, wsgi_to_str
from swift.common.swob import HTTPBadRequest, HTTPForbidden, HTTPNotFound, \ from swift.common.swob import HTTPBadRequest, HTTPForbidden, HTTPNotFound, \
HTTPUnauthorized HTTPUnauthorized
@@ -239,6 +239,9 @@ class TempAuth(object):
account = base64.b64decode(account) account = base64.b64decode(account)
username += '=' * (len(username) % 4) username += '=' * (len(username) % 4)
username = base64.b64decode(username) username = base64.b64decode(username)
if not six.PY2:
account = account.decode('utf8')
username = username.decode('utf8')
values = conf[conf_key].split() values = conf[conf_key].split()
if not values: if not values:
raise ValueError('%s has no key set' % conf_key) raise ValueError('%s has no key set' % conf_key)
@@ -438,7 +441,7 @@ class TempAuth(object):
expires, groups = cached_auth_data expires, groups = cached_auth_data
if expires < time(): if expires < time():
groups = None groups = None
else: elif six.PY2:
groups = groups.encode('utf8') groups = groups.encode('utf8')
s3_auth_details = env.get('s3api.auth_details') or\ s3_auth_details = env.get('s3api.auth_details') or\
@@ -498,7 +501,7 @@ class TempAuth(object):
or None if there are no errors. or None if there are no errors.
""" """
acl_header = 'x-account-access-control' acl_header = 'x-account-access-control'
acl_data = req.headers.get(acl_header) acl_data = wsgi_to_str(req.headers.get(acl_header))
result = parse_acl(version=2, data=acl_data) result = parse_acl(version=2, data=acl_data)
if result is None: if result is None:
return 'Syntax error in input (%r)' % acl_data return 'Syntax error in input (%r)' % acl_data
@@ -509,19 +512,17 @@ class TempAuth(object):
# on ACLs, TempAuth is not such an auth system. At this point, # on ACLs, TempAuth is not such an auth system. At this point,
# it thinks it is authoritative. # it thinks it is authoritative.
if key not in tempauth_acl_keys: if key not in tempauth_acl_keys:
return "Key %s not recognized" % json.dumps( return "Key %s not recognized" % json.dumps(key)
key).encode('ascii')
for key in tempauth_acl_keys: for key in tempauth_acl_keys:
if key not in result: if key not in result:
continue continue
if not isinstance(result[key], list): if not isinstance(result[key], list):
return "Value for key %s must be a list" % json.dumps( return "Value for key %s must be a list" % json.dumps(key)
key).encode('ascii')
for grantee in result[key]: for grantee in result[key]:
if not isinstance(grantee, six.string_types): if not isinstance(grantee, six.string_types):
return "Elements of %s list must be strings" % json.dumps( return "Elements of %s list must be strings" % json.dumps(
key).encode('ascii') key)
# Everything looks fine, no errors found # Everything looks fine, no errors found
internal_hdr = get_sys_meta_prefix('account') + 'core-access-control' internal_hdr = get_sys_meta_prefix('account') + 'core-access-control'
@@ -568,7 +569,7 @@ class TempAuth(object):
% account_user) % account_user)
return None return None
if account in user_groups and \ if wsgi_to_str(account) in user_groups and \
(req.method not in ('DELETE', 'PUT') or container): (req.method not in ('DELETE', 'PUT') or container):
# The user is admin for the account and is not trying to do an # The user is admin for the account and is not trying to do an
# account DELETE or PUT # account DELETE or PUT
@@ -744,7 +745,7 @@ class TempAuth(object):
return HTTPUnauthorized(request=req, return HTTPUnauthorized(request=req,
headers={'Www-Authenticate': auth}) headers={'Www-Authenticate': auth})
account2, user = user.split(':', 1) account2, user = user.split(':', 1)
if account != account2: if wsgi_to_str(account) != account2:
self.logger.increment('token_denied') self.logger.increment('token_denied')
auth = 'Swift realm="%s"' % account auth = 'Swift realm="%s"' % account
return HTTPUnauthorized(request=req, return HTTPUnauthorized(request=req,
@@ -800,7 +801,7 @@ class TempAuth(object):
cached_auth_data = memcache_client.get(memcache_token_key) cached_auth_data = memcache_client.get(memcache_token_key)
if cached_auth_data: if cached_auth_data:
expires, old_groups = cached_auth_data expires, old_groups = cached_auth_data
old_groups = [group.encode('utf8') old_groups = [group.encode('utf8') if six.PY2 else group
for group in old_groups.split(',')] for group in old_groups.split(',')]
new_groups = self._get_user_groups(account, account_user, new_groups = self._get_user_groups(account, account_user,
account_id) account_id)

View File

@@ -590,7 +590,8 @@ def _get_info_from_memcache(app, env, account, container=None):
memcache = getattr(app, 'memcache', None) or env.get('swift.cache') memcache = getattr(app, 'memcache', None) or env.get('swift.cache')
if memcache: if memcache:
info = memcache.get(cache_key) info = memcache.get(cache_key)
if info: if info and six.PY2:
# Get back to native strings
for key in info: for key in info:
if isinstance(info[key], six.text_type): if isinstance(info[key], six.text_type):
info[key] = info[key].encode("utf-8") info[key] = info[key].encode("utf-8")
@@ -598,6 +599,7 @@ def _get_info_from_memcache(app, env, account, container=None):
for subkey, value in info[key].items(): for subkey, value in info[key].items():
if isinstance(value, six.text_type): if isinstance(value, six.text_type):
info[key][subkey] = value.encode("utf-8") info[key][subkey] = value.encode("utf-8")
if info:
env.setdefault('swift.infocache', {})[cache_key] = info env.setdefault('swift.infocache', {})[cache_key] = info
return info return info
return None return None

View File

@@ -17,9 +17,10 @@
import json import json
import unittest import unittest
from contextlib import contextmanager from contextlib import contextmanager
from base64 import b64encode from base64 import b64encode as _b64encode
from time import time from time import time
import six
from six.moves.urllib.parse import quote, urlparse from six.moves.urllib.parse import quote, urlparse
from swift.common.middleware import tempauth as auth from swift.common.middleware import tempauth as auth
from swift.common.middleware.acl import format_acl from swift.common.middleware.acl import format_acl
@@ -29,6 +30,12 @@ from swift.common.utils import split_path
NO_CONTENT_RESP = (('204 No Content', {}, ''),) # mock server response NO_CONTENT_RESP = (('204 No Content', {}, ''),) # mock server response
def b64encode(str_or_bytes):
if not isinstance(str_or_bytes, bytes):
str_or_bytes = str_or_bytes.encode('utf8')
return _b64encode(str_or_bytes).decode('ascii')
class FakeMemcache(object): class FakeMemcache(object):
def __init__(self): def __init__(self):
@@ -41,7 +48,7 @@ class FakeMemcache(object):
if isinstance(value, (tuple, list)): if isinstance(value, (tuple, list)):
decoded = [] decoded = []
for elem in value: for elem in value:
if type(elem) == str: if isinstance(elem, bytes):
decoded.append(elem.decode('utf8')) decoded.append(elem.decode('utf8'))
else: else:
decoded.append(elem) decoded.append(elem)
@@ -972,9 +979,11 @@ class TestAuth(unittest.TestCase):
def test_successful_token_unicode_user(self): def test_successful_token_unicode_user(self):
app = FakeApp(iter(NO_CONTENT_RESP * 2)) app = FakeApp(iter(NO_CONTENT_RESP * 2))
ath = auth.filter_factory( conf = {u'user_t\u00e9st_t\u00e9ster': u'p\u00e1ss .admin'}
{u'user_t\u00e9st_t\u00e9ster'.encode('utf8'): if six.PY2:
u'p\u00e1ss .admin'.encode('utf8')})(app) conf = {k.encode('utf8'): v.encode('utf8')
for k, v in conf.items()}
ath = auth.filter_factory(conf)(app)
quoted_acct = quote(u'/v1/AUTH_t\u00e9st'.encode('utf8')) quoted_acct = quote(u'/v1/AUTH_t\u00e9st'.encode('utf8'))
memcache = FakeMemcache() memcache = FakeMemcache()
@@ -1009,7 +1018,8 @@ class TestAuth(unittest.TestCase):
# ...but it also works if you send the account raw # ...but it also works if you send the account raw
req = self._make_request( req = self._make_request(
u'/v1/AUTH_t\u00e9st', headers={'X-Auth-Token': auth_token}) u'/v1/AUTH_t\u00e9st'.encode('utf8'),
headers={'X-Auth-Token': auth_token})
req.environ['swift.cache'] = memcache req.environ['swift.cache'] = memcache
resp = req.get_response(ath) resp = req.get_response(ath)
self.assertEqual(204, resp.status_int) self.assertEqual(204, resp.status_int)
@@ -1395,13 +1405,13 @@ class TestAccountAcls(unittest.TestCase):
resp = req.get_response(test_auth) resp = req.get_response(test_auth)
self.assertEqual(resp.status_int, 204) self.assertEqual(resp.status_int, 204)
errmsg = 'X-Account-Access-Control invalid: %s' errmsg = b'X-Account-Access-Control invalid: %s'
# syntactically invalid acls get a 400 # syntactically invalid acls get a 400
update = {'x-account-access-control': bad_acl} update = {'x-account-access-control': bad_acl}
req = self._make_request(target, headers=dict(good_headers, **update)) req = self._make_request(target, headers=dict(good_headers, **update))
resp = req.get_response(test_auth) resp = req.get_response(test_auth)
self.assertEqual(resp.status_int, 400) self.assertEqual(resp.status_int, 400)
self.assertEqual(errmsg % "Syntax error", resp.body[:46]) self.assertEqual(errmsg % b"Syntax error", resp.body[:46])
# syntactically valid acls with bad keys also get a 400 # syntactically valid acls with bad keys also get a 400
update = {'x-account-access-control': wrong_acl} update = {'x-account-access-control': wrong_acl}
@@ -1409,7 +1419,7 @@ class TestAccountAcls(unittest.TestCase):
resp = req.get_response(test_auth) resp = req.get_response(test_auth)
self.assertEqual(resp.status_int, 400) self.assertEqual(resp.status_int, 400)
self.assertTrue(resp.body.startswith( self.assertTrue(resp.body.startswith(
errmsg % 'Key "other-auth-system" not recognized'), resp.body) errmsg % b'Key "other-auth-system" not recognized'), resp.body)
# and do something sane with crazy data # and do something sane with crazy data
update = {'x-account-access-control': u'{"\u1234": []}'.encode('utf8')} update = {'x-account-access-control': u'{"\u1234": []}'.encode('utf8')}
@@ -1417,7 +1427,7 @@ class TestAccountAcls(unittest.TestCase):
resp = req.get_response(test_auth) resp = req.get_response(test_auth)
self.assertEqual(resp.status_int, 400) self.assertEqual(resp.status_int, 400)
self.assertTrue(resp.body.startswith( self.assertTrue(resp.body.startswith(
errmsg % 'Key "\\u1234" not recognized'), resp.body) errmsg % b'Key "\\u1234" not recognized'), resp.body)
# acls with good keys but bad values also get a 400 # acls with good keys but bad values also get a 400
update = {'x-account-access-control': bad_value_acl} update = {'x-account-access-control': bad_value_acl}
@@ -1425,7 +1435,7 @@ class TestAccountAcls(unittest.TestCase):
resp = req.get_response(test_auth) resp = req.get_response(test_auth)
self.assertEqual(resp.status_int, 400) self.assertEqual(resp.status_int, 400)
self.assertTrue(resp.body.startswith( self.assertTrue(resp.body.startswith(
errmsg % 'Value for key "admin" must be a list'), resp.body) errmsg % b'Value for key "admin" must be a list'), resp.body)
# acls with non-string-types in list also get a 400 # acls with non-string-types in list also get a 400
update = {'x-account-access-control': bad_list_types} update = {'x-account-access-control': bad_list_types}
@@ -1433,7 +1443,7 @@ class TestAccountAcls(unittest.TestCase):
resp = req.get_response(test_auth) resp = req.get_response(test_auth)
self.assertEqual(resp.status_int, 400) self.assertEqual(resp.status_int, 400)
self.assertTrue(resp.body.startswith( self.assertTrue(resp.body.startswith(
errmsg % 'Elements of "read-only" list must be strings'), errmsg % b'Elements of "read-only" list must be strings'),
resp.body) resp.body)
# acls with wrong json structure also get a 400 # acls with wrong json structure also get a 400
@@ -1441,14 +1451,14 @@ class TestAccountAcls(unittest.TestCase):
req = self._make_request(target, headers=dict(good_headers, **update)) req = self._make_request(target, headers=dict(good_headers, **update))
resp = req.get_response(test_auth) resp = req.get_response(test_auth)
self.assertEqual(resp.status_int, 400) self.assertEqual(resp.status_int, 400)
self.assertEqual(errmsg % "Syntax error", resp.body[:46]) self.assertEqual(errmsg % b"Syntax error", resp.body[:46])
# acls with wrong json structure also get a 400 # acls with wrong json structure also get a 400
update = {'x-account-access-control': not_dict_acl2} update = {'x-account-access-control': not_dict_acl2}
req = self._make_request(target, headers=dict(good_headers, **update)) req = self._make_request(target, headers=dict(good_headers, **update))
resp = req.get_response(test_auth) resp = req.get_response(test_auth)
self.assertEqual(resp.status_int, 400) self.assertEqual(resp.status_int, 400)
self.assertEqual(errmsg % "Syntax error", resp.body[:46]) self.assertEqual(errmsg % b"Syntax error", resp.body[:46])
def test_acls_propagate_to_sysmeta(self): def test_acls_propagate_to_sysmeta(self):
test_auth = auth.filter_factory({'user_admin_user': 'testing'})( test_auth = auth.filter_factory({'user_admin_user': 'testing'})(

View File

@@ -44,6 +44,7 @@ commands =
test/unit/common/middleware/test_list_endpoints.py \ test/unit/common/middleware/test_list_endpoints.py \
test/unit/common/middleware/test_listing_formats.py \ test/unit/common/middleware/test_listing_formats.py \
test/unit/common/middleware/test_proxy_logging.py \ test/unit/common/middleware/test_proxy_logging.py \
test/unit/common/middleware/test_tempauth.py \
test/unit/common/ring \ test/unit/common/ring \
test/unit/common/test_base_storage_server.py \ test/unit/common/test_base_storage_server.py \
test/unit/common/test_bufferedhttp.py \ test/unit/common/test_bufferedhttp.py \