summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2012-02-14 22:26:49 +0000
committerGerrit Code Review <review@openstack.org>2012-02-14 22:26:49 +0000
commitfa9b949b5947dd1163e5321bef8a279fb90faa93 (patch)
treedec9b6a484c28279dfeae554a4c34f916a152537
parent988a5850d1f1ea7f2db4b179a2fab22a878e7bfe (diff)
parent71436dbf188b3ff1c576fcd54b992530aac98b6c (diff)
downloadkeystone-fa9b949b5947dd1163e5321bef8a279fb90faa93.tar.gz
keystone-fa9b949b5947dd1163e5321bef8a279fb90faa93.tar.xz
keystone-fa9b949b5947dd1163e5321bef8a279fb90faa93.zip
Merge "Add token expiration" into redux
-rw-r--r--etc/keystone.conf3
-rw-r--r--keystone/common/sql/core.py1
-rw-r--r--keystone/common/sql/migrate_repo/versions/001_add_initial_tables.py1
-rw-r--r--keystone/common/utils.py24
-rw-r--r--keystone/contrib/ec2/core.py3
-rw-r--r--keystone/identity/backends/sql.py4
-rw-r--r--keystone/service.py12
-rw-r--r--keystone/token/backends/kvs.py18
-rw-r--r--keystone/token/backends/memcache.py13
-rw-r--r--keystone/token/backends/sql.py33
-rw-r--r--keystone/token/core.py12
-rw-r--r--tests/backend_sql.conf3
-rw-r--r--tests/test_backend.py22
-rw-r--r--tests/test_backend_memcache.py14
-rw-r--r--tests/test_keystoneclient_sql.py1
-rw-r--r--tests/test_utils.py9
16 files changed, 142 insertions, 31 deletions
diff --git a/etc/keystone.conf b/etc/keystone.conf
index fde13004..dd78b4a9 100644
--- a/etc/keystone.conf
+++ b/etc/keystone.conf
@@ -32,6 +32,9 @@ template_file = ./etc/default_catalog.templates
[token]
driver = keystone.token.backends.kvs.Token
+# Amount of time a token should remain valid (in seconds)
+expiration = 86400
+
[policy]
driver = keystone.policy.backends.simple.SimpleMatch
diff --git a/keystone/common/sql/core.py b/keystone/common/sql/core.py
index 2bd24f48..cb621865 100644
--- a/keystone/common/sql/core.py
+++ b/keystone/common/sql/core.py
@@ -26,6 +26,7 @@ ModelBase = declarative.declarative_base()
Column = sql.Column
String = sql.String
ForeignKey = sql.ForeignKey
+DateTime = sql.DateTime
# Special Fields
diff --git a/keystone/common/sql/migrate_repo/versions/001_add_initial_tables.py b/keystone/common/sql/migrate_repo/versions/001_add_initial_tables.py
index 5875817e..ae54b476 100644
--- a/keystone/common/sql/migrate_repo/versions/001_add_initial_tables.py
+++ b/keystone/common/sql/migrate_repo/versions/001_add_initial_tables.py
@@ -5,6 +5,7 @@ from keystone.common import sql
# these are to make sure all the models we care about are defined
import keystone.identity.backends.sql
+import keystone.token.backends.sql
import keystone.contrib.ec2.backends.sql
diff --git a/keystone/common/utils.py b/keystone/common/utils.py
index b5269bed..96e595bb 100644
--- a/keystone/common/utils.py
+++ b/keystone/common/utils.py
@@ -23,6 +23,7 @@ import hmac
import json
import subprocess
import sys
+import time
import urllib
import passlib.hash
@@ -35,6 +36,9 @@ CONF = config.CONF
config.register_int('crypt_strength', default=40000)
+ISO_TIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
+
+
def import_class(import_str):
"""Returns a class from a string including module and class."""
mod_str, _sep, class_str = import_str.rpartition('.')
@@ -201,3 +205,23 @@ def check_output(*popenargs, **kwargs):
def git(*args):
return check_output(['git'] + list(args))
+
+
+def isotime(dt_obj):
+ """Format datetime object as ISO compliant string.
+
+ :param dt_obj: datetime.datetime object
+ :returns: string representation of datetime object
+
+ """
+ return dt_obj.strftime(ISO_TIME_FORMAT)
+
+
+def unixtime(dt_obj):
+ """Format datetime object as unix timestamp
+
+ :param dt_obj: datetime.datetime object
+ :returns: float
+
+ """
+ return time.mktime(dt_obj.utctimetuple())
diff --git a/keystone/contrib/ec2/core.py b/keystone/contrib/ec2/core.py
index c8ad4425..08a286cc 100644
--- a/keystone/contrib/ec2/core.py
+++ b/keystone/contrib/ec2/core.py
@@ -163,8 +163,7 @@ class Ec2Controller(wsgi.Application):
metadata=metadata_ref)
token_ref = self.token_api.create_token(
- context, token_id, dict(expires='',
- id=token_id,
+ context, token_id, dict(id=token_id,
user=user_ref,
tenant=tenant_ref,
metadata=metadata_ref))
diff --git a/keystone/identity/backends/sql.py b/keystone/identity/backends/sql.py
index 8a3db422..4918a942 100644
--- a/keystone/identity/backends/sql.py
+++ b/keystone/identity/backends/sql.py
@@ -1,5 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+import copy
+
from keystone import identity
from keystone.common import sql
from keystone.common import utils
@@ -64,7 +66,7 @@ class Tenant(sql.ModelBase, sql.DictBase):
return cls(**tenant_dict)
def to_dict(self):
- extra_copy = self.extra.copy()
+ extra_copy = copy.deepcopy(self.extra)
extra_copy['id'] = self.id
extra_copy['name'] = self.name
return extra_copy
diff --git a/keystone/service.py b/keystone/service.py
index 5be853c1..2c361e40 100644
--- a/keystone/service.py
+++ b/keystone/service.py
@@ -12,6 +12,7 @@ from keystone import identity
from keystone import policy
from keystone import token
from keystone.common import logging
+from keystone.common import utils
from keystone.common import wsgi
@@ -226,8 +227,7 @@ class TokenController(wsgi.Application):
raise webob.exc.HTTPForbidden(e.message)
token_ref = self.token_api.create_token(
- context, token_id, dict(expires='',
- id=token_id,
+ context, token_id, dict(id=token_id,
user=user_ref,
tenant=tenant_ref,
metadata=metadata_ref))
@@ -283,8 +283,7 @@ class TokenController(wsgi.Application):
catalog_ref = {}
token_ref = self.token_api.create_token(
- context, token_id, dict(expires='',
- id=token_id,
+ context, token_id, dict(id=token_id,
user=user_ref,
tenant=tenant_ref,
metadata=metadata_ref))
@@ -351,8 +350,11 @@ class TokenController(wsgi.Application):
def _format_token(self, token_ref, roles_ref):
user_ref = token_ref['user']
metadata_ref = token_ref['metadata']
+ expires = token_ref['expires']
+ if expires is not None:
+ expires = utils.isotime(expires)
o = {'access': {'token': {'id': token_ref['id'],
- 'expires': token_ref['expires']
+ 'expires': expires,
},
'user': {'id': user_ref['id'],
'name': user_ref['name'],
diff --git a/keystone/token/backends/kvs.py b/keystone/token/backends/kvs.py
index 84604ac5..990e107b 100644
--- a/keystone/token/backends/kvs.py
+++ b/keystone/token/backends/kvs.py
@@ -1,5 +1,8 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+import copy
+import datetime
+
from keystone.common import kvs
from keystone import exception
from keystone import token
@@ -8,14 +11,19 @@ from keystone import token
class Token(kvs.Base, token.Driver):
# Public interface
def get_token(self, token_id):
- try:
- return self.db['token-%s' % token_id]
- except KeyError:
+ token = self.db.get('token-%s' % token_id)
+ if (token and (token['expires'] is None
+ or token['expires'] > datetime.datetime.now())):
+ return token
+ else:
raise exception.TokenNotFound(token_id=token_id)
def create_token(self, token_id, data):
- self.db.set('token-%s' % token_id, data)
- return data
+ data_copy = copy.deepcopy(data)
+ if 'expires' not in data:
+ data_copy['expires'] = self._get_default_expire_time()
+ self.db.set('token-%s' % token_id, data_copy)
+ return copy.deepcopy(data_copy)
def delete_token(self, token_id):
try:
diff --git a/keystone/token/backends/memcache.py b/keystone/token/backends/memcache.py
index 8dfb3731..b9c2cbe1 100644
--- a/keystone/token/backends/memcache.py
+++ b/keystone/token/backends/memcache.py
@@ -1,12 +1,14 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
from __future__ import absolute_import
+import copy
import memcache
from keystone import config
from keystone import exception
from keystone import token
+from keystone.common import utils
CONF = config.CONF
@@ -38,9 +40,16 @@ class Token(token.Driver):
return token
def create_token(self, token_id, data):
+ data_copy = copy.deepcopy(data)
ptk = self._prefix_token_id(token_id)
- self.client.set(ptk, data)
- return data
+ if 'expires' not in data_copy:
+ data_copy['expires'] = self._get_default_expire_time()
+ kwargs = {}
+ if data_copy['expires'] is not None:
+ expires_ts = utils.unixtime(data_copy['expires'])
+ kwargs['time'] = expires_ts
+ self.client.set(ptk, data_copy, **kwargs)
+ return copy.deepcopy(data_copy)
def delete_token(self, token_id):
# Test for existence
diff --git a/keystone/token/backends/sql.py b/keystone/token/backends/sql.py
index 090a3600..b57f32a8 100644
--- a/keystone/token/backends/sql.py
+++ b/keystone/token/backends/sql.py
@@ -1,5 +1,8 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+import copy
+import datetime
+
from keystone.common import sql
from keystone import exception
from keystone import token
@@ -8,21 +11,24 @@ from keystone import token
class TokenModel(sql.ModelBase, sql.DictBase):
__tablename__ = 'token'
id = sql.Column(sql.String(64), primary_key=True)
+ expires = sql.Column(sql.DateTime(), default=None)
extra = sql.Column(sql.JsonBlob())
@classmethod
def from_dict(cls, token_dict):
# shove any non-indexed properties into extra
+ extra = copy.deepcopy(token_dict)
data = {}
- token_dict_copy = token_dict.copy()
- data['id'] = token_dict_copy.pop('id')
- data['extra'] = token_dict_copy
+ for k in ('id', 'expires'):
+ data[k] = extra.pop(k, None)
+ data['extra'] = extra
return cls(**data)
def to_dict(self):
- extra_copy = self.extra.copy()
- extra_copy['id'] = self.id
- return extra_copy
+ out = copy.deepcopy(self.extra)
+ out['id'] = self.id
+ out['expires'] = self.expires
+ return out
class Token(sql.Base, token.Driver):
@@ -30,15 +36,22 @@ class Token(sql.Base, token.Driver):
def get_token(self, token_id):
session = self.get_session()
token_ref = session.query(TokenModel).filter_by(id=token_id).first()
- if not token_ref:
+ now = datetime.datetime.now()
+ if token_ref and (not token_ref.expires or now < token_ref.expires):
+ return token_ref.to_dict()
+ else:
raise exception.TokenNotFound(token_id=token_id)
- return token_ref.to_dict()
def create_token(self, token_id, data):
- data['id'] = token_id
+ data_copy = copy.deepcopy(data)
+ if 'expires' not in data_copy:
+ data_copy['expires'] = self._get_default_expire_time()
+
+ token_ref = TokenModel.from_dict(data_copy)
+ token_ref.id = token_id
+
session = self.get_session()
with session.begin():
- token_ref = TokenModel.from_dict(data)
session.add(token_ref)
session.flush()
return token_ref.to_dict()
diff --git a/keystone/token/core.py b/keystone/token/core.py
index 7183c179..8c816efe 100644
--- a/keystone/token/core.py
+++ b/keystone/token/core.py
@@ -2,11 +2,14 @@
"""Main entry point into the Token service."""
+import datetime
+
from keystone import config
from keystone.common import manager
CONF = config.CONF
+config.register_int('expiration', group='token', default=86400)
class Manager(manager.Manager):
@@ -68,3 +71,12 @@ class Driver(object):
"""
raise NotImplementedError()
+
+ def _get_default_expire_time(self):
+ """Determine when a token should expire based on the config.
+
+ :returns: datetime.datetime object
+
+ """
+ expire_delta = datetime.timedelta(seconds=CONF.token.expiration)
+ return datetime.datetime.now() + expire_delta
diff --git a/tests/backend_sql.conf b/tests/backend_sql.conf
index 0fec0488..cb246194 100644
--- a/tests/backend_sql.conf
+++ b/tests/backend_sql.conf
@@ -8,5 +8,8 @@ pool_timeout = 200
[identity]
driver = keystone.identity.backends.sql.Identity
+[token]
+driver = keystone.token.backends.sql.Token
+
[ec2]
driver = keystone.contrib.ec2.backends.sql.Ec2
diff --git a/tests/test_backend.py b/tests/test_backend.py
index 9dc949da..bbc651ef 100644
--- a/tests/test_backend.py
+++ b/tests/test_backend.py
@@ -1,5 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+import datetime
import uuid
from keystone import exception
@@ -211,9 +212,13 @@ class TokenTests(object):
token_id = uuid.uuid4().hex
data = {'id': token_id, 'a': 'b'}
data_ref = self.token_api.create_token(token_id, data)
+ expires = data_ref.pop('expires')
+ self.assertTrue(isinstance(expires, datetime.datetime))
self.assertDictEquals(data_ref, data)
new_data_ref = self.token_api.get_token(token_id)
+ expires = new_data_ref.pop('expires')
+ self.assertTrue(isinstance(expires, datetime.datetime))
self.assertEquals(new_data_ref, data)
self.token_api.delete_token(token_id)
@@ -221,3 +226,20 @@ class TokenTests(object):
self.token_api.delete_token, token_id)
self.assertRaises(exception.TokenNotFound,
self.token_api.get_token, token_id)
+
+ def test_expired_token(self):
+ token_id = uuid.uuid4().hex
+ expire_time = datetime.datetime.now() - datetime.timedelta(minutes=1)
+ data = {'id': token_id, 'a': 'b', 'expires': expire_time}
+ data_ref = self.token_api.create_token(token_id, data)
+ self.assertDictEquals(data_ref, data)
+ self.assertRaises(exception.TokenNotFound,
+ self.token_api.get_token, token_id)
+
+ def test_null_expires_token(self):
+ token_id = uuid.uuid4().hex
+ data = {'id': token_id, 'a': 'b', 'expires': None}
+ data_ref = self.token_api.create_token(token_id, data)
+ self.assertDictEquals(data_ref, data)
+ new_data_ref = self.token_api.get_token(token_id)
+ self.assertEqual(data_ref, new_data_ref)
diff --git a/tests/test_backend_memcache.py b/tests/test_backend_memcache.py
index b320b1f9..05ef2107 100644
--- a/tests/test_backend_memcache.py
+++ b/tests/test_backend_memcache.py
@@ -1,5 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+import datetime
+import time
import uuid
import memcache
@@ -25,15 +27,17 @@ class MemcacheClient(object):
def get(self, key):
"""Retrieves the value for a key or None."""
self.check_key(key)
- try:
- return self.cache[key]
- except KeyError:
+ obj = self.cache.get(key)
+ now = time.mktime(datetime.datetime.now().timetuple())
+ if obj and (obj[1] == 0 or obj[1] > now):
+ return obj[0]
+ else:
raise exception.TokenNotFound(token_id=key)
- def set(self, key, value):
+ def set(self, key, value, time=0):
"""Sets the value for a key."""
self.check_key(key)
- self.cache[key] = value
+ self.cache[key] = (value, time)
return True
def delete(self, key):
diff --git a/tests/test_keystoneclient_sql.py b/tests/test_keystoneclient_sql.py
index e365f83c..0d8ef52d 100644
--- a/tests/test_keystoneclient_sql.py
+++ b/tests/test_keystoneclient_sql.py
@@ -2,7 +2,6 @@
from keystone import config
from keystone import test
from keystone.common.sql import util as sql_util
-from keystone.common.sql import migration
import test_keystoneclient
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 81cec7c9..7409e30c 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -15,6 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import datetime
+
from keystone import test
from keystone.common import utils
@@ -38,3 +40,10 @@ class UtilsTestCase(test.TestCase):
hashed = utils.hash_password(password)
self.assertTrue(utils.check_password(password, hashed))
self.assertFalse(utils.check_password(wrong, hashed))
+
+ def test_isotime(self):
+ dt = datetime.datetime(year=1987, month=10, day=13,
+ hour=1, minute=2, second=3)
+ output = utils.isotime(dt)
+ expected = '1987-10-13T01:02:03Z'
+ self.assertEqual(output, expected)