Allow user set X-Auth-Token-Lifetime, with limits
* New conf value of max_token_life, existing token_life conf value is now just the default token life. * When a user requests a token, they can send an X-Auth-Token-Lifetime header with the number of seconds they'd like the token to be valid for. This will be capped to max_token_life. * Response to getting a token has new X-Auth-Token-Expires header that is the number of seconds the token is valid for.
This commit is contained in:
parent
ce1072bc97
commit
36a156bc52
@ -41,8 +41,10 @@ use = egg:swauth#swauth
|
||||
# useful when a load balancer url should be used by users, but swauth itself is
|
||||
# behind the load balancer. Example:
|
||||
# default_swift_cluster = local#https://public.com:8080/v1#http://private.com:8080/v1
|
||||
# Number of seconds a newly issued token should be valid for.
|
||||
# Number of seconds a newly issued token should be valid for, by default.
|
||||
# token_life = 86400
|
||||
# Maximum number of seconds a newly issued token can be valid for.
|
||||
# max_token_life = <same as token_life>
|
||||
# Specifies how the user key is stored. The default is 'plaintext', leaving the
|
||||
# key unsecured but available for key-signing features if such are ever added.
|
||||
# An alternative is 'sha1' which stores only a one-way hash of the key leaving
|
||||
|
@ -122,6 +122,7 @@ class Swauth(object):
|
||||
except Exception:
|
||||
pass
|
||||
self.token_life = int(conf.get('token_life', 86400))
|
||||
self.max_token_life = int(conf.get('max_token_life', self.token_life))
|
||||
self.timeout = int(conf.get('node_timeout', 10))
|
||||
self.itoken = None
|
||||
self.itoken_expires = None
|
||||
@ -1203,6 +1204,7 @@ class Swauth(object):
|
||||
return HTTPUnauthorized(request=req)
|
||||
# See if a token already exists and hasn't expired
|
||||
token = None
|
||||
expires = None
|
||||
candidate_token = resp.headers.get('x-object-meta-auth-token')
|
||||
if candidate_token:
|
||||
path = quote('/v1/%s/.token_%s/%s' %
|
||||
@ -1219,6 +1221,7 @@ class Swauth(object):
|
||||
token_detail = json.loads(resp.body)
|
||||
if token_detail['expires'] > time():
|
||||
token = candidate_token
|
||||
expires = token_detail['expires']
|
||||
else:
|
||||
delete_token = True
|
||||
elif resp.status_int != 404:
|
||||
@ -1245,19 +1248,20 @@ class Swauth(object):
|
||||
# Save token info
|
||||
path = quote('/v1/%s/.token_%s/%s' %
|
||||
(self.auth_account, token[-1], token))
|
||||
|
||||
if self.conf.get('user_set_tokenlifetime', False):
|
||||
try:
|
||||
self.token_life = int(req.headers.get('x-auth-token-lifetime'))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
try:
|
||||
token_life = min(
|
||||
int(req.headers.get('x-auth-token-lifetime',
|
||||
self.token_life)),
|
||||
self.max_token_life)
|
||||
except ValueError:
|
||||
token_life = self.token_life
|
||||
expires = int(time() + token_life)
|
||||
resp = self.make_pre_authed_request(
|
||||
req.environ, 'PUT', path,
|
||||
json.dumps({'account': account, 'user': user,
|
||||
'account_id': account_id,
|
||||
'groups': user_detail['groups'],
|
||||
'expires': time() + self.token_life})).get_response(self.app)
|
||||
'expires': expires})).get_response(self.app)
|
||||
if resp.status_int // 100 != 2:
|
||||
raise Exception('Could not create new token: %s %s' %
|
||||
(path, resp.status))
|
||||
@ -1281,6 +1285,7 @@ class Swauth(object):
|
||||
url = detail['storage'][detail['storage']['default']]
|
||||
return Response(request=req, body=resp.body,
|
||||
headers={'x-auth-token': token, 'x-storage-token': token,
|
||||
'x-auth-token-expires': str(int(expires - time())),
|
||||
'x-storage-url': url})
|
||||
|
||||
def handle_validate_token(self, req):
|
||||
|
@ -27,6 +27,10 @@ from swauth import middleware as auth
|
||||
from swauth.authtypes import MAX_TOKEN_LENGTH
|
||||
|
||||
|
||||
DEFAULT_TOKEN_LIFE = 86400
|
||||
MAX_TOKEN_LIFE = 100000
|
||||
|
||||
|
||||
class FakeMemcache(object):
|
||||
|
||||
def __init__(self):
|
||||
@ -110,7 +114,10 @@ class TestAuth(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.test_auth = \
|
||||
auth.filter_factory({'super_admin_key': 'supertest'})(FakeApp())
|
||||
auth.filter_factory({
|
||||
'super_admin_key': 'supertest',
|
||||
'token_life': str(DEFAULT_TOKEN_LIFE),
|
||||
'max_token_life': str(MAX_TOKEN_LIFE)})(FakeApp())
|
||||
|
||||
def test_super_admin_key_not_required(self):
|
||||
auth.filter_factory({})(FakeApp())
|
||||
@ -699,6 +706,80 @@ class TestAuth(unittest.TestCase):
|
||||
"local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})
|
||||
self.assertEquals(self.test_auth.app.calls, 5)
|
||||
|
||||
def test_get_token_success_v1_0_with_user_token_life(self):
|
||||
self.test_auth.app = FakeApp(iter([
|
||||
# GET of user object
|
||||
('200 Ok', {},
|
||||
json.dumps({"auth": "plaintext:key",
|
||||
"groups": [{'name': "act:usr"}, {'name': "act"},
|
||||
{'name': ".admin"}]})),
|
||||
# GET of account
|
||||
('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''),
|
||||
# PUT of new token
|
||||
('201 Created', {}, ''),
|
||||
# POST of token to user object
|
||||
('204 No Content', {}, ''),
|
||||
# GET of services object
|
||||
('200 Ok', {}, json.dumps({"storage": {"default": "local",
|
||||
"local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))]))
|
||||
resp = Request.blank('/auth/v1.0',
|
||||
headers={'X-Auth-User': 'act:usr',
|
||||
'X-Auth-Key': 'key',
|
||||
'X-Auth-Token-Lifetime': 10}).get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
left = int(resp.headers['x-auth-token-expires'])
|
||||
self.assertTrue(left > 0, '%d > 0' % left)
|
||||
self.assertTrue(left <= 10, '%d <= 10' % left)
|
||||
self.assert_(resp.headers.get('x-auth-token',
|
||||
'').startswith('AUTH_tk'), resp.headers.get('x-auth-token'))
|
||||
self.assertEquals(resp.headers.get('x-auth-token'),
|
||||
resp.headers.get('x-storage-token'))
|
||||
self.assertEquals(resp.headers.get('x-storage-url'),
|
||||
'http://127.0.0.1:8080/v1/AUTH_cfa')
|
||||
self.assertEquals(json.loads(resp.body),
|
||||
{"storage": {"default": "local",
|
||||
"local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})
|
||||
self.assertEquals(self.test_auth.app.calls, 5)
|
||||
|
||||
def test_get_token_success_v1_0_with_user_token_life_past_max(self):
|
||||
self.test_auth.app = FakeApp(iter([
|
||||
# GET of user object
|
||||
('200 Ok', {},
|
||||
json.dumps({"auth": "plaintext:key",
|
||||
"groups": [{'name': "act:usr"}, {'name': "act"},
|
||||
{'name': ".admin"}]})),
|
||||
# GET of account
|
||||
('204 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''),
|
||||
# PUT of new token
|
||||
('201 Created', {}, ''),
|
||||
# POST of token to user object
|
||||
('204 No Content', {}, ''),
|
||||
# GET of services object
|
||||
('200 Ok', {}, json.dumps({"storage": {"default": "local",
|
||||
"local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))]))
|
||||
req = Request.blank(
|
||||
'/auth/v1.0',
|
||||
headers={'X-Auth-User': 'act:usr',
|
||||
'X-Auth-Key': 'key',
|
||||
'X-Auth-Token-Lifetime': MAX_TOKEN_LIFE * 10})
|
||||
resp = req.get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
left = int(resp.headers['x-auth-token-expires'])
|
||||
self.assertTrue(left > DEFAULT_TOKEN_LIFE,
|
||||
'%d > %d' % (left, DEFAULT_TOKEN_LIFE))
|
||||
self.assertTrue(left <= MAX_TOKEN_LIFE,
|
||||
'%d <= %d' % (left, MAX_TOKEN_LIFE))
|
||||
self.assert_(resp.headers.get('x-auth-token',
|
||||
'').startswith('AUTH_tk'), resp.headers.get('x-auth-token'))
|
||||
self.assertEquals(resp.headers.get('x-auth-token'),
|
||||
resp.headers.get('x-storage-token'))
|
||||
self.assertEquals(resp.headers.get('x-storage-url'),
|
||||
'http://127.0.0.1:8080/v1/AUTH_cfa')
|
||||
self.assertEquals(json.loads(resp.body),
|
||||
{"storage": {"default": "local",
|
||||
"local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})
|
||||
self.assertEquals(self.test_auth.app.calls, 5)
|
||||
|
||||
def test_get_token_success_v1_act_auth(self):
|
||||
self.test_auth.app = FakeApp(iter([
|
||||
# GET of user object
|
||||
|
Loading…
x
Reference in New Issue
Block a user