From 389c3ee836192f091bda383b3dcea785d2bfdc68 Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Tue, 31 Mar 2015 10:03:20 +1100 Subject: [PATCH] Allow requesting an unscoped Token The keystone server understands that specifying unscoped in the scope section of an auth request means that it should ignore the default_project_id of a user and return an unscoped token. This is the client side change to allow requesting these tokens via an auth plugin. Change-Id: Iba5ebcea0bf0d8e5a31d552977276fc03e536c67 Implements: bp explicit-unscoped --- keystoneclient/auth/identity/v3/base.py | 11 ++++-- .../tests/unit/auth/test_identity_v3.py | 35 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/keystoneclient/auth/identity/v3/base.py b/keystoneclient/auth/identity/v3/base.py index add571e39..9d1f56274 100644 --- a/keystoneclient/auth/identity/v3/base.py +++ b/keystoneclient/auth/identity/v3/base.py @@ -112,9 +112,13 @@ class Auth(BaseAuth): is going to expire. (optional) default True :param bool include_catalog: Include the service catalog in the returned token. (optional) default True. + :param bool unscoped: Force the return of an unscoped token. This will make + the keystone server return an unscoped token even if + a default_project_id is set for this user. """ def __init__(self, auth_url, auth_methods, **kwargs): + self.unscoped = kwargs.pop('unscoped', False) super(Auth, self).__init__(auth_url=auth_url, **kwargs) self.auth_methods = auth_methods @@ -138,12 +142,13 @@ class Auth(BaseAuth): mutual_exclusion = [bool(self.domain_id or self.domain_name), bool(self.project_id or self.project_name), - bool(self.trust_id)] + bool(self.trust_id), + bool(self.unscoped)] if sum(mutual_exclusion) > 1: raise exceptions.AuthorizationFailure( _('Authentication cannot be scoped to multiple targets. Pick ' - 'one of: project, domain or trust')) + 'one of: project, domain, trust or unscoped')) if self.domain_id: body['auth']['scope'] = {'domain': {'id': self.domain_id}} @@ -161,6 +166,8 @@ class Auth(BaseAuth): scope['project']['domain'] = {'name': self.project_domain_name} elif self.trust_id: body['auth']['scope'] = {'OS-TRUST:trust': {'id': self.trust_id}} + elif self.unscoped: + body['auth']['scope'] = {'unscoped': {}} # NOTE(jamielennox): we add nocatalog here rather than in token_url # directly as some federation plugins require the base token_url diff --git a/keystoneclient/tests/unit/auth/test_identity_v3.py b/keystoneclient/tests/unit/auth/test_identity_v3.py index c01b39f1c..077ebf53f 100644 --- a/keystoneclient/tests/unit/auth/test_identity_v3.py +++ b/keystoneclient/tests/unit/auth/test_identity_v3.py @@ -496,3 +496,38 @@ class V3IdentityPlugin(utils.TestCase): self.assertIs(v3.AuthMethod, v3_base.AuthMethod) self.assertIs(v3.AuthConstructor, v3_base.AuthConstructor) self.assertIs(v3.Auth, v3_base.Auth) + + def test_unscoped_request(self): + token = fixture.V3Token() + self.stub_auth(json=token) + password = uuid.uuid4().hex + + a = v3.Password(self.TEST_URL, + user_id=token.user_id, + password=password, + unscoped=True) + s = session.Session() + + auth_ref = a.get_access(s) + + self.assertFalse(auth_ref.scoped) + body = self.requests_mock.last_request.json() + + ident = body['auth']['identity'] + + self.assertEqual(['password'], ident['methods']) + self.assertEqual(token.user_id, ident['password']['user']['id']) + self.assertEqual(password, ident['password']['user']['password']) + + self.assertEqual({}, body['auth']['scope']['unscoped']) + + def test_unscoped_with_scope_data(self): + a = v3.Password(self.TEST_URL, + user_id=uuid.uuid4().hex, + password=uuid.uuid4().hex, + unscoped=True, + project_id=uuid.uuid4().hex) + + s = session.Session() + + self.assertRaises(exceptions.AuthorizationFailure, a.get_auth_ref, s)