From 02f906bcd6866b24fa0b48d47f573197b17f0753 Mon Sep 17 00:00:00 2001 From: Phil Day Date: Thu, 27 Jun 2013 22:57:10 +0100 Subject: [PATCH] Allow tenant ID for authentication Tenant names are not necessarily unique for a User, so the client should also allow authentication by tenant_id If both ID and Name are specificed then use the ID Fixes bug 1195454 Change-Id: Ib62aabc3702db88f02259cd721f9efb31404bcb7 --- novaclient/client.py | 9 ++++++--- novaclient/shell.py | 26 ++++++++++++++++++-------- novaclient/tests/test_shell.py | 26 ++++++++++++++++++++++---- novaclient/v1_1/client.py | 14 ++++++++------ 4 files changed, 54 insertions(+), 21 deletions(-) diff --git a/novaclient/client.py b/novaclient/client.py index 1c93a33d0..50aedcdd5 100644 --- a/novaclient/client.py +++ b/novaclient/client.py @@ -33,7 +33,7 @@ class HTTPClient(object): USER_AGENT = 'python-novaclient' - def __init__(self, user, password, projectid, auth_url=None, + def __init__(self, user, password, projectid=None, auth_url=None, insecure=False, timeout=None, proxy_tenant_id=None, proxy_token=None, region_name=None, endpoint_type='publicURL', service_type=None, @@ -42,10 +42,11 @@ class HTTPClient(object): os_cache=False, no_cache=True, http_log_debug=False, auth_system='keystone', auth_plugin=None, - cacert=None): + cacert=None, tenant_id=None): self.user = user self.password = password self.projectid = projectid + self.tenant_id = tenant_id if auth_system and auth_system != 'keystone' and not auth_plugin: raise exceptions.AuthSystemNotFound(auth_system) @@ -407,7 +408,9 @@ class HTTPClient(object): "passwordCredentials": {"username": self.user, "password": self.password}}} - if self.projectid: + if self.tenant_id: + body['auth']['tenantId'] = self.tenant_id + elif self.projectid: body['auth']['tenantName'] = self.projectid self._authenticate(url, body) diff --git a/novaclient/shell.py b/novaclient/shell.py index 5c4697d98..3c8468dd0 100644 --- a/novaclient/shell.py +++ b/novaclient/shell.py @@ -288,6 +288,11 @@ class OpenStackComputeShell(object): parser.add_argument('--os_tenant_name', help=argparse.SUPPRESS) + parser.add_argument('--os-tenant-id', + metavar='', + default=utils.env('OS_TENANT_ID'), + help='Defaults to env[OS_TENANT_ID].') + parser.add_argument('--os-auth-url', metavar='', default=utils.env('OS_AUTH_URL', 'NOVA_URL'), @@ -532,12 +537,13 @@ class OpenStackComputeShell(object): self.do_bash_completion(args) return 0 - (os_username, os_tenant_name, os_auth_url, + (os_username, os_tenant_name, os_tenant_id, os_auth_url, os_region_name, os_auth_system, endpoint_type, insecure, service_type, service_name, volume_service_name, bypass_url, os_cache, cacert, timeout) = ( args.os_username, - args.os_tenant_name, args.os_auth_url, + args.os_tenant_name, args.os_tenant_id, + args.os_auth_url, args.os_region_name, args.os_auth_system, args.endpoint_type, args.insecure, args.service_type, args.service_name, args.volume_service_name, @@ -570,10 +576,11 @@ class OpenStackComputeShell(object): raise exc.CommandError("You must provide a username " "via either --os-username or env[OS_USERNAME]") - if not os_tenant_name: + if not os_tenant_name and not os_tenant_id: raise exc.CommandError("You must provide a tenant name " - "via either --os-tenant-name or " - "env[OS_TENANT_NAME]") + "or tenant id via --os-tenant-name, " + "--os-tenant-id, env[OS_TENANT_NAME] " + "or env[OS_TENANT_ID]") if not os_auth_url: if os_auth_system and os_auth_system != 'keystone': @@ -588,16 +595,19 @@ class OpenStackComputeShell(object): if (options.os_compute_api_version and options.os_compute_api_version != '1.0'): - if not os_tenant_name: + if not os_tenant_name and not os_tenant_id: raise exc.CommandError("You must provide a tenant name " - "via either --os-tenant-name or env[OS_TENANT_NAME]") + "or tenant id via --os-tenant-name, " + "--os-tenant-id, env[OS_TENANT_NAME] " + "or env[OS_TENANT_ID]") if not os_auth_url: raise exc.CommandError("You must provide an auth url " "via either --os-auth-url or env[OS_AUTH_URL]") self.cs = client.Client(options.os_compute_api_version, os_username, - os_password, os_tenant_name, os_auth_url, insecure, + os_password, os_tenant_name, tenant_id=os_tenant_id, + auth_url=os_auth_url, insecure=insecure, region_name=os_region_name, endpoint_type=endpoint_type, extensions=self.extensions, service_type=service_type, service_name=service_name, auth_system=os_auth_system, diff --git a/novaclient/tests/test_shell.py b/novaclient/tests/test_shell.py index 4da55d9d7..a4d8c5bed 100644 --- a/novaclient/tests/test_shell.py +++ b/novaclient/tests/test_shell.py @@ -19,11 +19,16 @@ FAKE_ENV = {'OS_USERNAME': 'username', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where'} +FAKE_ENV2 = {'OS_USERNAME': 'username', + 'OS_PASSWORD': 'password', + 'OS_TENANT_ID': 'tenant_id', + 'OS_AUTH_URL': 'http://no.where'} + class ShellTest(utils.TestCase): - def make_env(self, exclude=None): - env = dict((k, v) for k, v in FAKE_ENV.items() if k != exclude) + def make_env(self, exclude=None, fake_env=FAKE_ENV): + env = dict((k, v) for k, v in fake_env.items() if k != exclude) self.useFixture(fixtures.MonkeyPatch('os.environ', env)) def setUp(self): @@ -125,8 +130,9 @@ class ShellTest(utils.TestCase): self.fail('CommandError not raised') def test_no_tenant_name(self): - required = ('You must provide a tenant name' - ' via either --os-tenant-name or env[OS_TENANT_NAME]',) + required = ('You must provide a tenant name or tenant id' + ' via --os-tenant-name, --os-tenant-id,' + ' env[OS_TENANT_NAME] or env[OS_TENANT_ID]',) self.make_env(exclude='OS_TENANT_NAME') try: self.shell('list') @@ -135,6 +141,18 @@ class ShellTest(utils.TestCase): else: self.fail('CommandError not raised') + def test_no_tenant_id(self): + required = ('You must provide a tenant name or tenant id' + ' via --os-tenant-name, --os-tenant-id,' + ' env[OS_TENANT_NAME] or env[OS_TENANT_ID]',) + self.make_env(exclude='OS_TENANT_ID', fake_env=FAKE_ENV2) + try: + self.shell('list') + except exceptions.CommandError as message: + self.assertEqual(required, message.args) + else: + self.fail('CommandError not raised') + def test_no_auth_url(self): required = ('You must provide an auth url' ' via either --os-auth-url or env[OS_AUTH_URL] or' diff --git a/novaclient/v1_1/client.py b/novaclient/v1_1/client.py index 7b93cc369..df97b7316 100644 --- a/novaclient/v1_1/client.py +++ b/novaclient/v1_1/client.py @@ -65,8 +65,8 @@ class Client(object): """ - # FIXME(jesse): project_id isn't required to authenticate - def __init__(self, username, api_key, project_id, auth_url=None, + # FIXME(jesse): projectid isn't required to authenticate + def __init__(self, username, api_key, projectid, auth_url=None, insecure=False, timeout=None, proxy_tenant_id=None, proxy_token=None, region_name=None, endpoint_type='publicURL', extensions=None, @@ -75,11 +75,12 @@ class Client(object): bypass_url=None, os_cache=False, no_cache=True, http_log_debug=False, auth_system='keystone', auth_plugin=None, - cacert=None): + cacert=None, tenant_id=None): # FIXME(comstud): Rename the api_key argument above when we # know it's not being used as keyword argument password = api_key - self.project_id = project_id + self.projectid = projectid + self.tenant_id = tenant_id self.flavors = flavors.FlavorManager(self) self.flavor_access = flavor_access.FlavorAccessManager(self) self.images = images.ImageManager(self) @@ -128,8 +129,9 @@ class Client(object): self.client = client.HTTPClient(username, password, - project_id, - auth_url, + projectid=projectid, + tenant_id=tenant_id, + auth_url=auth_url, insecure=insecure, timeout=timeout, auth_system=auth_system,