Merge "Make keystoneclient use an adapter"
This commit is contained in:
@@ -211,13 +211,12 @@ class Manager(object):
|
||||
return self.client.delete(url, **kwargs)
|
||||
|
||||
def _update(self, url, body=None, response_key=None, method="PUT",
|
||||
management=True, **kwargs):
|
||||
**kwargs):
|
||||
methods = {"PUT": self.client.put,
|
||||
"POST": self.client.post,
|
||||
"PATCH": self.client.patch}
|
||||
try:
|
||||
resp, body = methods[method](url, body=body,
|
||||
management=management,
|
||||
**kwargs)
|
||||
except KeyError:
|
||||
raise exceptions.ClientException(_("Invalid update method: %s")
|
||||
|
@@ -52,10 +52,11 @@ if not hasattr(urlparse, 'parse_qsl'):
|
||||
|
||||
|
||||
from keystoneclient import access
|
||||
from keystoneclient import adapter
|
||||
from keystoneclient.auth import base
|
||||
from keystoneclient import baseclient
|
||||
from keystoneclient import exceptions
|
||||
from keystoneclient.i18n import _, _LI, _LW
|
||||
from keystoneclient.i18n import _, _LW
|
||||
from keystoneclient import session as client_session
|
||||
from keystoneclient import utils
|
||||
|
||||
@@ -84,6 +85,51 @@ class _FakeRequestSession(object):
|
||||
return requests.request(*args, **kwargs)
|
||||
|
||||
|
||||
class _KeystoneAdapter(adapter.LegacyJsonAdapter):
|
||||
"""A wrapper layer to interface keystoneclient with a session.
|
||||
|
||||
An adapter provides a generic interface between a client and the session to
|
||||
provide client specific defaults. This object is passed to the managers.
|
||||
Keystoneclient managers have some additional requirements of variables that
|
||||
they expect to be present on the passed object.
|
||||
|
||||
Subclass the existing adapter to provide those values that keystoneclient
|
||||
managers expect.
|
||||
"""
|
||||
|
||||
@property
|
||||
def user_id(self):
|
||||
"""Best effort to retrieve the user_id from the plugin.
|
||||
|
||||
Some managers rely on being able to get the currently authenticated
|
||||
user id. This is a problem when we are trying to abstract away the
|
||||
details of an auth plugin.
|
||||
|
||||
For example changing a user's password can require access to the
|
||||
currently authenticated user_id.
|
||||
|
||||
Perform a best attempt to fetch this data. It will work in the legacy
|
||||
case and with identity plugins and be None otherwise which is the same
|
||||
as the historical behavior.
|
||||
"""
|
||||
# the identity plugin case
|
||||
try:
|
||||
return self.session.auth.get_access(self.session).user_id
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# there is a case that we explicity allow (tested by our unit tests)
|
||||
# that says you should be able to set the user_id on a legacy client
|
||||
# and it should overwrite the one retrieved via authentication. If it's
|
||||
# a legacy then self.session.auth is a client and we retrieve user_id.
|
||||
try:
|
||||
return self.session.auth.user_id
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class HTTPClient(baseclient.Client, base.BaseAuthPlugin):
|
||||
|
||||
version = None
|
||||
@@ -169,7 +215,6 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin):
|
||||
self.project_domain_id = None
|
||||
self.project_domain_name = None
|
||||
|
||||
self.region_name = None
|
||||
self.auth_url = None
|
||||
self._endpoint = None
|
||||
self._management_url = None
|
||||
@@ -193,8 +238,8 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin):
|
||||
self._management_url = self.auth_ref.management_url[0]
|
||||
self.auth_token_from_user = self.auth_ref.auth_token
|
||||
self.trust_id = self.auth_ref.trust_id
|
||||
if self.auth_ref.has_service_catalog():
|
||||
self.region_name = self.auth_ref.service_catalog.region_name
|
||||
if self.auth_ref.has_service_catalog() and not region_name:
|
||||
region_name = self.auth_ref.service_catalog.region_name
|
||||
else:
|
||||
self.auth_ref = None
|
||||
|
||||
@@ -251,8 +296,6 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin):
|
||||
self.auth_token_from_user = None
|
||||
if endpoint:
|
||||
self._endpoint = endpoint.rstrip('/')
|
||||
if region_name:
|
||||
self.region_name = region_name
|
||||
self._auth_token = None
|
||||
|
||||
if not session:
|
||||
@@ -264,6 +307,12 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin):
|
||||
self.domain = ''
|
||||
self.debug_log = debug
|
||||
|
||||
self._adapter = _KeystoneAdapter(session,
|
||||
service_type='identity',
|
||||
interface='admin',
|
||||
region_name=region_name,
|
||||
version=self.version)
|
||||
|
||||
# keyring setup
|
||||
if use_keyring and keyring is None:
|
||||
_logger.warning(_LW('Failed to load keyring modules.'))
|
||||
@@ -396,7 +445,7 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin):
|
||||
project_domain_name = project_domain_name or self.project_domain_name
|
||||
|
||||
trust_id = trust_id or self.trust_id
|
||||
region_name = region_name or self.region_name
|
||||
region_name = region_name or self._adapter.region_name
|
||||
|
||||
if not token:
|
||||
token = self.auth_token_from_user
|
||||
@@ -569,83 +618,110 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin):
|
||||
def serialize(self, entity):
|
||||
return jsonutils.dumps(entity)
|
||||
|
||||
@staticmethod
|
||||
def _decode_body(resp):
|
||||
if resp.text:
|
||||
try:
|
||||
body_resp = jsonutils.loads(resp.text)
|
||||
except (ValueError, TypeError):
|
||||
body_resp = None
|
||||
_logger.debug("Could not decode JSON from body: %s",
|
||||
resp.text)
|
||||
else:
|
||||
_logger.debug("No body was returned.")
|
||||
body_resp = None
|
||||
|
||||
return body_resp
|
||||
|
||||
def request(self, url, method, **kwargs):
|
||||
def request(self, *args, **kwargs):
|
||||
"""Send an http request with the specified characteristics.
|
||||
|
||||
Wrapper around requests.request to handle tasks such as
|
||||
setting headers, JSON encoding/decoding, and error handling.
|
||||
|
||||
.. warning::
|
||||
*DEPRECATED*: This function is no longer used. It was designed to
|
||||
be used only by the managers and the managers now receive an
|
||||
adapter so this function is no longer on the standard request path.
|
||||
"""
|
||||
|
||||
try:
|
||||
kwargs['json'] = kwargs.pop('body')
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
kwargs.setdefault('authenticated', False)
|
||||
resp = super(HTTPClient, self).request(url, method, **kwargs)
|
||||
return resp, self._decode_body(resp)
|
||||
return self._adapter.request(*args, **kwargs)
|
||||
|
||||
def _cs_request(self, url, method, management=True, **kwargs):
|
||||
"""Makes an authenticated request to keystone endpoint by
|
||||
concatenating self.management_url and url and passing in method and
|
||||
any associated kwargs.
|
||||
"""
|
||||
# NOTE(jamielennox): remember that if you use the legacy client mode
|
||||
# (you create a client without a session) then this HTTPClient object
|
||||
# is the auth plugin you are using. Values in the endpoint_filter may
|
||||
# be ignored and you should look at get_endpoint to figure out what.
|
||||
interface = 'admin' if management else 'public'
|
||||
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
|
||||
endpoint_filter.setdefault('service_type', 'identity')
|
||||
endpoint_filter.setdefault('interface', interface)
|
||||
|
||||
if self.version:
|
||||
endpoint_filter.setdefault('version', self.version)
|
||||
|
||||
if self.region_name:
|
||||
endpoint_filter.setdefault('region_name', self.region_name)
|
||||
# NOTE(jamielennox): This is deprecated and is no longer a part of the
|
||||
# standard client request path. It now goes via the adapter instead.
|
||||
if not management:
|
||||
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
|
||||
endpoint_filter.setdefault('interface', 'public')
|
||||
|
||||
kwargs.setdefault('authenticated', None)
|
||||
try:
|
||||
return self.request(url, method, **kwargs)
|
||||
except exceptions.MissingAuthPlugin:
|
||||
_logger.info(_LI('Cannot get authenticated endpoint without an '
|
||||
'auth plugin'))
|
||||
raise exceptions.AuthorizationFailure(
|
||||
_('Current authorization does not have a known management '
|
||||
'url'))
|
||||
return self.request(url, method, **kwargs)
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
"""Perform an authenticated GET request.
|
||||
|
||||
This calls :py:meth:`.request()` with ``method`` set to ``GET`` and an
|
||||
authentication token if one is available.
|
||||
|
||||
.. warning::
|
||||
*DEPRECATED*: This function is no longer used. It was designed to
|
||||
be used by the managers and the managers now receive an adapter so
|
||||
this function is no longer on the standard request path.
|
||||
"""
|
||||
return self._cs_request(url, 'GET', **kwargs)
|
||||
|
||||
def head(self, url, **kwargs):
|
||||
"""Perform an authenticated HEAD request.
|
||||
|
||||
This calls :py:meth:`.request()` with ``method`` set to ``HEAD`` and an
|
||||
authentication token if one is available.
|
||||
|
||||
.. warning::
|
||||
*DEPRECATED*: This function is no longer used. It was designed to
|
||||
be used by the managers and the managers now receive an adapter so
|
||||
this function is no longer on the standard request path.
|
||||
"""
|
||||
return self._cs_request(url, 'HEAD', **kwargs)
|
||||
|
||||
def post(self, url, **kwargs):
|
||||
"""Perform an authenticate POST request.
|
||||
|
||||
This calls :py:meth:`.request()` with ``method`` set to ``POST`` and an
|
||||
authentication token if one is available.
|
||||
|
||||
.. warning::
|
||||
*DEPRECATED*: This function is no longer used. It was designed to
|
||||
be used by the managers and the managers now receive an adapter so
|
||||
this function is no longer on the standard request path.
|
||||
"""
|
||||
return self._cs_request(url, 'POST', **kwargs)
|
||||
|
||||
def put(self, url, **kwargs):
|
||||
"""Perform an authenticate PUT request.
|
||||
|
||||
This calls :py:meth:`.request()` with ``method`` set to ``PUT`` and an
|
||||
authentication token if one is available.
|
||||
|
||||
.. warning::
|
||||
*DEPRECATED*: This function is no longer used. It was designed to
|
||||
be used by the managers and the managers now receive an adapter so
|
||||
this function is no longer on the standard request path.
|
||||
"""
|
||||
return self._cs_request(url, 'PUT', **kwargs)
|
||||
|
||||
def patch(self, url, **kwargs):
|
||||
"""Perform an authenticate PATCH request.
|
||||
|
||||
This calls :py:meth:`.request()` with ``method`` set to ``PATCH`` and
|
||||
an authentication token if one is available.
|
||||
|
||||
.. warning::
|
||||
*DEPRECATED*: This function is no longer used. It was designed to
|
||||
be used by the managers and the managers now receive an adapter so
|
||||
this function is no longer on the standard request path.
|
||||
"""
|
||||
return self._cs_request(url, 'PATCH', **kwargs)
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
"""Perform an authenticate DELETE request.
|
||||
|
||||
This calls :py:meth:`.request()` with ``method`` set to ``DELETE`` and
|
||||
an authentication token if one is available.
|
||||
|
||||
.. warning::
|
||||
*DEPRECATED*: This function is no longer used. It was designed to
|
||||
be used by the managers and the managers now receive an adapter so
|
||||
this function is no longer on the standard request path.
|
||||
"""
|
||||
return self._cs_request(url, 'DELETE', **kwargs)
|
||||
|
||||
# DEPRECATIONS: The following methods are no longer directly supported
|
||||
@@ -656,20 +732,40 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin):
|
||||
'timeout': None,
|
||||
'verify_cert': 'verify'}
|
||||
|
||||
deprecated_adapter_variables = {'region_name': None}
|
||||
|
||||
def __getattr__(self, name):
|
||||
# FIXME(jamielennox): provide a proper deprecated warning
|
||||
try:
|
||||
var_name = self.deprecated_session_variables[name]
|
||||
except KeyError:
|
||||
raise AttributeError(_("Unknown Attribute: %s") % name)
|
||||
pass
|
||||
else:
|
||||
return getattr(self.session, var_name or name)
|
||||
|
||||
return getattr(self.session, var_name or name)
|
||||
try:
|
||||
var_name = self.deprecated_adapter_variables[name]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return getattr(self._adapter, var_name or name)
|
||||
|
||||
raise AttributeError(_("Unknown Attribute: %s") % name)
|
||||
|
||||
def __setattr__(self, name, val):
|
||||
# FIXME(jamielennox): provide a proper deprecated warning
|
||||
try:
|
||||
var_name = self.deprecated_session_variables[name]
|
||||
except KeyError:
|
||||
super(HTTPClient, self).__setattr__(name, val)
|
||||
pass
|
||||
else:
|
||||
setattr(self.session, var_name or name)
|
||||
return setattr(self.session, var_name or name)
|
||||
|
||||
try:
|
||||
var_name = self.deprecated_adapter_variables[name]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return setattr(self._adapter, var_name or name)
|
||||
|
||||
super(HTTPClient, self).__setattr__(name, val)
|
||||
|
@@ -40,8 +40,8 @@ class BaseTest(utils.TestCase):
|
||||
auth_url='http://127.0.0.1:5000',
|
||||
endpoint='http://127.0.0.1:5000')
|
||||
|
||||
self.client.get = self.mox.CreateMockAnything()
|
||||
self.client.get('/OS-KSADM/roles/1').AndRaise(AttributeError)
|
||||
self.client._adapter.get = self.mox.CreateMockAnything()
|
||||
self.client._adapter.get('/OS-KSADM/roles/1').AndRaise(AttributeError)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
f = roles.Role(self.client.roles, {'id': 1, 'name': 'Member'})
|
||||
|
@@ -130,17 +130,19 @@ class Client(httpclient.HTTPClient):
|
||||
def __init__(self, **kwargs):
|
||||
"""Initialize a new client for the Keystone v2.0 API."""
|
||||
super(Client, self).__init__(**kwargs)
|
||||
self.endpoints = endpoints.EndpointManager(self)
|
||||
self.extensions = extensions.ExtensionManager(self)
|
||||
self.roles = roles.RoleManager(self)
|
||||
self.services = services.ServiceManager(self)
|
||||
self.tokens = tokens.TokenManager(self)
|
||||
self.users = users.UserManager(self, self.roles)
|
||||
|
||||
self.tenants = tenants.TenantManager(self, self.roles, self.users)
|
||||
self.endpoints = endpoints.EndpointManager(self._adapter)
|
||||
self.extensions = extensions.ExtensionManager(self._adapter)
|
||||
self.roles = roles.RoleManager(self._adapter)
|
||||
self.services = services.ServiceManager(self._adapter)
|
||||
self.tokens = tokens.TokenManager(self._adapter)
|
||||
self.users = users.UserManager(self._adapter, self.roles)
|
||||
|
||||
self.tenants = tenants.TenantManager(self._adapter,
|
||||
self.roles, self.users)
|
||||
|
||||
# extensions
|
||||
self.ec2 = ec2.CredentialsManager(self)
|
||||
self.ec2 = ec2.CredentialsManager(self._adapter)
|
||||
|
||||
# DEPRECATED: if session is passed then we go to the new behaviour of
|
||||
# authenticating on the first required call.
|
||||
|
@@ -78,7 +78,7 @@ class UserManager(base.ManagerWithFind):
|
||||
return self._update("/OS-KSCRUD/users/%s" % self.api.user_id, params,
|
||||
response_key="access",
|
||||
method="PATCH",
|
||||
management=False,
|
||||
endpoint_filter={'interface': 'public'},
|
||||
log=False)
|
||||
|
||||
def update_tenant(self, user, tenant):
|
||||
|
@@ -169,23 +169,26 @@ EndpointPolicyManager`
|
||||
"""Initialize a new client for the Keystone v3 API."""
|
||||
super(Client, self).__init__(**kwargs)
|
||||
|
||||
self.credentials = credentials.CredentialManager(self)
|
||||
self.endpoint_filter = endpoint_filter.EndpointFilterManager(self)
|
||||
self.endpoint_policy = endpoint_policy.EndpointPolicyManager(self)
|
||||
self.endpoints = endpoints.EndpointManager(self)
|
||||
self.domains = domains.DomainManager(self)
|
||||
self.federation = federation.FederationManager(self)
|
||||
self.groups = groups.GroupManager(self)
|
||||
self.oauth1 = oauth1.create_oauth_manager(self)
|
||||
self.policies = policies.PolicyManager(self)
|
||||
self.projects = projects.ProjectManager(self)
|
||||
self.regions = regions.RegionManager(self)
|
||||
self.role_assignments = role_assignments.RoleAssignmentManager(self)
|
||||
self.roles = roles.RoleManager(self)
|
||||
self.services = services.ServiceManager(self)
|
||||
self.tokens = tokens.TokenManager(self)
|
||||
self.trusts = trusts.TrustManager(self)
|
||||
self.users = users.UserManager(self)
|
||||
self.credentials = credentials.CredentialManager(self._adapter)
|
||||
self.endpoint_filter = endpoint_filter.EndpointFilterManager(
|
||||
self._adapter)
|
||||
self.endpoint_policy = endpoint_policy.EndpointPolicyManager(
|
||||
self._adapter)
|
||||
self.endpoints = endpoints.EndpointManager(self._adapter)
|
||||
self.domains = domains.DomainManager(self._adapter)
|
||||
self.federation = federation.FederationManager(self._adapter)
|
||||
self.groups = groups.GroupManager(self._adapter)
|
||||
self.oauth1 = oauth1.create_oauth_manager(self._adapter)
|
||||
self.policies = policies.PolicyManager(self._adapter)
|
||||
self.projects = projects.ProjectManager(self._adapter)
|
||||
self.regions = regions.RegionManager(self._adapter)
|
||||
self.role_assignments = (
|
||||
role_assignments.RoleAssignmentManager(self._adapter))
|
||||
self.roles = roles.RoleManager(self._adapter)
|
||||
self.services = services.ServiceManager(self._adapter)
|
||||
self.tokens = tokens.TokenManager(self._adapter)
|
||||
self.trusts = trusts.TrustManager(self._adapter)
|
||||
self.users = users.UserManager(self._adapter)
|
||||
|
||||
# DEPRECATED: if session is passed then we go to the new behaviour of
|
||||
# authenticating on the first required call.
|
||||
|
@@ -15,6 +15,8 @@
|
||||
from keystoneclient import base
|
||||
from keystoneclient import exceptions
|
||||
from keystoneclient.i18n import _
|
||||
from keystoneclient.v3 import endpoints
|
||||
from keystoneclient.v3 import projects
|
||||
|
||||
|
||||
class EndpointFilterManager(base.Manager):
|
||||
@@ -72,8 +74,8 @@ class EndpointFilterManager(base.Manager):
|
||||
base_url = self._build_base_url(project=project)
|
||||
return super(EndpointFilterManager, self)._list(
|
||||
base_url,
|
||||
self.client.endpoints.collection_key,
|
||||
obj_class=self.client.endpoints.resource_class)
|
||||
endpoints.EndpointManager.collection_key,
|
||||
obj_class=endpoints.EndpointManager.resource_class)
|
||||
|
||||
def list_projects_for_endpoint(self, endpoint):
|
||||
"""List all projects for a given endpoint."""
|
||||
@@ -83,5 +85,5 @@ class EndpointFilterManager(base.Manager):
|
||||
base_url = self._build_base_url(endpoint=endpoint)
|
||||
return super(EndpointFilterManager, self)._list(
|
||||
base_url,
|
||||
self.client.projects.collection_key,
|
||||
obj_class=self.client.projects.resource_class)
|
||||
projects.ProjectManager.collection_key,
|
||||
obj_class=projects.ProjectManager.resource_class)
|
||||
|
@@ -14,6 +14,7 @@
|
||||
|
||||
from keystoneclient import base
|
||||
from keystoneclient.i18n import _
|
||||
from keystoneclient.v3 import endpoints
|
||||
from keystoneclient.v3 import policies
|
||||
|
||||
|
||||
@@ -150,5 +151,5 @@ class EndpointPolicyManager(base.Manager):
|
||||
'ext_name': self.OS_EP_POLICY_EXT}
|
||||
return self._list(
|
||||
url,
|
||||
self.client.endpoints.collection_key,
|
||||
obj_class=self.client.endpoints.resource_class)
|
||||
endpoints.EndpointManager.collection_key,
|
||||
obj_class=endpoints.EndpointManager.resource_class)
|
||||
|
@@ -13,6 +13,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from keystoneclient import auth
|
||||
from keystoneclient import base
|
||||
from keystoneclient.v3.contrib.oauth1 import utils
|
||||
|
||||
@@ -39,8 +40,9 @@ class AccessTokenManager(base.CrudManager):
|
||||
resource_owner_secret=request_secret,
|
||||
signature_method=oauth1.SIGNATURE_HMAC,
|
||||
verifier=verifier)
|
||||
url = self.client.auth_url.rstrip("/") + endpoint
|
||||
url, headers, body = oauth_client.sign(url, http_method='POST')
|
||||
url = self.api.get_endpoint(interface=auth.AUTH_INTERFACE).rstrip('/')
|
||||
url, headers, body = oauth_client.sign(url + endpoint,
|
||||
http_method='POST')
|
||||
resp, body = self.client.post(endpoint, headers=headers)
|
||||
token = utils.get_oauth_token_from_body(resp.content)
|
||||
return self.resource_class(self, token)
|
||||
|
@@ -15,6 +15,7 @@ from __future__ import unicode_literals
|
||||
|
||||
from six.moves.urllib import parse as urlparse
|
||||
|
||||
from keystoneclient import auth
|
||||
from keystoneclient import base
|
||||
from keystoneclient.v3.contrib.oauth1 import utils
|
||||
|
||||
@@ -62,8 +63,9 @@ class RequestTokenManager(base.CrudManager):
|
||||
client_secret=consumer_secret,
|
||||
signature_method=oauth1.SIGNATURE_HMAC,
|
||||
callback_uri="oob")
|
||||
url = self.client.auth_url.rstrip("/") + endpoint
|
||||
url, headers, body = oauth_client.sign(url, http_method='POST',
|
||||
url = self.api.get_endpoint(interface=auth.AUTH_INTERFACE).rstrip("/")
|
||||
url, headers, body = oauth_client.sign(url + endpoint,
|
||||
http_method='POST',
|
||||
headers=headers)
|
||||
resp, body = self.client.post(endpoint, headers=headers)
|
||||
token = utils.get_oauth_token_from_body(resp.content)
|
||||
|
@@ -158,8 +158,8 @@ class UserManager(base.CrudManager):
|
||||
|
||||
base_url = '/users/%s/password' % self.api.user_id
|
||||
|
||||
return self._update(base_url, params, method='POST', management=False,
|
||||
log=False)
|
||||
return self._update(base_url, params, method='POST', log=False,
|
||||
endpoint_filter={'interface': 'public'})
|
||||
|
||||
def add_to_group(self, user, group):
|
||||
self._require_user_and_group(user, group)
|
||||
|
Reference in New Issue
Block a user