aboutsummaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/contrib/revoke
diff options
context:
space:
mode:
authorasteroide <thomas.duval@orange.com>2015-09-01 16:03:26 +0200
committerasteroide <thomas.duval@orange.com>2015-09-01 16:04:53 +0200
commit92fd2dbfb672d7b2b1cdfd5dd5cf89f7716b3e12 (patch)
tree7ba22297042019e7363fa1d4ad26d1c32c5908c6 /keystone-moon/keystone/contrib/revoke
parent26e753254f3e43399cc76e62892908b7742415e8 (diff)
Update Keystone code from official Github repository with branch Master on 09/01/2015.
Change-Id: I0ff6099e6e2580f87f502002a998bbfe12673498
Diffstat (limited to 'keystone-moon/keystone/contrib/revoke')
-rw-r--r--keystone-moon/keystone/contrib/revoke/backends/kvs.py33
-rw-r--r--keystone-moon/keystone/contrib/revoke/backends/sql.py4
-rw-r--r--keystone-moon/keystone/contrib/revoke/core.py25
-rw-r--r--keystone-moon/keystone/contrib/revoke/migrate_repo/versions/001_revoke_table.py11
-rw-r--r--keystone-moon/keystone/contrib/revoke/migrate_repo/versions/002_add_audit_id_and_chain_to_revoke_table.py9
-rw-r--r--keystone-moon/keystone/contrib/revoke/model.py120
6 files changed, 99 insertions, 103 deletions
diff --git a/keystone-moon/keystone/contrib/revoke/backends/kvs.py b/keystone-moon/keystone/contrib/revoke/backends/kvs.py
index cc41fbee..349ed6e3 100644
--- a/keystone-moon/keystone/contrib/revoke/backends/kvs.py
+++ b/keystone-moon/keystone/contrib/revoke/backends/kvs.py
@@ -13,12 +13,12 @@
import datetime
from oslo_config import cfg
+from oslo_log import versionutils
from oslo_utils import timeutils
from keystone.common import kvs
from keystone.contrib import revoke
from keystone import exception
-from keystone.openstack.common import versionutils
CONF = cfg.CONF
@@ -45,29 +45,30 @@ class Revoke(revoke.Driver):
except exception.NotFound:
return []
- def _prune_expired_events_and_get(self, last_fetch=None, new_event=None):
- pruned = []
+ def list_events(self, last_fetch=None):
results = []
+
+ with self._store.get_lock(_EVENT_KEY):
+ events = self._list_events()
+
+ for event in events:
+ revoked_at = event.revoked_at
+ if last_fetch is None or revoked_at > last_fetch:
+ results.append(event)
+ return results
+
+ def revoke(self, event):
+ pruned = []
expire_delta = datetime.timedelta(seconds=CONF.token.expiration)
oldest = timeutils.utcnow() - expire_delta
- # TODO(ayoung): Store the time of the oldest event so that the
- # prune process can be skipped if none of the events have timed out.
+
with self._store.get_lock(_EVENT_KEY) as lock:
events = self._list_events()
- if new_event is not None:
- events.append(new_event)
+ if event:
+ events.append(event)
for event in events:
revoked_at = event.revoked_at
if revoked_at > oldest:
pruned.append(event)
- if last_fetch is None or revoked_at > last_fetch:
- results.append(event)
self._store.set(_EVENT_KEY, pruned, lock)
- return results
-
- def list_events(self, last_fetch=None):
- return self._prune_expired_events_and_get(last_fetch=last_fetch)
-
- def revoke(self, event):
- self._prune_expired_events_and_get(new_event=event)
diff --git a/keystone-moon/keystone/contrib/revoke/backends/sql.py b/keystone-moon/keystone/contrib/revoke/backends/sql.py
index 1b0cde1e..dd7fdd19 100644
--- a/keystone-moon/keystone/contrib/revoke/backends/sql.py
+++ b/keystone-moon/keystone/contrib/revoke/backends/sql.py
@@ -33,7 +33,7 @@ class RevocationEvent(sql.ModelBase, sql.ModelDictMixin):
access_token_id = sql.Column(sql.String(64))
issued_before = sql.Column(sql.DateTime(), nullable=False)
expires_at = sql.Column(sql.DateTime())
- revoked_at = sql.Column(sql.DateTime(), nullable=False)
+ revoked_at = sql.Column(sql.DateTime(), nullable=False, index=True)
audit_id = sql.Column(sql.String(32))
audit_chain_id = sql.Column(sql.String(32))
@@ -81,7 +81,6 @@ class Revoke(revoke.Driver):
session.flush()
def list_events(self, last_fetch=None):
- self._prune_expired_events()
session = sql.get_session()
query = session.query(RevocationEvent).order_by(
RevocationEvent.revoked_at)
@@ -102,3 +101,4 @@ class Revoke(revoke.Driver):
session = sql.get_session()
with session.begin():
session.add(record)
+ self._prune_expired_events()
diff --git a/keystone-moon/keystone/contrib/revoke/core.py b/keystone-moon/keystone/contrib/revoke/core.py
index c7335690..e1ab87c8 100644
--- a/keystone-moon/keystone/contrib/revoke/core.py
+++ b/keystone-moon/keystone/contrib/revoke/core.py
@@ -10,11 +10,14 @@
# License for the specific language governing permissions and limitations
# under the License.
+"""Main entry point into the Revoke service."""
+
import abc
import datetime
from oslo_config import cfg
from oslo_log import log
+from oslo_log import versionutils
from oslo_utils import timeutils
import six
@@ -26,7 +29,6 @@ from keystone.contrib.revoke import model
from keystone import exception
from keystone.i18n import _
from keystone import notifications
-from keystone.openstack.common import versionutils
CONF = cfg.CONF
@@ -64,12 +66,17 @@ def revoked_before_cutoff_time():
@dependency.provider('revoke_api')
class Manager(manager.Manager):
- """Revoke API Manager.
+ """Default pivot point for the Revoke backend.
Performs common logic for recording revocations.
+ See :mod:`keystone.common.manager.Manager` for more details on
+ how this dynamically calls the backend.
+
"""
+ driver_namespace = 'keystone.revoke'
+
def __init__(self):
super(Manager, self).__init__(CONF.revoke.driver)
self._register_listeners()
@@ -109,11 +116,12 @@ class Manager(manager.Manager):
self.revoke(
model.RevokeEvent(access_token_id=payload['resource_info']))
- def _group_callback(self, service, resource_type, operation, payload):
- user_ids = (u['id'] for u in self.identity_api.list_users_in_group(
- payload['resource_info']))
- for uid in user_ids:
- self.revoke(model.RevokeEvent(user_id=uid))
+ def _role_assignment_callback(self, service, resource_type, operation,
+ payload):
+ info = payload['resource_info']
+ self.revoke_by_grant(role_id=info['role_id'], user_id=info['user_id'],
+ domain_id=info.get('domain_id'),
+ project_id=info.get('project_id'))
def _register_listeners(self):
callbacks = {
@@ -124,6 +132,7 @@ class Manager(manager.Manager):
['role', self._role_callback],
['user', self._user_callback],
['project', self._project_callback],
+ ['role_assignment', self._role_assignment_callback]
],
notifications.ACTIONS.disabled: [
['user', self._user_callback],
@@ -136,7 +145,7 @@ class Manager(manager.Manager):
]
}
- for event, cb_info in six.iteritems(callbacks):
+ for event, cb_info in callbacks.items():
for resource_type, callback_fns in cb_info:
notifications.register_event_callback(event, resource_type,
callback_fns)
diff --git a/keystone-moon/keystone/contrib/revoke/migrate_repo/versions/001_revoke_table.py b/keystone-moon/keystone/contrib/revoke/migrate_repo/versions/001_revoke_table.py
index 7927ce0c..8b59010e 100644
--- a/keystone-moon/keystone/contrib/revoke/migrate_repo/versions/001_revoke_table.py
+++ b/keystone-moon/keystone/contrib/revoke/migrate_repo/versions/001_revoke_table.py
@@ -34,14 +34,3 @@ def upgrade(migrate_engine):
sql.Column('expires_at', sql.DateTime()),
sql.Column('revoked_at', sql.DateTime(), index=True, nullable=False))
service_table.create(migrate_engine, checkfirst=True)
-
-
-def downgrade(migrate_engine):
- # Operations to reverse the above upgrade go here.
- meta = sql.MetaData()
- meta.bind = migrate_engine
-
- tables = ['revocation_event']
- for t in tables:
- table = sql.Table(t, meta, autoload=True)
- table.drop(migrate_engine, checkfirst=True)
diff --git a/keystone-moon/keystone/contrib/revoke/migrate_repo/versions/002_add_audit_id_and_chain_to_revoke_table.py b/keystone-moon/keystone/contrib/revoke/migrate_repo/versions/002_add_audit_id_and_chain_to_revoke_table.py
index bee6fb2a..b6d821d7 100644
--- a/keystone-moon/keystone/contrib/revoke/migrate_repo/versions/002_add_audit_id_and_chain_to_revoke_table.py
+++ b/keystone-moon/keystone/contrib/revoke/migrate_repo/versions/002_add_audit_id_and_chain_to_revoke_table.py
@@ -26,12 +26,3 @@ def upgrade(migrate_engine):
nullable=True)
event_table.create_column(audit_id_column)
event_table.create_column(audit_chain_column)
-
-
-def downgrade(migrate_engine):
- meta = sql.MetaData()
- meta.bind = migrate_engine
-
- event_table = sql.Table(_TABLE_NAME, meta, autoload=True)
- event_table.drop_column('audit_id')
- event_table.drop_column('audit_chain_id')
diff --git a/keystone-moon/keystone/contrib/revoke/model.py b/keystone-moon/keystone/contrib/revoke/model.py
index 5e92042d..1a23d57d 100644
--- a/keystone-moon/keystone/contrib/revoke/model.py
+++ b/keystone-moon/keystone/contrib/revoke/model.py
@@ -11,6 +11,9 @@
# under the License.
from oslo_utils import timeutils
+from six.moves import map
+
+from keystone.common import utils
# The set of attributes common between the RevokeEvent
@@ -43,6 +46,15 @@ _TOKEN_KEYS = ['identity_domain_id',
'trustor_id',
'trustee_id']
+# Alternative names to be checked in token for every field in
+# revoke tree.
+ALTERNATIVES = {
+ 'user_id': ['user_id', 'trustor_id', 'trustee_id'],
+ 'domain_id': ['identity_domain_id', 'assignment_domain_id'],
+ # For a domain-scoped token, the domain is in assignment_domain_id.
+ 'domain_scope_id': ['assignment_domain_id', ],
+}
+
REVOKE_KEYS = _NAMES + _EVENT_ARGS
@@ -100,10 +112,10 @@ class RevokeEvent(object):
if self.consumer_id is not None:
event['OS-OAUTH1:access_token_id'] = self.access_token_id
if self.expires_at is not None:
- event['expires_at'] = timeutils.isotime(self.expires_at)
+ event['expires_at'] = utils.isotime(self.expires_at)
if self.issued_before is not None:
- event['issued_before'] = timeutils.isotime(self.issued_before,
- subsecond=True)
+ event['issued_before'] = utils.isotime(self.issued_before,
+ subsecond=True)
return event
def key_for_name(self, name):
@@ -111,7 +123,7 @@ class RevokeEvent(object):
def attr_keys(event):
- return map(event.key_for_name, _EVENT_NAMES)
+ return list(map(event.key_for_name, _EVENT_NAMES))
class RevokeTree(object):
@@ -176,7 +188,52 @@ class RevokeTree(object):
del parent[key]
def add_events(self, revoke_events):
- return map(self.add_event, revoke_events or [])
+ return list(map(self.add_event, revoke_events or []))
+
+ @staticmethod
+ def _next_level_keys(name, token_data):
+ """Generate keys based on current field name and token data
+
+ Generate all keys to look for in the next iteration of revocation
+ event tree traversal.
+ """
+ yield '*'
+ if name == 'role_id':
+ # Roles are very special since a token has a list of them.
+ # If the revocation event matches any one of them,
+ # revoke the token.
+ for role_id in token_data.get('roles', []):
+ yield role_id
+ else:
+ # For other fields we try to get any branch that concur
+ # with any alternative field in the token.
+ for alt_name in ALTERNATIVES.get(name, [name]):
+ yield token_data[alt_name]
+
+ def _search(self, revoke_map, names, token_data):
+ """Search for revocation event by token_data
+
+ Traverse the revocation events tree looking for event matching token
+ data issued after the token.
+ """
+ if not names:
+ # The last (leaf) level is checked in a special way because we
+ # verify issued_at field differently.
+ try:
+ return revoke_map['issued_before'] > token_data['issued_at']
+ except KeyError:
+ return False
+
+ name, remaining_names = names[0], names[1:]
+
+ for key in self._next_level_keys(name, token_data):
+ subtree = revoke_map.get('%s=%s' % (name, key))
+ if subtree and self._search(subtree, remaining_names, token_data):
+ return True
+
+ # If we made it out of the loop then no element in revocation tree
+ # corresponds to our token and it is good.
+ return False
def is_revoked(self, token_data):
"""Check if a token matches the revocation event
@@ -195,58 +252,7 @@ class RevokeTree(object):
'consumer_id', 'access_token_id'
"""
- # Alternative names to be checked in token for every field in
- # revoke tree.
- alternatives = {
- 'user_id': ['user_id', 'trustor_id', 'trustee_id'],
- 'domain_id': ['identity_domain_id', 'assignment_domain_id'],
- # For a domain-scoped token, the domain is in assignment_domain_id.
- 'domain_scope_id': ['assignment_domain_id', ],
- }
- # Contains current forest (collection of trees) to be checked.
- partial_matches = [self.revoke_map]
- # We iterate over every layer of our revoke tree (except the last one).
- for name in _EVENT_NAMES:
- # bundle is the set of partial matches for the next level down
- # the tree
- bundle = []
- wildcard = '%s=*' % (name,)
- # For every tree in current forest.
- for tree in partial_matches:
- # If there is wildcard node on current level we take it.
- bundle.append(tree.get(wildcard))
- if name == 'role_id':
- # Roles are very special since a token has a list of them.
- # If the revocation event matches any one of them,
- # revoke the token.
- for role_id in token_data.get('roles', []):
- bundle.append(tree.get('role_id=%s' % role_id))
- else:
- # For other fields we try to get any branch that concur
- # with any alternative field in the token.
- for alt_name in alternatives.get(name, [name]):
- bundle.append(
- tree.get('%s=%s' % (name, token_data[alt_name])))
- # tree.get returns `None` if there is no match, so `bundle.append`
- # adds a 'None' entry. This call remoes the `None` entries.
- partial_matches = [x for x in bundle if x is not None]
- if not partial_matches:
- # If we end up with no branches to follow means that the token
- # is definitely not in the revoke tree and all further
- # iterations will be for nothing.
- return False
-
- # The last (leaf) level is checked in a special way because we verify
- # issued_at field differently.
- for leaf in partial_matches:
- try:
- if leaf['issued_before'] > token_data['issued_at']:
- return True
- except KeyError:
- pass
- # If we made it out of the loop then no element in revocation tree
- # corresponds to our token and it is good.
- return False
+ return self._search(self.revoke_map, _EVENT_NAMES, token_data)
def build_token_values_v2(access, default_domain_id):