Repository: libcloud Updated Branches: refs/heads/trunk b9a5586e9 -> 2846da70f
Fix openstack v3 authentication. This change also allows user to provide a custom value for the OpenStack "domain" which defaults to "Default". With the code for the OpenStack Identity API v3, two new parameters were added: domain_name and token_scope but it was impossible to define them to a value other than their respective default. Closes #744 Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/eb497fa9 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/eb497fa9 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/eb497fa9 Branch: refs/heads/trunk Commit: eb497fa92287623fcc81413ae4775543b7ff8e71 Parents: b9a5586 Author: lionel <lio...@sixsq.com> Authored: Tue Apr 12 22:58:16 2016 +0200 Committer: Tomaz Muraus <to...@tomaz.me> Committed: Sat Apr 23 08:44:27 2016 +0200 ---------------------------------------------------------------------- libcloud/common/openstack.py | 23 ++++++++++++++ libcloud/common/openstack_identity.py | 31 +++++++++--------- libcloud/test/common/test_openstack_identity.py | 33 ++++++++++++++------ 3 files changed, 61 insertions(+), 26 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/eb497fa9/libcloud/common/openstack.py ---------------------------------------------------------------------- diff --git a/libcloud/common/openstack.py b/libcloud/common/openstack.py index cfdef79..8347613 100644 --- a/libcloud/common/openstack.py +++ b/libcloud/common/openstack.py @@ -94,6 +94,17 @@ class OpenStackBaseConnection(ConnectionUserAndKey): ex_force_base_url must also be provided. :type ex_force_auth_token: ``str`` + :param token_scope: Whether to scope a token to a "project", a + "domain" or "unscoped". + :type token_scope: ``str`` + + :param ex_domain_name: When authenticating, provide this domain name to + the identity service. A scoped token will be + returned. Some cloud providers require the domain + name to be provided at authentication time. Others + will use a default domain if none is provided. + :type ex_domain_name: ``str`` + :param ex_tenant_name: When authenticating, provide this tenant name to the identity service. A scoped token will be returned. Some cloud providers require the tenant name to be @@ -134,6 +145,8 @@ class OpenStackBaseConnection(ConnectionUserAndKey): ex_force_auth_url=None, ex_force_auth_version=None, ex_force_auth_token=None, + ex_token_scope=None, + ex_domain_name=None, ex_tenant_name=None, ex_force_service_type=None, ex_force_service_name=None, @@ -149,6 +162,8 @@ class OpenStackBaseConnection(ConnectionUserAndKey): self._ex_force_base_url = ex_force_base_url self._ex_force_auth_url = ex_force_auth_url self._ex_force_auth_token = ex_force_auth_token + self._ex_token_scope = ex_token_scope + self._ex_domain_name = ex_domain_name self._ex_tenant_name = ex_tenant_name self._ex_force_service_type = ex_force_service_type self._ex_force_service_name = ex_force_service_name @@ -186,6 +201,8 @@ class OpenStackBaseConnection(ConnectionUserAndKey): user_id=self.user_id, key=self.key, tenant_name=self._ex_tenant_name, + domain_name=self._ex_domain_name, + token_scope=self._ex_token_scope, timeout=self.timeout, parent_conn=self) @@ -400,6 +417,8 @@ class OpenStackDriverMixin(object): self._ex_force_auth_url = kwargs.get('ex_force_auth_url', None) self._ex_force_auth_version = kwargs.get('ex_force_auth_version', None) self._ex_force_auth_token = kwargs.get('ex_force_auth_token', None) + self._ex_token_scope = kwargs.get('ex_token_scope', None) + self._ex_domain_name = kwargs.get('ex_domain_name', None) self._ex_tenant_name = kwargs.get('ex_tenant_name', None) self._ex_force_service_type = kwargs.get('ex_force_service_type', None) self._ex_force_service_name = kwargs.get('ex_force_service_name', None) @@ -420,6 +439,10 @@ class OpenStackDriverMixin(object): rv['ex_force_auth_url'] = self._ex_force_auth_url if self._ex_force_auth_version: rv['ex_force_auth_version'] = self._ex_force_auth_version + if self._ex_token_scope: + rv['ex_token_scope'] = self._ex_token_scope + if self._ex_domain_name: + rv['ex_domain_name'] = self._ex_domain_name if self._ex_tenant_name: rv['ex_tenant_name'] = self._ex_tenant_name if self._ex_force_service_type: http://git-wip-us.apache.org/repos/asf/libcloud/blob/eb497fa9/libcloud/common/openstack_identity.py ---------------------------------------------------------------------- diff --git a/libcloud/common/openstack_identity.py b/libcloud/common/openstack_identity.py index 24c12d0..3ac9338 100644 --- a/libcloud/common/openstack_identity.py +++ b/libcloud/common/openstack_identity.py @@ -572,14 +572,13 @@ class OpenStackIdentityConnection(ConnectionUserAndKey): auth_version = None def __init__(self, auth_url, user_id, key, tenant_name=None, + domain_name=None, token_scope=None, timeout=None, parent_conn=None): super(OpenStackIdentityConnection, self).__init__(user_id=user_id, key=key, url=auth_url, timeout=timeout) - self.auth_url = auth_url - self.tenant_name = tenant_name self.parent_conn = parent_conn # enable tests to use the same mock connection classes. @@ -591,6 +590,10 @@ class OpenStackIdentityConnection(ConnectionUserAndKey): self.auth_url = auth_url self.tenant_name = tenant_name + self.domain_name = domain_name if domain_name is not None else \ + 'Default' + self.token_scope = token_scope if token_scope is not None else \ + OpenStackIdentityTokenScope.PROJECT self.timeout = timeout self.urls = {} @@ -927,8 +930,7 @@ class OpenStackIdentity_3_0_Connection(OpenStackIdentityConnection): ] def __init__(self, auth_url, user_id, key, tenant_name=None, - domain_name='Default', - token_scope=OpenStackIdentityTokenScope.PROJECT, + domain_name=None, token_scope=None, timeout=None, parent_conn=None): """ :param tenant_name: Name of the project this user belongs to. Note: @@ -941,8 +943,8 @@ class OpenStackIdentity_3_0_Connection(OpenStackIdentityConnection): domain to scope the token to. :type domain_name: ``str`` - :param token_scope: Whether to scope a token to a "project" or a - "domain" + :param token_scope: Whether to scope a token to a "project", a + "domain" or "unscoped" :type token_scope: ``str`` """ super(OpenStackIdentity_3_0_Connection, @@ -950,23 +952,20 @@ class OpenStackIdentity_3_0_Connection(OpenStackIdentityConnection): user_id=user_id, key=key, tenant_name=tenant_name, + domain_name=domain_name, + token_scope=token_scope, timeout=timeout, parent_conn=parent_conn) - if token_scope not in self.VALID_TOKEN_SCOPES: + + if self.token_scope not in self.VALID_TOKEN_SCOPES: raise ValueError('Invalid value for "token_scope" argument: %s' % - (token_scope)) + (self.token_scope)) - if (token_scope == OpenStackIdentityTokenScope.PROJECT and - (not tenant_name or not domain_name)): + if (self.token_scope == OpenStackIdentityTokenScope.PROJECT and + (not self.tenant_name or not self.domain_name)): raise ValueError('Must provide tenant_name and domain_name ' 'argument') - elif (token_scope == OpenStackIdentityTokenScope.DOMAIN and - not domain_name): - raise ValueError('Must provide domain_name argument') - self.tenant_name = tenant_name - self.domain_name = domain_name - self.token_scope = token_scope self.auth_user_roles = None def authenticate(self, force=False): http://git-wip-us.apache.org/repos/asf/libcloud/blob/eb497fa9/libcloud/test/common/test_openstack_identity.py ---------------------------------------------------------------------- diff --git a/libcloud/test/common/test_openstack_identity.py b/libcloud/test/common/test_openstack_identity.py index 7c57cf1..69e6f45 100644 --- a/libcloud/test/common/test_openstack_identity.py +++ b/libcloud/test/common/test_openstack_identity.py @@ -274,16 +274,6 @@ class OpenStackIdentity_3_0_ConnectionTests(unittest.TestCase): key='test', token_scope='project') - # Missing domain_name - expected_msg = 'Must provide domain_name argument' - self.assertRaisesRegexp(ValueError, expected_msg, - OpenStackIdentity_3_0_Connection, - auth_url='http://none', - user_id='test', - key='test', - token_scope='domain', - domain_name=None) - # Scope to project all ok OpenStackIdentity_3_0_Connection(auth_url='http://none', user_id='test', @@ -299,6 +289,15 @@ class OpenStackIdentity_3_0_ConnectionTests(unittest.TestCase): tenant_name=None, domain_name='Default') + def test_authenticate(self): + auth = OpenStackIdentity_3_0_Connection(auth_url='http://none', + user_id='test_user_id', + key='test_key', + token_scope='project', + tenant_name="test_tenant", + domain_name='test_domain') + auth.authenticate() + def test_list_supported_versions(self): OpenStackIdentity_3_0_MockHttp.type = 'v3' @@ -574,6 +573,20 @@ class OpenStackIdentity_3_0_MockHttp(MockHttp): return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK]) raise NotImplementedError() + def _v3_auth_tokens(self, method, url, body, headers): + if method == 'POST': + status = httplib.OK + data = json.loads(body) + if data['auth']['identity']['password']['user']['domain']['name'] != 'test_domain' or \ + data['auth']['scope']['project']['domain']['name'] != 'test_domain': + status = httplib.UNAUTHORIZED + + body = ComputeFileFixtures('openstack').load('_v3__auth.json') + headers = self.json_content_headers.copy() + headers['x-subject-token'] = '00000000000000000000000000000000' + return (status, body, headers, httplib.responses[httplib.OK]) + raise NotImplementedError() + def _v3_users(self, method, url, body, headers): if method == 'GET': # list users