From dfc90092a797d35a235a70f5df9eaba5b4778203 Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Fri, 27 Mar 2015 14:03:20 +1100 Subject: [PATCH] Expose audit_id via AccessInfo The audit_id is now a standard part of the v2 and v3 tokens. Expose it via AccessInfo so that it is usable for services and middleware. Change-Id: I14ddcfee5434084ad9da73c384e6f456602fdd2b Closes-Bug: #1437129 --- keystoneclient/access.py | 57 +++++++++++++++++++ keystoneclient/fixture/v2.py | 31 +++++++++- keystoneclient/fixture/v3.py | 30 +++++++++- .../tests/functional/test_access.py | 47 +++++++++++++++ .../tests/unit/v2_0/client_fixtures.py | 7 ++- keystoneclient/tests/unit/v2_0/test_access.py | 7 +++ .../tests/unit/v3/client_fixtures.py | 13 +++-- keystoneclient/tests/unit/v3/test_access.py | 12 ++++ 8 files changed, 196 insertions(+), 8 deletions(-) create mode 100644 keystoneclient/tests/functional/test_access.py diff --git a/keystoneclient/access.py b/keystoneclient/access.py index 8217de81b..009b72e0e 100644 --- a/keystoneclient/access.py +++ b/keystoneclient/access.py @@ -400,6 +400,35 @@ class AccessInfo(dict): """ raise NotImplementedError() + @property + def audit_id(self): + """Return the audit ID if present. + + :returns: str or None. + """ + raise NotImplementedError() + + @property + def audit_chain_id(self): + """Return the audit chain ID if present. + + In the event that a token was rescoped then this ID will be the + :py:attr:`audit_id` of the initial token. Returns None if no value + present. + + :returns: str or None. + """ + raise NotImplementedError() + + @property + def initial_audit_id(self): + """The audit ID of the initially requested token. + + This is the :py:attr:`audit_chain_id` if present or the + :py:attr:`audit_id`. + """ + return self.audit_chain_id or self.audit_id + class AccessInfoV2(AccessInfo): """An object for encapsulating a raw v2 auth token from identity @@ -592,6 +621,20 @@ class AccessInfoV2(AccessInfo): def is_federated(self): return False + @property + def audit_id(self): + try: + return self['token'].get('audit_ids', [])[0] + except IndexError: + return None + + @property + def audit_chain_id(self): + try: + return self['token'].get('audit_ids', [])[1] + except IndexError: + return None + class AccessInfoV3(AccessInfo): """An object for encapsulating a raw v3 auth token from identity @@ -760,3 +803,17 @@ class AccessInfoV3(AccessInfo): @property def oauth_consumer_id(self): return self.get('OS-OAUTH1', {}).get('consumer_id') + + @property + def audit_id(self): + try: + return self.get('audit_ids', [])[0] + except IndexError: + return None + + @property + def audit_chain_id(self): + try: + return self.get('audit_ids', [])[1] + except IndexError: + return None diff --git a/keystoneclient/fixture/v2.py b/keystoneclient/fixture/v2.py index cd4207b5f..3d6898b03 100644 --- a/keystoneclient/fixture/v2.py +++ b/keystoneclient/fixture/v2.py @@ -43,12 +43,14 @@ class Token(dict): def __init__(self, token_id=None, expires=None, issued=None, tenant_id=None, tenant_name=None, user_id=None, - user_name=None, trust_id=None, trustee_user_id=None): + user_name=None, trust_id=None, trustee_user_id=None, + audit_id=None, audit_chain_id=None): super(Token, self).__init__() self.token_id = token_id or uuid.uuid4().hex self.user_id = user_id or uuid.uuid4().hex self.user_name = user_name or uuid.uuid4().hex + self.audit_id = audit_id or uuid.uuid4().hex if not issued: issued = timeutils.utcnow() - datetime.timedelta(minutes=2) @@ -76,6 +78,9 @@ class Token(dict): self.set_trust(id=trust_id, trustee_user_id=trustee_user_id or user_id) + if audit_chain_id: + self.audit_chain_id = audit_chain_id + @property def root(self): return self.setdefault('access', {}) @@ -180,6 +185,30 @@ class Token(dict): def trustee_user_id(self, value): self.root.setdefault('trust', {})['trustee_user_id'] = value + @property + def audit_id(self): + try: + return self._token.get('audit_ids', [])[0] + except IndexError: + return None + + @audit_id.setter + def audit_id(self, value): + audit_chain_id = self.audit_chain_id + lval = [value] if audit_chain_id else [value, audit_chain_id] + self._token['audit_ids'] = lval + + @property + def audit_chain_id(self): + try: + return self._token.get('audit_ids', [])[1] + except IndexError: + return None + + @audit_chain_id.setter + def audit_chain_id(self, value): + self._token['audit_ids'] = [self.audit_id, value] + def validate(self): scoped = 'tenant' in self.token catalog = self.root.get('serviceCatalog') diff --git a/keystoneclient/fixture/v3.py b/keystoneclient/fixture/v3.py index 4f3d1f14b..646e46dbf 100644 --- a/keystoneclient/fixture/v3.py +++ b/keystoneclient/fixture/v3.py @@ -62,13 +62,14 @@ class Token(dict): project_domain_name=None, domain_id=None, domain_name=None, trust_id=None, trust_impersonation=None, trustee_user_id=None, trustor_user_id=None, oauth_access_token_id=None, - oauth_consumer_id=None): + oauth_consumer_id=None, audit_id=None, audit_chain_id=None): super(Token, self).__init__() self.user_id = user_id or uuid.uuid4().hex self.user_name = user_name or uuid.uuid4().hex self.user_domain_id = user_domain_id or uuid.uuid4().hex self.user_domain_name = user_domain_name or uuid.uuid4().hex + self.audit_id = audit_id or uuid.uuid4().hex if not methods: methods = ['password'] @@ -113,6 +114,9 @@ class Token(dict): self.set_oauth(access_token_id=oauth_access_token_id, consumer_id=oauth_consumer_id) + if audit_chain_id: + self.audit_chain_id = audit_chain_id + @property def root(self): return self.setdefault('token', {}) @@ -295,6 +299,30 @@ class Token(dict): def oauth_consumer_id(self, value): self.root.setdefault('OS-OAUTH1', {})['consumer_id'] = value + @property + def audit_id(self): + try: + return self.root.get('audit_ids', [])[0] + except IndexError: + return None + + @audit_id.setter + def audit_id(self, value): + audit_chain_id = self.audit_chain_id + lval = [value] if audit_chain_id else [value, audit_chain_id] + self.root['audit_ids'] = lval + + @property + def audit_chain_id(self): + try: + return self.root.get('audit_ids', [])[1] + except IndexError: + return None + + @audit_chain_id.setter + def audit_chain_id(self, value): + self.root['audit_ids'] = [self.audit_id, value] + def validate(self): project = self.root.get('project') domain = self.root.get('domain') diff --git a/keystoneclient/tests/functional/test_access.py b/keystoneclient/tests/functional/test_access.py new file mode 100644 index 000000000..36fe60ef6 --- /dev/null +++ b/keystoneclient/tests/functional/test_access.py @@ -0,0 +1,47 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os + +from keystoneclient.auth.identity import v2 +from keystoneclient import session +from tempest_lib import base + + +class TestV2AccessInfo(base.BaseTestCase): + + def setUp(self): + super(TestV2AccessInfo, self).setUp() + + self.session = session.Session() + + def test_access_audit_id(self): + unscoped_plugin = v2.Password(auth_url=os.environ.get('OS_AUTH_URL'), + username=os.environ.get('OS_USERNAME'), + password=os.environ.get('OS_PASSWORD')) + + unscoped_auth_ref = unscoped_plugin.get_access(self.session) + + self.assertIsNotNone(unscoped_auth_ref.audit_id) + self.assertIsNone(unscoped_auth_ref.audit_chain_id) + + scoped_plugin = v2.Token(auth_url=os.environ.get('OS_AUTH_URL'), + token=unscoped_auth_ref.auth_token, + tenant_name=os.environ.get('OS_TENANT_NAME')) + + scoped_auth_ref = scoped_plugin.get_access(self.session) + + self.assertIsNotNone(scoped_auth_ref.audit_id) + self.assertIsNotNone(scoped_auth_ref.audit_chain_id) + + self.assertEqual(unscoped_auth_ref.audit_id, + scoped_auth_ref.audit_chain_id) diff --git a/keystoneclient/tests/unit/v2_0/client_fixtures.py b/keystoneclient/tests/unit/v2_0/client_fixtures.py index 39d808eb1..0ce318d32 100644 --- a/keystoneclient/tests/unit/v2_0/client_fixtures.py +++ b/keystoneclient/tests/unit/v2_0/client_fixtures.py @@ -11,6 +11,7 @@ # under the License. from __future__ import unicode_literals +import uuid from keystoneclient import fixture @@ -30,7 +31,8 @@ def project_scoped_token(): tenant_id='225da22d3ce34b15877ea70b2a575f58', tenant_name='exampleproject', user_id='c4da488862bd435c9e6c0275a0d0e49a', - user_name='exampleuser') + user_name='exampleuser', + audit_chain_id=uuid.uuid4().hex) f.add_role(id='member_id', name='Member') @@ -73,7 +75,8 @@ def auth_response_body(): tenant_id='345', tenant_name='My Project', user_id='123', - user_name='jqsmith') + user_name='jqsmith', + audit_chain_id=uuid.uuid4().hex) f.add_role(id='234', name='compute:admin') role = f.add_role(id='235', name='object-store:admin') diff --git a/keystoneclient/tests/unit/v2_0/test_access.py b/keystoneclient/tests/unit/v2_0/test_access.py index f05f138d8..e966874dd 100644 --- a/keystoneclient/tests/unit/v2_0/test_access.py +++ b/keystoneclient/tests/unit/v2_0/test_access.py @@ -61,6 +61,10 @@ class AccessInfoTest(utils.TestCase, testresources.ResourcedTestCase): self.assertEqual(auth_ref.expires, token.expires) self.assertEqual(auth_ref.issued, token.issued) + self.assertEqual(token.audit_id, auth_ref.audit_id) + self.assertIsNone(auth_ref.audit_chain_id) + self.assertIsNone(token.audit_chain_id) + def test_will_expire_soon(self): token = client_fixtures.unscoped_token() expires = timeutils.utcnow() + datetime.timedelta(minutes=5) @@ -106,6 +110,9 @@ class AccessInfoTest(utils.TestCase, testresources.ResourcedTestCase): self.assertTrue(auth_ref.project_scoped) self.assertFalse(auth_ref.domain_scoped) + self.assertEqual(token.audit_id, auth_ref.audit_id) + self.assertEqual(token.audit_chain_id, auth_ref.audit_chain_id) + def test_diablo_token(self): diablo_token = self.examples.TOKEN_RESPONSES[ self.examples.VALID_DIABLO_TOKEN] diff --git a/keystoneclient/tests/unit/v3/client_fixtures.py b/keystoneclient/tests/unit/v3/client_fixtures.py index 517f9ae07..99e49f03d 100644 --- a/keystoneclient/tests/unit/v3/client_fixtures.py +++ b/keystoneclient/tests/unit/v3/client_fixtures.py @@ -11,6 +11,7 @@ # under the License. from __future__ import unicode_literals +import uuid from keystoneclient import fixture @@ -30,7 +31,8 @@ def domain_scoped_token(): user_domain_name='exampledomain', expires='2010-11-01T03:32:15-05:00', domain_id='8e9283b7ba0b1038840c3842058b86ab', - domain_name='anotherdomain') + domain_name='anotherdomain', + audit_chain_id=uuid.uuid4().hex) f.add_role(id='76e72a', name='admin') f.add_role(id='f4f392', name='member') @@ -78,7 +80,8 @@ def project_scoped_token(): project_id='225da22d3ce34b15877ea70b2a575f58', project_name='exampleproject', project_domain_id='4e6893b7ba0b4006840c3845660b86ed', - project_domain_name='exampledomain') + project_domain_name='exampledomain', + audit_chain_id=uuid.uuid4().hex) f.add_role(id='76e72a', name='admin') f.add_role(id='f4f392', name='member') @@ -135,7 +138,8 @@ def auth_response_body(): project_domain_id='123', project_domain_name='aDomain', project_id='345', - project_name='aTenant') + project_name='aTenant', + audit_chain_id=uuid.uuid4().hex) f.add_role(id='76e72a', name='admin') f.add_role(id='f4f392', name='member') @@ -179,4 +183,5 @@ def trust_token(): trust_id='fe0aef', trust_impersonation=False, trustee_user_id='0ca8f6', - trustor_user_id='bd263c') + trustor_user_id='bd263c', + audit_chain_id=uuid.uuid4().hex) diff --git a/keystoneclient/tests/unit/v3/test_access.py b/keystoneclient/tests/unit/v3/test_access.py index 5e4a9493d..f069f7162 100644 --- a/keystoneclient/tests/unit/v3/test_access.py +++ b/keystoneclient/tests/unit/v3/test_access.py @@ -70,6 +70,10 @@ class AccessInfoTest(utils.TestCase): self.assertEqual(auth_ref.expires, UNSCOPED_TOKEN.expires) self.assertEqual(auth_ref.issued, UNSCOPED_TOKEN.issued) + self.assertEqual(auth_ref.audit_id, UNSCOPED_TOKEN.audit_id) + self.assertIsNone(auth_ref.audit_chain_id) + self.assertIsNone(UNSCOPED_TOKEN.audit_chain_id) + def test_will_expire_soon(self): expires = timeutils.utcnow() + datetime.timedelta(minutes=5) UNSCOPED_TOKEN['token']['expires_at'] = expires.isoformat() @@ -113,6 +117,10 @@ class AccessInfoTest(utils.TestCase): self.assertTrue(auth_ref.domain_scoped) self.assertFalse(auth_ref.project_scoped) + self.assertEqual(DOMAIN_SCOPED_TOKEN.audit_id, auth_ref.audit_id) + self.assertEqual(DOMAIN_SCOPED_TOKEN.audit_chain_id, + auth_ref.audit_chain_id) + def test_building_project_scoped_accessinfo(self): auth_ref = access.AccessInfo.factory(resp=TOKEN_RESPONSE, body=PROJECT_SCOPED_TOKEN) @@ -156,6 +164,10 @@ class AccessInfoTest(utils.TestCase): self.assertFalse(auth_ref.domain_scoped) self.assertTrue(auth_ref.project_scoped) + self.assertEqual(PROJECT_SCOPED_TOKEN.audit_id, auth_ref.audit_id) + self.assertEqual(PROJECT_SCOPED_TOKEN.audit_chain_id, + auth_ref.audit_chain_id) + def test_oauth_access(self): consumer_id = uuid.uuid4().hex access_token_id = uuid.uuid4().hex