Got rid of inter-reseller ACLs. Enforce ACLs to only work within a reseller space. Updated docs and tests. We can expand to inter-reseller in the future with ACLs like .x:RESELLER_group

This commit is contained in:
gholt 2010-09-09 10:24:25 -07:00
parent d2ec027e22
commit a5df15005b
8 changed files with 71 additions and 72 deletions

View File

@ -35,14 +35,15 @@ string. If the user does not have admin access to the account, the third group
will be omitted. will be omitted.
It is highly recommended that authentication server implementers prefix their It is highly recommended that authentication server implementers prefix their
group names and tokens with a configurable reseller prefix (`AUTH_` by default tokens and Swift storage accounts they create with a configurable reseller
with the included DevAuth). This prefix will allow deconflicting with other prefix (`AUTH_` by default with the included DevAuth). This prefix will allow
authentication servers that might be using the same Swift cluster. deconflicting with other authentication servers that might be using the same
Swift cluster. Otherwise, the Swift cluster will have to try all the resellers
until one validates a token or all fail.
The only other restriction is that no group name should begin with a period '.' A restriction with group names is that no group name should begin with a period
as that is reserved for internal Swift use (such as the .r for referrer '.' 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 designations as you'll see later).
prefix is in use and does not begin with a period.
Example Authentication with DevAuth: Example Authentication with DevAuth:
@ -54,10 +55,11 @@ Example Authentication with DevAuth:
it matches the "tester" user within the "test" account for the storage it matches the "tester" user within the "test" account for the storage
account "AUTH_storage_xyz". account "AUTH_storage_xyz".
* The external DevAuth server responds with "X-Auth-Groups: * The external DevAuth server responds with "X-Auth-Groups:
AUTH_test:tester,AUTH_test,AUTH_storage_xyz" test:tester,test,AUTH_storage_xyz"
* Now this user will have full access (via authorization procedures later) * Now this user will have full access (via authorization procedures later)
to the AUTH_storage_xyz Swift storage account and access to anything with to the AUTH_storage_xyz Swift storage account and access to other storage
ACLs specifying at least one of those three groups returned. accounts with the same `AUTH_` reseller prefix and has an ACL 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

View File

@ -37,10 +37,12 @@ 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 # The reseller prefix will verify a token begins with this prefix before even
# before even attempting to validate it with the external reseller. Usefull if # attempting to validate it with the external authentication server. Also, with
# multiple auth systems are in use for one Swift cluster. # authorization, only Swift storage accounts with this prefix will be
# reseller_prefix = # authorized by this middleware. Useful if multiple auth systems are in use for
# one Swift cluster.
# reseller_prefix = AUTH
# ip = 127.0.0.1 # ip = 127.0.0.1
# port = 11000 # port = 11000
# ssl = false # ssl = false

View File

@ -361,9 +361,7 @@ class AuthController(object):
validation = self.validate_token(token) validation = self.validate_token(token)
if not validation: if not validation:
return HTTPNotFound() return HTTPNotFound()
groups = [ groups = ['%s:%s' % (validation[1], validation[2]), validation[1]]
'%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],

View File

@ -29,7 +29,7 @@ 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() self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip()
if self.reseller_prefix and self.reseller_prefix[-1] != '_': if self.reseller_prefix and self.reseller_prefix[-1] != '_':
self.reseller_prefix += '_' self.reseller_prefix += '_'
self.auth_host = conf.get('ip', '127.0.0.1') self.auth_host = conf.get('ip', '127.0.0.1')
@ -83,7 +83,7 @@ class DevAuth(object):
WSGI response callable if not. WSGI response callable if not.
""" """
version, account, container, obj = split_path(req.path, 1, 4, True) version, account, container, obj = split_path(req.path, 1, 4, True)
if not account: if not account or not account.startswith(self.reseller_prefix):
return self.denied_response(req) return self.denied_response(req)
if req.remote_user and account in req.remote_user.split(','): if req.remote_user and account in req.remote_user.split(','):
return None return None

View File

@ -2,7 +2,6 @@
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,9 +10,6 @@ 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]
@ -35,9 +32,6 @@ 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_auth_prefix, swift_test_user swift_test_user
class TestContainer(unittest.TestCase): class TestContainer(unittest.TestCase):
@ -377,10 +377,9 @@ 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()
@ -446,9 +445,8 @@ 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,9 +483,7 @@ 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-Auth-Token': token, 'X-Container-Read': swift_test_user[2]})
'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()
@ -508,8 +504,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-Write': '%s%s' % 'X-Container-Write': swift_test_user[2]})
(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

@ -109,7 +109,7 @@ class TestAuth(unittest.TestCase):
try: try:
auth.http_connect = mock_http_connect(404) auth.http_connect = mock_http_connect(404)
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET', result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': FakeMemcache()}, 'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': FakeMemcache()},
lambda x, y: None)) lambda x, y: None))
self.assert_(result.startswith('401'), result) self.assert_(result.startswith('401'), result)
finally: finally:
@ -119,9 +119,9 @@ class TestAuth(unittest.TestCase):
old_http_connect = auth.http_connect old_http_connect = auth.http_connect
try: try:
auth.http_connect = mock_http_connect(204, auth.http_connect = mock_http_connect(204,
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,cfa'}) {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET', result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': FakeMemcache()}, 'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': FakeMemcache()},
lambda x, y: None)) lambda x, y: None))
self.assert_(result.startswith('204'), result) self.assert_(result.startswith('204'), result)
finally: finally:
@ -132,15 +132,15 @@ class TestAuth(unittest.TestCase):
try: try:
fake_memcache = FakeMemcache() fake_memcache = FakeMemcache()
auth.http_connect = mock_http_connect(204, auth.http_connect = mock_http_connect(204,
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,cfa'}) {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET', result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': fake_memcache}, 'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': fake_memcache},
lambda x, y: None)) lambda x, y: None))
self.assert_(result.startswith('204'), result) self.assert_(result.startswith('204'), result)
auth.http_connect = mock_http_connect(404) auth.http_connect = mock_http_connect(404)
# Should still be in memcache # Should still be in memcache
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET', result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': fake_memcache}, 'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': fake_memcache},
lambda x, y: None)) lambda x, y: None))
self.assert_(result.startswith('204'), result) self.assert_(result.startswith('204'), result)
finally: finally:
@ -151,15 +151,15 @@ class TestAuth(unittest.TestCase):
try: try:
fake_memcache = FakeMemcache() fake_memcache = FakeMemcache()
auth.http_connect = mock_http_connect(204, auth.http_connect = mock_http_connect(204,
{'x-auth-ttl': '0', 'x-auth-groups': 'act:usr,act,cfa'}) {'x-auth-ttl': '0', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET', result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': fake_memcache}, 'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': fake_memcache},
lambda x, y: None)) lambda x, y: None))
self.assert_(result.startswith('204'), result) self.assert_(result.startswith('204'), result)
auth.http_connect = mock_http_connect(404) auth.http_connect = mock_http_connect(404)
# Should still be in memcache, but expired # Should still be in memcache, but expired
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET', result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': fake_memcache}, 'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': fake_memcache},
lambda x, y: None)) lambda x, y: None))
self.assert_(result.startswith('401'), result) self.assert_(result.startswith('401'), result)
finally: finally:
@ -169,12 +169,12 @@ class TestAuth(unittest.TestCase):
old_http_connect = auth.http_connect old_http_connect = auth.http_connect
try: try:
auth.http_connect = mock_http_connect(204, auth.http_connect = mock_http_connect(204,
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,cfa'}) {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
req = Request.blank('/v/a/c/o', headers={'x-auth-token': 't'}) req = Request.blank('/v/a/c/o', headers={'x-auth-token': 'AUTH_t'})
req.environ['swift.cache'] = FakeMemcache() req.environ['swift.cache'] = FakeMemcache()
result = ''.join(self.test_auth(req.environ, start_response)) result = ''.join(self.test_auth(req.environ, start_response))
self.assert_(result.startswith('204'), result) self.assert_(result.startswith('204'), result)
self.assertEquals(req.remote_user, 'act:usr,act,cfa') self.assertEquals(req.remote_user, 'act:usr,act,AUTH_cfa')
finally: finally:
auth.http_connect = old_http_connect auth.http_connect = old_http_connect
@ -182,7 +182,7 @@ class TestAuth(unittest.TestCase):
old_http_connect = auth.http_connect old_http_connect = auth.http_connect
try: try:
auth.http_connect = mock_http_connect(204, auth.http_connect = mock_http_connect(204,
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,cfa'}) {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
req = Request.blank('/v/a/c/o') req = Request.blank('/v/a/c/o')
req.environ['swift.cache'] = FakeMemcache() req.environ['swift.cache'] = FakeMemcache()
result = ''.join(self.test_auth(req.environ, start_response)) result = ''.join(self.test_auth(req.environ, start_response))
@ -195,12 +195,13 @@ class TestAuth(unittest.TestCase):
old_http_connect = auth.http_connect old_http_connect = auth.http_connect
try: try:
auth.http_connect = mock_http_connect(204, auth.http_connect = mock_http_connect(204,
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,cfa'}) {'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
req = Request.blank('/v/a/c/o', headers={'x-storage-token': 't'}) req = Request.blank('/v/a/c/o',
headers={'x-storage-token': 'AUTH_t'})
req.environ['swift.cache'] = FakeMemcache() req.environ['swift.cache'] = FakeMemcache()
result = ''.join(self.test_auth(req.environ, start_response)) result = ''.join(self.test_auth(req.environ, start_response))
self.assert_(result.startswith('204'), result) self.assert_(result.startswith('204'), result)
self.assertEquals(req.remote_user, 'act:usr,act,cfa') self.assertEquals(req.remote_user, 'act:usr,act,AUTH_cfa')
finally: finally:
auth.http_connect = old_http_connect auth.http_connect = old_http_connect
@ -209,73 +210,81 @@ class TestAuth(unittest.TestCase):
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('/badpath') req = Request.blank('/badpath')
req.remote_user = 'act:usr,act,cfa' req.remote_user = 'act:usr,act,AUTH_cfa'
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)
def test_authorize_account_access(self): def test_authorize_account_access(self):
req = Request.blank('/v1/cfa') req = Request.blank('/v1/AUTH_cfa')
req.remote_user = 'act:usr,act,cfa' req.remote_user = 'act:usr,act,AUTH_cfa'
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/AUTH_cfa')
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
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)
def test_authorize_acl_group_access(self): def test_authorize_acl_group_access(self):
req = Request.blank('/v1/cfa') req = Request.blank('/v1/AUTH_cfa')
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
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/AUTH_cfa')
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
req.acl = 'act' req.acl = 'act'
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/AUTH_cfa')
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
req.acl = 'act:usr' req.acl = 'act:usr'
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/AUTH_cfa')
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
req.acl = 'act2' req.acl = 'act2'
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/AUTH_cfa')
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
req.acl = 'act:usr2' req.acl = 'act:usr2'
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)
def test_deny_cross_reseller(self):
# Tests that cross-reseller is denied, even if ACLs/group names match
req = Request.blank('/v1/OTHER_cfa')
req.remote_user = 'act:usr,act,AUTH_cfa'
req.acl = 'act'
resp = str(self.test_auth.authorize(req))
self.assert_(resp.startswith('403'), resp)
def test_authorize_acl_referrer_access(self): def test_authorize_acl_referrer_access(self):
req = Request.blank('/v1/cfa') req = Request.blank('/v1/AUTH_cfa')
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
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/AUTH_cfa')
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
req.acl = '.r:*' 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/AUTH_cfa')
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
req.acl = '.r:.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/AUTH_cfa')
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
req.referer = 'http://www.example.com/index.html' req.referer = 'http://www.example.com/index.html'
req.acl = '.r:.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/AUTH_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/AUTH_cfa')
req.acl = '.r:*' 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/AUTH_cfa')
req.acl = '.r:.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/AUTH_cfa')
req.referer = 'http://www.example.com/index.html' req.referer = 'http://www.example.com/index.html'
req.acl = '.r:.example.com' req.acl = '.r:.example.com'
self.assertEquals(self.test_auth.authorize(req), None) self.assertEquals(self.test_auth.authorize(req), None)