Merge "Use keystoneclient auth plugins"
This commit is contained in:
@@ -52,14 +52,14 @@ class KeystonePasswordAuthProtocol(object):
|
||||
ctx = context.RequestContext(username=username, password=password,
|
||||
tenant_id=tenant, auth_url=auth_url,
|
||||
is_admin=False)
|
||||
hc = heat_keystoneclient.KeystoneClient(ctx)
|
||||
client = hc.client
|
||||
session = heat_keystoneclient.KeystoneClient(ctx).session
|
||||
auth_ref = ctx.auth_plugin.get_access(session)
|
||||
except (keystone_exceptions.Unauthorized,
|
||||
keystone_exceptions.Forbidden,
|
||||
keystone_exceptions.NotFound,
|
||||
keystone_exceptions.AuthorizationFailure):
|
||||
return self._reject_request(env, start_response, auth_url)
|
||||
env.update(self._build_user_headers(client.auth_ref))
|
||||
env.update(self._build_user_headers(auth_ref))
|
||||
|
||||
return self.app(env, start_response)
|
||||
|
||||
|
||||
@@ -11,16 +11,60 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneclient import access
|
||||
from keystoneclient.auth.identity import base
|
||||
from keystoneclient.auth.identity import v3
|
||||
from keystoneclient.auth import token_endpoint
|
||||
from oslo.config import cfg
|
||||
from oslo.middleware import request_id as oslo_request_id
|
||||
from oslo.utils import importutils
|
||||
from oslo_context import context
|
||||
from oslo_middleware import request_id as oslo_request_id
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common.i18n import _LE
|
||||
from heat.common import policy
|
||||
from heat.common import wsgi
|
||||
from heat.db import api as db_api
|
||||
from heat.engine import clients
|
||||
from heat.openstack.common import local
|
||||
from heat.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# FIXME(jamielennox): I copied this out of a review that is proposed against
|
||||
# keystoneclient which can be used when available.
|
||||
# https://review.openstack.org/#/c/143338/
|
||||
class _AccessInfoPlugin(base.BaseIdentityPlugin):
|
||||
"""A plugin that turns an existing AccessInfo object into a usable plugin.
|
||||
|
||||
In certain circumstances you already have an auth_ref/AccessInfo object
|
||||
that you just want to reuse. This could have been from a cache, in
|
||||
auth_token middleware or other.
|
||||
|
||||
Turn that existing object into a simple identity plugin. This plugin cannot
|
||||
be refreshed as the AccessInfo object does not contain any authorizing
|
||||
information.
|
||||
|
||||
:param auth_ref: the existing AccessInfo object.
|
||||
:type auth_ref: keystoneclient.access.AccessInfo
|
||||
:param auth_url: the url where this AccessInfo was retrieved from. Required
|
||||
if using the AUTH_INTERFACE with get_endpoint. (optional)
|
||||
"""
|
||||
|
||||
def __init__(self, auth_url, auth_ref):
|
||||
super(_AccessInfoPlugin, self).__init__(auth_url=auth_url,
|
||||
reauthenticate=False)
|
||||
self.auth_ref = auth_ref
|
||||
|
||||
def get_auth_ref(self, session, **kwargs):
|
||||
return self.auth_ref
|
||||
|
||||
def invalidate(self):
|
||||
# NOTE(jamielennox): Don't allow the default invalidation to occur
|
||||
# because on next authentication request we will only get the same
|
||||
# auth_ref object again.
|
||||
return False
|
||||
|
||||
|
||||
class RequestContext(context.RequestContext):
|
||||
@@ -35,7 +79,7 @@ class RequestContext(context.RequestContext):
|
||||
read_only=False, show_deleted=False,
|
||||
overwrite=True, trust_id=None, trustor_user_id=None,
|
||||
request_id=None, auth_token_info=None, region_name=None,
|
||||
**kwargs):
|
||||
auth_plugin=None, **kwargs):
|
||||
"""
|
||||
:param overwrite: Set to False to ensure that the greenthread local
|
||||
copy of the index is not overwritten.
|
||||
@@ -66,6 +110,7 @@ class RequestContext(context.RequestContext):
|
||||
self.trust_id = trust_id
|
||||
self.trustor_user_id = trustor_user_id
|
||||
self.policy = policy.Enforcer()
|
||||
self._auth_plugin = auth_plugin
|
||||
|
||||
if is_admin is None:
|
||||
self.is_admin = self.policy.check_is_admin(self)
|
||||
@@ -110,6 +155,59 @@ class RequestContext(context.RequestContext):
|
||||
def from_dict(cls, values):
|
||||
return cls(**values)
|
||||
|
||||
@property
|
||||
def _keystone_v3_endpoint(self):
|
||||
if self.auth_url:
|
||||
auth_uri = self.auth_url
|
||||
else:
|
||||
importutils.import_module('keystonemiddleware.auth_token')
|
||||
auth_uri = cfg.CONF.keystone_authtoken.auth_uri
|
||||
|
||||
return auth_uri.replace('v2.0', 'v3')
|
||||
|
||||
def _create_auth_plugin(self):
|
||||
if self.trust_id:
|
||||
importutils.import_module('keystonemiddleware.auth_token')
|
||||
username = cfg.CONF.keystone_authtoken.admin_user
|
||||
password = cfg.CONF.keystone_authtoken.admin_password
|
||||
|
||||
return v3.Password(username=username,
|
||||
password=password,
|
||||
user_domain_id='default',
|
||||
auth_url=self._keystone_v3_endpoint,
|
||||
trust_id=self.trust_id)
|
||||
|
||||
if self.auth_token_info:
|
||||
auth_ref = access.AccessInfo.factory(body=self.auth_token_info,
|
||||
auth_token=self.auth_token)
|
||||
return _AccessInfoPlugin(self._keystone_v3_endpoint, auth_ref)
|
||||
|
||||
if self.auth_token:
|
||||
# FIXME(jamielennox): This is broken but consistent. If you
|
||||
# only have a token but don't load a service catalog then
|
||||
# url_for wont work. Stub with the keystone endpoint so at
|
||||
# least it might be right.
|
||||
return token_endpoint.Token(endpoint=self._keystone_v3_endpoint,
|
||||
token=self.auth_token)
|
||||
|
||||
if self.password:
|
||||
return v3.Password(username=self.username,
|
||||
password=self.password,
|
||||
project_id=self.tenant_id,
|
||||
user_domain_id='default',
|
||||
auth_url=self._keystone_v3_endpoint)
|
||||
|
||||
LOG.error(_LE("Keystone v3 API connection failed, no password "
|
||||
"trust or auth_token!"))
|
||||
raise exception.AuthorizationFailure()
|
||||
|
||||
@property
|
||||
def auth_plugin(self):
|
||||
if not self._auth_plugin:
|
||||
self._auth_plugin = self._create_auth_plugin()
|
||||
|
||||
return self._auth_plugin
|
||||
|
||||
|
||||
def get_admin_context(show_deleted=False):
|
||||
return RequestContext(is_admin=True, show_deleted=show_deleted)
|
||||
@@ -160,6 +258,7 @@ class ContextMiddleware(wsgi.Middleware):
|
||||
if roles is not None:
|
||||
roles = roles.split(',')
|
||||
token_info = environ.get('keystone.token_info')
|
||||
auth_plugin = environ.get('keystone.token_auth')
|
||||
req_id = environ.get(oslo_request_id.ENV_REQUEST_ID)
|
||||
|
||||
except Exception:
|
||||
@@ -175,7 +274,8 @@ class ContextMiddleware(wsgi.Middleware):
|
||||
roles=roles,
|
||||
request_id=req_id,
|
||||
auth_token_info=token_info,
|
||||
region_name=region_name)
|
||||
region_name=region_name,
|
||||
auth_plugin=auth_plugin)
|
||||
|
||||
|
||||
def ContextMiddleware_filter_factory(global_conf, **local_conf):
|
||||
|
||||
@@ -14,11 +14,9 @@
|
||||
"""Keystone Client functionality for use by resources."""
|
||||
|
||||
import collections
|
||||
import copy
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from keystoneclient.auth.identity import v3 as kc_auth_v3
|
||||
import keystoneclient.exceptions as kc_exception
|
||||
from keystoneclient import session
|
||||
from keystoneclient.v3 import client as kc_v3
|
||||
@@ -75,6 +73,8 @@ class KeystoneClientV3(object):
|
||||
self._admin_client = None
|
||||
self._domain_admin_client = None
|
||||
|
||||
self.session = session.Session.construct(self._ssl_options())
|
||||
|
||||
if self.context.auth_url:
|
||||
self.v3_endpoint = self.context.auth_url.replace('v2.0', 'v3')
|
||||
else:
|
||||
@@ -158,66 +158,25 @@ class KeystoneClientV3(object):
|
||||
return self._domain_admin_client
|
||||
|
||||
def _v3_client_init(self):
|
||||
kwargs = {
|
||||
'auth_url': self.v3_endpoint,
|
||||
'endpoint': self.v3_endpoint
|
||||
}
|
||||
# Note try trust_id first, as we can't reuse auth_token in that case
|
||||
if self.context.trust_id is not None:
|
||||
# We got a trust_id, so we use the admin credentials
|
||||
# to authenticate with the trust_id so we can use the
|
||||
# trust impersonating the trustor user.
|
||||
kwargs.update(self._service_admin_creds())
|
||||
kwargs['trust_id'] = self.context.trust_id
|
||||
kwargs.pop('project_name')
|
||||
elif self.context.auth_token_info is not None:
|
||||
# The auth_ref version must be set according to the token version
|
||||
if 'access' in self.context.auth_token_info:
|
||||
kwargs['auth_ref'] = copy.deepcopy(
|
||||
self.context.auth_token_info['access'])
|
||||
kwargs['auth_ref']['version'] = 'v2.0'
|
||||
kwargs['auth_ref']['token']['id'] = self.context.auth_token
|
||||
elif 'token' in self.context.auth_token_info:
|
||||
kwargs['auth_ref'] = copy.deepcopy(
|
||||
self.context.auth_token_info['token'])
|
||||
kwargs['auth_ref']['version'] = 'v3'
|
||||
kwargs['auth_ref']['auth_token'] = self.context.auth_token
|
||||
else:
|
||||
LOG.error(_LE('Unknown version in auth_token_info'))
|
||||
raise exception.AuthorizationFailure(
|
||||
_('Unknown token version'))
|
||||
elif self.context.auth_token is not None:
|
||||
kwargs['token'] = self.context.auth_token
|
||||
kwargs['project_id'] = self.context.tenant_id
|
||||
elif self.context.password is not None:
|
||||
kwargs['username'] = self.context.username
|
||||
kwargs['password'] = self.context.password
|
||||
kwargs['project_id'] = self.context.tenant_id
|
||||
else:
|
||||
LOG.error(_LE("Keystone v3 API connection failed, no password "
|
||||
"trust or auth_token!"))
|
||||
raise exception.AuthorizationFailure()
|
||||
kwargs.update(self._ssl_options())
|
||||
client = kc_v3.Client(**kwargs)
|
||||
client = kc_v3.Client(session=self.session,
|
||||
auth=self.context.auth_plugin)
|
||||
|
||||
# If auth_ref has already be specified via auth_token_info, don't
|
||||
# authenticate as we want to reuse, rather than request a new token
|
||||
if 'auth_ref' not in kwargs:
|
||||
if hasattr(self.context.auth_plugin, 'get_access'):
|
||||
# NOTE(jamielennox): get_access returns the current token without
|
||||
# reauthenticating if it's present and valid.
|
||||
try:
|
||||
client.authenticate()
|
||||
auth_ref = self.context.auth_plugin.get_access(self.session)
|
||||
except kc_exception.Unauthorized:
|
||||
LOG.error(_LE("Keystone client authentication failed"))
|
||||
raise exception.AuthorizationFailure()
|
||||
|
||||
# If we are authenticating with a trust set the context auth_token
|
||||
# with the trust scoped token
|
||||
if 'trust_id' in kwargs:
|
||||
if self.context.trust_id:
|
||||
# Sanity check
|
||||
if not client.auth_ref.trust_scoped:
|
||||
if not auth_ref.trust_scoped:
|
||||
LOG.error(_LE("trust token re-scoping failed!"))
|
||||
raise exception.AuthorizationFailure()
|
||||
# Sanity check that impersonation is effective
|
||||
if self.context.trustor_user_id != client.auth_ref.user_id:
|
||||
if self.context.trustor_user_id != auth_ref.user_id:
|
||||
LOG.error(_LE("Trust impersonation failed"))
|
||||
raise exception.AuthorizationFailure()
|
||||
|
||||
@@ -282,29 +241,29 @@ class KeystoneClientV3(object):
|
||||
# can't lookup the ID in keystoneclient unless they're admin
|
||||
# workaround this by getting the user_id from admin_client
|
||||
trustee_user_id = self.admin_client.auth_ref.user_id
|
||||
trustor_user_id = self.client.auth_ref.user_id
|
||||
trustor_project_id = self.client.auth_ref.project_id
|
||||
trustor = self.context.auth_plugin.get_access(self.session)
|
||||
|
||||
# inherit the roles of the trustor, unless set trusts_delegated_roles
|
||||
if cfg.CONF.trusts_delegated_roles:
|
||||
roles = cfg.CONF.trusts_delegated_roles
|
||||
else:
|
||||
roles = self.context.roles
|
||||
try:
|
||||
trust = self.client.trusts.create(trustor_user=trustor_user_id,
|
||||
trust = self.client.trusts.create(trustor_user=trustor.user_id,
|
||||
trustee_user=trustee_user_id,
|
||||
project=trustor_project_id,
|
||||
project=trustor.project_id,
|
||||
impersonation=True,
|
||||
role_names=roles)
|
||||
except kc_exception.NotFound:
|
||||
LOG.debug("Failed to find roles %s for user %s"
|
||||
% (roles, trustor_user_id))
|
||||
% (roles, trustor.user_id))
|
||||
raise exception.MissingCredentialError(
|
||||
required=_("roles %s") % roles)
|
||||
|
||||
trust_context = context.RequestContext.from_dict(
|
||||
self.context.to_dict())
|
||||
trust_context.trust_id = trust.id
|
||||
trust_context.trustor_user_id = trustor_user_id
|
||||
trust_context.trustor_user_id = trustor.user_id
|
||||
return trust_context
|
||||
|
||||
def delete_trust(self, trust_id):
|
||||
@@ -383,19 +342,6 @@ class KeystoneClientV3(object):
|
||||
# catalog (the token is expected to be used inside an instance
|
||||
# where a specific endpoint will be specified, and user-data
|
||||
# space is limited..)
|
||||
if self._stack_domain_is_id:
|
||||
auth = kc_auth_v3.Password(auth_url=self.v3_endpoint,
|
||||
user_id=user_id,
|
||||
password=password,
|
||||
project_id=project_id,
|
||||
user_domain_id=self.stack_domain)
|
||||
else:
|
||||
auth = kc_auth_v3.Password(auth_url=self.v3_endpoint,
|
||||
user_id=user_id,
|
||||
password=password,
|
||||
project_id=project_id,
|
||||
user_domain_name=self.stack_domain)
|
||||
sess = session.Session(auth=auth)
|
||||
# Note we do this directly via a post as there's currently
|
||||
# no way to get a nocatalog token via keystoneclient
|
||||
token_url = "%s/auth/tokens?nocatalog" % self.v3_endpoint
|
||||
@@ -410,8 +356,8 @@ class KeystoneClientV3(object):
|
||||
'domain': domain,
|
||||
'password': password, 'id': user_id}},
|
||||
'methods': ['password']}}}
|
||||
t = sess.post(token_url, headers=headers, json=body,
|
||||
authenticated=False)
|
||||
t = self.session.post(token_url, headers=headers,
|
||||
json=body, authenticated=False)
|
||||
return t.headers['X-Subject-Token']
|
||||
|
||||
def create_stack_domain_user(self, username, project_id, password=None):
|
||||
@@ -589,7 +535,7 @@ class KeystoneClientV3(object):
|
||||
raise ValueError("Must specify either credential_id or access")
|
||||
|
||||
def create_ec2_keypair(self, user_id=None):
|
||||
user_id = user_id or self.client.auth_ref.user_id
|
||||
user_id = user_id or self.context.get_access(self.session).user_id
|
||||
project_id = self.context.tenant_id
|
||||
data_blob = {'access': uuid.uuid4().hex,
|
||||
'secret': uuid.uuid4().hex}
|
||||
@@ -665,11 +611,11 @@ class KeystoneClientV3(object):
|
||||
default_region_name = (self.context.region_name or
|
||||
cfg.CONF.region_name_for_services)
|
||||
kwargs.setdefault('region_name', default_region_name)
|
||||
return self.client.service_catalog.url_for(**kwargs)
|
||||
return self.context.auth_plugin.get_endpoint(self.session, **kwargs)
|
||||
|
||||
@property
|
||||
def auth_token(self):
|
||||
return self.client.auth_token
|
||||
return self.context.auth_plugin.get_token(self.session)
|
||||
|
||||
|
||||
class KeystoneClient(object):
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
import abc
|
||||
|
||||
from keystoneclient import exceptions
|
||||
from oslo.config import cfg
|
||||
import six
|
||||
|
||||
@@ -41,14 +42,34 @@ class ClientPlugin(object):
|
||||
|
||||
@property
|
||||
def auth_token(self):
|
||||
# Always use the auth_token from the keystone client, as
|
||||
# this may be refreshed if the context contains credentials
|
||||
# which allow reissuing of a new token before the context
|
||||
# auth_token expiry (e.g trust_id or username/password)
|
||||
return self.clients.client('keystone').auth_token
|
||||
# NOTE(jamielennox): use the session defined by the keystoneclient
|
||||
# options as traditionally the token was always retrieved from
|
||||
# keystoneclient.
|
||||
session = self.clients.client('keystone').session
|
||||
return self.context.auth_plugin.get_token(session)
|
||||
|
||||
def url_for(self, **kwargs):
|
||||
return self.clients.client('keystone').url_for(**kwargs)
|
||||
# NOTE(jamielennox): use the session defined by the keystoneclient
|
||||
# options as traditionally the token was always retrieved from
|
||||
# keystoneclient.
|
||||
session = self.clients.client('keystone').session
|
||||
|
||||
try:
|
||||
kwargs.setdefault('interface', kwargs.pop('endpoint_type'))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
reg = self.context.region_name or cfg.CONF.region_name_for_services
|
||||
kwargs.setdefault('region_name', reg)
|
||||
|
||||
url = self.context.auth_plugin.get_endpoint(session, **kwargs)
|
||||
|
||||
# NOTE(jamielennox): raising exception maintains compatibility with
|
||||
# older keystoneclient service catalog searching.
|
||||
if url is None:
|
||||
raise exceptions.EndpointNotFound()
|
||||
|
||||
return url
|
||||
|
||||
def _get_client_option(self, client, option):
|
||||
# look for the option in the [clients_${client}] section
|
||||
|
||||
@@ -24,6 +24,7 @@ from oslotest import mockpatch
|
||||
import testscenarios
|
||||
import testtools
|
||||
|
||||
from heat.common import context
|
||||
from heat.common import messaging
|
||||
from heat.engine.clients.os import cinder
|
||||
from heat.engine.clients.os import glance
|
||||
@@ -137,6 +138,13 @@ class HeatTestCase(testscenarios.WithScenarios,
|
||||
mockfixture = self.useFixture(mockpatch.Patch(target, **kwargs))
|
||||
return mockfixture.mock
|
||||
|
||||
def stub_auth(self, ctx=None, **kwargs):
|
||||
auth = self.patchobject(ctx or context.RequestContext,
|
||||
"_create_auth_plugin")
|
||||
fake_auth = fakes.FakeAuth(**kwargs)
|
||||
auth.return_value = fake_auth
|
||||
return auth
|
||||
|
||||
def stub_keystoneclient(self, fake_client=None, **kwargs):
|
||||
client = self.patchobject(keystone.KeystoneClientPlugin, "_create")
|
||||
fkc = fake_client or fakes.FakeKeystoneClient(**kwargs)
|
||||
|
||||
@@ -19,7 +19,8 @@ wrong the tests might raise AssertionError. I've indicated in comments the
|
||||
places where actual behavior differs from the spec.
|
||||
"""
|
||||
|
||||
from keystoneclient import exceptions
|
||||
from keystoneclient import auth
|
||||
from keystoneclient import session
|
||||
|
||||
from heat.common import context
|
||||
|
||||
@@ -77,18 +78,34 @@ class FakeClient(object):
|
||||
pass
|
||||
|
||||
|
||||
class FakeAuth(auth.BaseAuthPlugin):
|
||||
|
||||
def __init__(self, auth_token='abcd1234', only_services=None):
|
||||
self.auth_token = auth_token
|
||||
self.only_services = only_services
|
||||
|
||||
def get_token(self, session, **kwargs):
|
||||
return self.auth_token
|
||||
|
||||
def get_endpoint(self, session, service_type=None, **kwargs):
|
||||
if (self.only_services is not None and
|
||||
service_type not in self.only_services):
|
||||
return None
|
||||
|
||||
return 'http://example.com:1234/v1'
|
||||
|
||||
|
||||
class FakeKeystoneClient(object):
|
||||
def __init__(self, username='test_user', password='apassword',
|
||||
user_id='1234', access='4567', secret='8901',
|
||||
credential_id='abcdxyz', auth_token='abcd1234',
|
||||
only_services=None):
|
||||
credential_id='abcdxyz'):
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.user_id = user_id
|
||||
self.access = access
|
||||
self.secret = secret
|
||||
self.session = session.Session()
|
||||
self.credential_id = credential_id
|
||||
self.only_services = only_services
|
||||
|
||||
class FakeCred(object):
|
||||
id = self.credential_id
|
||||
@@ -96,8 +113,6 @@ class FakeKeystoneClient(object):
|
||||
secret = self.secret
|
||||
self.creds = FakeCred()
|
||||
|
||||
self.auth_token = auth_token
|
||||
|
||||
def create_stack_user(self, username, password=''):
|
||||
self.username = username
|
||||
return self.user_id
|
||||
@@ -131,15 +146,6 @@ class FakeKeystoneClient(object):
|
||||
def disable_stack_user(self, user_id):
|
||||
pass
|
||||
|
||||
def url_for(self, **kwargs):
|
||||
if self.only_services is not None:
|
||||
if ('service_type' in kwargs and
|
||||
kwargs['service_type'] not in self.only_services):
|
||||
# keystone client throws keystone exceptions, not cinder
|
||||
# exceptions.
|
||||
raise exceptions.EndpointNotFound()
|
||||
return 'http://example.com:1234/v1'
|
||||
|
||||
def create_trust_context(self):
|
||||
return context.RequestContext(username=self.username,
|
||||
password=self.password,
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from keystoneclient.auth.identity import v3 as ks_v3_auth
|
||||
from keystoneclient import exceptions as keystone_exc
|
||||
from keystoneclient.v3 import client as keystone_client
|
||||
from keystoneclient import session as ks_session
|
||||
import mox
|
||||
from oslo.config import cfg
|
||||
import webob
|
||||
|
||||
@@ -76,6 +78,7 @@ TOKEN_V3_RESPONSE = {
|
||||
'id': 'tenant_id1',
|
||||
'name': 'tenant_name1',
|
||||
},
|
||||
'methods': ['password'],
|
||||
},
|
||||
'user': {
|
||||
'id': 'user_id1',
|
||||
@@ -120,21 +123,20 @@ class KeystonePasswordAuthProtocolTest(common.HeatTestCase):
|
||||
self.response_headers = dict(headers)
|
||||
|
||||
def test_valid_v2_request(self):
|
||||
mock_client = self.m.CreateMock(keystone_client.Client)
|
||||
self.m.StubOutWithMock(keystone_client, 'Client')
|
||||
keystone_client.Client(
|
||||
mock_auth = self.m.CreateMock(ks_v3_auth.Password)
|
||||
self.m.StubOutWithMock(ks_v3_auth, 'Password')
|
||||
|
||||
ks_v3_auth.Password(
|
||||
auth_url=self.config['auth_uri'],
|
||||
cacert=None,
|
||||
cert=None,
|
||||
endpoint=self.config['auth_uri'],
|
||||
insecure=False,
|
||||
key=None,
|
||||
password='goodpassword',
|
||||
project_id='tenant_id1',
|
||||
username='user_name1').AndReturn(mock_client)
|
||||
mock_client.auth_ref = TOKEN_V2_RESPONSE
|
||||
user_domain_id='default',
|
||||
username='user_name1').AndReturn(mock_auth)
|
||||
|
||||
m = mock_auth.get_access(mox.IsA(ks_session.Session))
|
||||
m.AndReturn(TOKEN_V2_RESPONSE)
|
||||
|
||||
self.app.expected_env['keystone.token_info'] = TOKEN_V2_RESPONSE
|
||||
mock_client.authenticate().AndReturn(None)
|
||||
self.m.ReplayAll()
|
||||
req = webob.Request.blank('/tenant_id1/')
|
||||
req.headers['X_AUTH_USER'] = 'user_name1'
|
||||
@@ -144,23 +146,21 @@ class KeystonePasswordAuthProtocolTest(common.HeatTestCase):
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_valid_v3_request(self):
|
||||
mock_client = self.m.CreateMock(keystone_client.Client)
|
||||
self.m.StubOutWithMock(keystone_client, 'Client')
|
||||
keystone_client.Client(
|
||||
auth_url=self.config['auth_uri'],
|
||||
cacert=None,
|
||||
cert=None,
|
||||
endpoint=self.config['auth_uri'],
|
||||
insecure=False,
|
||||
key=None,
|
||||
mock_auth = self.m.CreateMock(ks_v3_auth.Password)
|
||||
self.m.StubOutWithMock(ks_v3_auth, 'Password')
|
||||
|
||||
ks_v3_auth.Password(auth_url=self.config['auth_uri'],
|
||||
password='goodpassword',
|
||||
project_id='tenant_id1',
|
||||
username='user_name1').AndReturn(mock_client)
|
||||
mock_client.auth_ref = TOKEN_V3_RESPONSE
|
||||
user_domain_id='default',
|
||||
username='user_name1').AndReturn(mock_auth)
|
||||
|
||||
m = mock_auth.get_access(mox.IsA(ks_session.Session))
|
||||
m.AndReturn(TOKEN_V3_RESPONSE)
|
||||
|
||||
self.app.expected_env['keystone.token_info'] = {
|
||||
'token': TOKEN_V3_RESPONSE
|
||||
}
|
||||
mock_client.authenticate().AndReturn(None)
|
||||
self.m.ReplayAll()
|
||||
req = webob.Request.blank('/tenant_id1/')
|
||||
req.headers['X_AUTH_USER'] = 'user_name1'
|
||||
@@ -170,18 +170,15 @@ class KeystonePasswordAuthProtocolTest(common.HeatTestCase):
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_request_with_bad_credentials(self):
|
||||
self.m.StubOutWithMock(
|
||||
keystone_client, 'Client', use_mock_anything=True)
|
||||
keystone_client.Client(
|
||||
auth_url=self.config['auth_uri'],
|
||||
cacert=None,
|
||||
cert=None,
|
||||
endpoint=self.config['auth_uri'],
|
||||
insecure=False,
|
||||
key=None,
|
||||
self.m.StubOutWithMock(ks_v3_auth, 'Password')
|
||||
|
||||
m = ks_v3_auth.Password(auth_url=self.config['auth_uri'],
|
||||
password='badpassword',
|
||||
project_id='tenant_id1',
|
||||
username='user_name1').AndRaise(keystone_exc.Unauthorized(401))
|
||||
user_domain_id='default',
|
||||
username='user_name1')
|
||||
m.AndRaise(keystone_exc.Unauthorized(401))
|
||||
|
||||
self.m.ReplayAll()
|
||||
req = webob.Request.blank('/tenant_id1/')
|
||||
req.headers['X_AUTH_USER'] = 'user_name1'
|
||||
|
||||
@@ -31,6 +31,7 @@ from heat.common import exception
|
||||
from heat.engine import clients
|
||||
from heat.engine.clients import client_plugin
|
||||
from heat.tests import common
|
||||
from heat.tests import fakes
|
||||
from heat.tests import utils
|
||||
from heat.tests.v1_1 import fakes as fakes_v1_1
|
||||
|
||||
@@ -87,11 +88,11 @@ class ClientsTest(common.HeatTestCase):
|
||||
|
||||
@mock.patch.object(heatclient, 'Client')
|
||||
def test_clients_heat_no_auth_token(self, mock_call):
|
||||
self.stub_keystoneclient(auth_token='anewtoken')
|
||||
con = mock.Mock()
|
||||
con.auth_url = "http://auth.example.com:5000/v2.0"
|
||||
con.tenant_id = "b363706f891f48019483f8bd6503c54b"
|
||||
con.auth_token = None
|
||||
con.auth_plugin = fakes.FakeAuth(auth_token='anewtoken')
|
||||
c = clients.Clients(con)
|
||||
con.clients = c
|
||||
|
||||
@@ -102,11 +103,12 @@ class ClientsTest(common.HeatTestCase):
|
||||
|
||||
@mock.patch.object(heatclient, 'Client')
|
||||
def test_clients_heat_cached(self, mock_call):
|
||||
self.stub_keystoneclient()
|
||||
self.stub_auth()
|
||||
con = mock.Mock()
|
||||
con.auth_url = "http://auth.example.com:5000/v2.0"
|
||||
con.tenant_id = "b363706f891f48019483f8bd6503c54b"
|
||||
con.auth_token = "3bcc3d3a03f44e3d8377f9247b0ad155"
|
||||
con.trust_id = None
|
||||
c = clients.Clients(con)
|
||||
con.clients = c
|
||||
|
||||
@@ -120,22 +122,6 @@ class ClientsTest(common.HeatTestCase):
|
||||
heat_cached = obj.client()
|
||||
self.assertEqual(heat, heat_cached)
|
||||
|
||||
def test_clients_auth_token_update(self):
|
||||
fkc = self.stub_keystoneclient(auth_token='token1')
|
||||
con = mock.Mock()
|
||||
con.auth_url = "http://auth.example.com:5000/v2.0"
|
||||
con.trust_id = "b363706f891f48019483f8bd6503c54b"
|
||||
con.username = 'heat'
|
||||
con.password = 'verysecret'
|
||||
con.auth_token = None
|
||||
obj = clients.Clients(con)
|
||||
con.clients = obj
|
||||
|
||||
self.assertIsNotNone(obj.client('heat'))
|
||||
self.assertEqual('token1', obj.auth_token)
|
||||
fkc.auth_token = 'token2'
|
||||
self.assertEqual('token2', obj.auth_token)
|
||||
|
||||
|
||||
class FooClientsPlugin(client_plugin.ClientPlugin):
|
||||
|
||||
@@ -177,37 +163,37 @@ class ClientPluginTest(common.HeatTestCase):
|
||||
def test_auth_token(self):
|
||||
con = mock.Mock()
|
||||
con.auth_token = "1234"
|
||||
con.trust_id = None
|
||||
|
||||
c = clients.Clients(con)
|
||||
con.clients = c
|
||||
|
||||
c.client = mock.Mock(name="client")
|
||||
mock_keystone = mock.Mock()
|
||||
c.client.return_value = mock_keystone
|
||||
mock_keystone.auth_token = '5678'
|
||||
con.auth_plugin = mock.Mock(name="auth_plugin")
|
||||
con.auth_plugin.get_token = mock.Mock(name="get_token")
|
||||
con.auth_plugin.get_token.return_value = '5678'
|
||||
plugin = FooClientsPlugin(con)
|
||||
|
||||
# assert token is from keystone rather than context
|
||||
# assert token is from plugin rather than context
|
||||
# even though both are set
|
||||
self.assertEqual('5678', plugin.auth_token)
|
||||
c.client.assert_called_with('keystone')
|
||||
con.auth_plugin.get_token.assert_called()
|
||||
|
||||
def test_url_for(self):
|
||||
con = mock.Mock()
|
||||
con.auth_token = "1234"
|
||||
con.trust_id = None
|
||||
|
||||
c = clients.Clients(con)
|
||||
con.clients = c
|
||||
|
||||
c.client = mock.Mock(name="client")
|
||||
mock_keystone = mock.Mock()
|
||||
c.client.return_value = mock_keystone
|
||||
mock_keystone.url_for.return_value = 'http://192.0.2.1/foo'
|
||||
con.auth_plugin = mock.Mock(name="auth_plugin")
|
||||
con.auth_plugin.get_endpoint = mock.Mock(name="get_endpoint")
|
||||
con.auth_plugin.get_endpoint.return_value = 'http://192.0.2.1/foo'
|
||||
plugin = FooClientsPlugin(con)
|
||||
|
||||
self.assertEqual('http://192.0.2.1/foo',
|
||||
plugin.url_for(service_type='foo'))
|
||||
c.client.assert_called_with('keystone')
|
||||
con.auth_plugin.get_endpoint.assert_called()
|
||||
|
||||
def test_abstract_create(self):
|
||||
con = mock.Mock()
|
||||
@@ -706,19 +692,19 @@ class TestIsNotFound(common.HeatTestCase):
|
||||
class ClientAPIVersionTest(common.HeatTestCase):
|
||||
|
||||
def test_cinder_api_v1_and_v2(self):
|
||||
self.stub_keystoneclient()
|
||||
self.stub_auth()
|
||||
ctx = utils.dummy_context()
|
||||
client = clients.Clients(ctx).client('cinder')
|
||||
self.assertEqual(2, client.volume_api_version)
|
||||
|
||||
def test_cinder_api_v1_only(self):
|
||||
self.stub_keystoneclient(only_services=['volume'])
|
||||
self.stub_auth(only_services=['volume'])
|
||||
ctx = utils.dummy_context()
|
||||
client = clients.Clients(ctx).client('cinder')
|
||||
self.assertEqual(1, client.volume_api_version)
|
||||
|
||||
def test_cinder_api_v2_only(self):
|
||||
self.stub_keystoneclient(only_services=['volumev2'])
|
||||
self.stub_auth(only_services=['volumev2'])
|
||||
ctx = utils.dummy_context()
|
||||
client = clients.Clients(ctx).client('cinder')
|
||||
self.assertEqual(2, client.volume_api_version)
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from keystoneclient import access as ks_access
|
||||
from keystoneclient.auth.identity import v3 as ks_auth_v3
|
||||
from keystoneclient.auth import token_endpoint as ks_token_endpoint
|
||||
import keystoneclient.exceptions as kc_exception
|
||||
from keystoneclient import session as ks_session
|
||||
from keystoneclient.v3 import client as kc_v3
|
||||
@@ -23,6 +25,7 @@ import mox
|
||||
from oslo.config import cfg
|
||||
import six
|
||||
|
||||
from heat.common import context
|
||||
from heat.common import exception
|
||||
from heat.common import heat_keystoneclient
|
||||
from heat.tests import common
|
||||
@@ -44,6 +47,9 @@ class KeystoneClientTest(common.HeatTestCase):
|
||||
self.mock_ks_v3_client_domain_mngr = self.m.CreateMock(
|
||||
kc_v3_domains.DomainManager)
|
||||
self.m.StubOutWithMock(kc_v3, "Client")
|
||||
self.m.StubOutWithMock(ks_auth_v3, 'Password')
|
||||
self.m.StubOutWithMock(ks_token_endpoint, 'Token')
|
||||
self.m.StubOutWithMock(context, '_AccessInfoPlugin')
|
||||
|
||||
dummy_url = 'http://server.test:5000/v2.0'
|
||||
cfg.CONF.set_override('auth_uri', dummy_url,
|
||||
@@ -100,56 +106,49 @@ class KeystoneClientTest(common.HeatTestCase):
|
||||
self.mock_admin_client.auth_ref = self.m.CreateMockAnything()
|
||||
self.mock_admin_client.auth_ref.user_id = '1234'
|
||||
|
||||
def _stubs_v3(self, method='token', auth_ok=True, trust_scoped=True,
|
||||
user_id='trustor_user_id', auth_ref=None):
|
||||
def _stubs_v3(self, method='token', trust_scoped=True,
|
||||
user_id='trustor_user_id', auth_ref=None, client=True,
|
||||
times=1):
|
||||
mock_auth_ref = self.m.CreateMockAnything()
|
||||
mock_ks_auth = self.m.CreateMockAnything()
|
||||
|
||||
if method == 'token':
|
||||
kc_v3.Client(
|
||||
token='abcd1234', project_id='test_tenant_id',
|
||||
auth_url='http://server.test:5000/v3',
|
||||
endpoint='http://server.test:5000/v3',
|
||||
cacert=None,
|
||||
cert=None,
|
||||
insecure=False,
|
||||
key=None).AndReturn(self.mock_ks_v3_client)
|
||||
p = ks_token_endpoint.Token(token='abcd1234',
|
||||
endpoint='http://server.test:5000/v3')
|
||||
elif method == 'auth_ref':
|
||||
kc_v3.Client(
|
||||
auth_ref=auth_ref,
|
||||
auth_url='http://server.test:5000/v3',
|
||||
endpoint='http://server.test:5000/v3',
|
||||
cacert=None,
|
||||
cert=None,
|
||||
insecure=False,
|
||||
key=None).AndReturn(self.mock_ks_v3_client)
|
||||
p = context._AccessInfoPlugin('http://server.test:5000/v3',
|
||||
mox.IsA(ks_access.AccessInfo))
|
||||
|
||||
elif method == 'password':
|
||||
kc_v3.Client(
|
||||
p = ks_auth_v3.Password(auth_url='http://server.test:5000/v3',
|
||||
username='test_username',
|
||||
password='password',
|
||||
project_id='test_tenant_id',
|
||||
auth_url='http://server.test:5000/v3',
|
||||
endpoint='http://server.test:5000/v3',
|
||||
cacert=None,
|
||||
cert=None,
|
||||
insecure=False,
|
||||
key=None).AndReturn(self.mock_ks_v3_client)
|
||||
user_domain_id='default')
|
||||
|
||||
elif method == 'trust':
|
||||
kc_v3.Client(
|
||||
p = ks_auth_v3.Password(auth_url='http://server.test:5000/v3',
|
||||
username='heat',
|
||||
password='verybadpass',
|
||||
auth_url='http://server.test:5000/v3',
|
||||
endpoint='http://server.test:5000/v3',
|
||||
trust_id='atrust123',
|
||||
cacert=None,
|
||||
cert=None,
|
||||
insecure=False,
|
||||
key=None).AndReturn(self.mock_ks_v3_client)
|
||||
self.mock_ks_v3_client.auth_ref = self.m.CreateMockAnything()
|
||||
self.mock_ks_v3_client.auth_ref.user_id = user_id
|
||||
self.mock_ks_v3_client.auth_ref.trust_scoped = trust_scoped
|
||||
self.mock_ks_v3_client.auth_ref.auth_token = 'atrusttoken'
|
||||
user_domain_id='default',
|
||||
trust_id='atrust123')
|
||||
|
||||
if method != 'auth_ref':
|
||||
self.mock_ks_v3_client.authenticate().AndReturn(auth_ok)
|
||||
mock_auth_ref.user_id = user_id
|
||||
mock_auth_ref.trust_scoped = trust_scoped
|
||||
mock_auth_ref.auth_token = 'atrusttoken'
|
||||
|
||||
p.AndReturn(mock_ks_auth)
|
||||
|
||||
if client:
|
||||
c = kc_v3.Client(session=mox.IsA(ks_session.Session),
|
||||
auth=mock_ks_auth)
|
||||
c.AndReturn(self.mock_ks_v3_client)
|
||||
|
||||
for x in xrange(0, times):
|
||||
m = mock_ks_auth.get_access(mox.IsA(ks_session.Session))
|
||||
m.AndReturn(mock_auth_ref)
|
||||
|
||||
return mock_ks_auth, mock_auth_ref
|
||||
|
||||
def test_username_length(self):
|
||||
"""Test that user names >64 characters are properly truncated."""
|
||||
@@ -427,7 +426,8 @@ class KeystoneClientTest(common.HeatTestCase):
|
||||
ctx.password = None
|
||||
ctx.trust_id = None
|
||||
ctx.auth_token = 'ctx_token'
|
||||
ctx.auth_token_info = {'access': {'token': {'expires': '123'}}}
|
||||
ctx.auth_token_info = {'access': {
|
||||
'token': {'id': 'abcd1234', 'expires': '123'}}}
|
||||
heat_ks_client = heat_keystoneclient.KeystoneClient(ctx)
|
||||
heat_ks_client.client
|
||||
self.assertIsNotNone(heat_ks_client._client)
|
||||
@@ -437,7 +437,9 @@ class KeystoneClientTest(common.HeatTestCase):
|
||||
"""Test creating the client, token v3 auth_ref."""
|
||||
|
||||
expected_auth_ref = {'auth_token': 'ctx_token',
|
||||
'expires': '456', 'version': 'v3'}
|
||||
'expires': '456',
|
||||
'version': 'v3',
|
||||
'methods': []}
|
||||
self._stubs_v3(method='auth_ref', auth_ref=expected_auth_ref)
|
||||
self.m.ReplayAll()
|
||||
|
||||
@@ -446,7 +448,7 @@ class KeystoneClientTest(common.HeatTestCase):
|
||||
ctx.password = None
|
||||
ctx.trust_id = None
|
||||
ctx.auth_token = 'ctx_token'
|
||||
ctx.auth_token_info = {'token': {'expires': '456'}}
|
||||
ctx.auth_token_info = {'token': {'expires': '456', 'methods': []}}
|
||||
heat_ks_client = heat_keystoneclient.KeystoneClient(ctx)
|
||||
heat_ks_client.client
|
||||
self.assertIsNotNone(heat_ks_client._client)
|
||||
@@ -510,19 +512,19 @@ class KeystoneClientTest(common.HeatTestCase):
|
||||
id = 'atrust123'
|
||||
|
||||
self._stub_admin_client()
|
||||
mock_client, mock_auth_ref = self._stubs_v3(times=2)
|
||||
|
||||
self._stubs_v3()
|
||||
cfg.CONF.set_override('deferred_auth_method', 'trusts')
|
||||
if delegate_roles:
|
||||
cfg.CONF.set_override('trusts_delegated_roles', delegate_roles)
|
||||
|
||||
trustor_roles = ['heat_stack_owner', 'admin', '__member__']
|
||||
trustee_roles = delegate_roles or trustor_roles
|
||||
self.mock_ks_v3_client.auth_ref = self.m.CreateMockAnything()
|
||||
self.mock_ks_v3_client.auth_ref.user_id = '5678'
|
||||
self.mock_ks_v3_client.auth_ref.project_id = '42'
|
||||
self.mock_ks_v3_client.trusts = self.m.CreateMockAnything()
|
||||
|
||||
mock_auth_ref.user_id = '5678'
|
||||
mock_auth_ref.project_id = '42'
|
||||
|
||||
self.mock_ks_v3_client.trusts = self.m.CreateMockAnything()
|
||||
self.mock_ks_v3_client.trusts.create(
|
||||
trustor_user='5678',
|
||||
trustee_user='1234',
|
||||
@@ -543,18 +545,15 @@ class KeystoneClientTest(common.HeatTestCase):
|
||||
|
||||
"""Test create_trust_context when creating a trust."""
|
||||
|
||||
class MockTrust(object):
|
||||
id = 'atrust123'
|
||||
|
||||
self._stub_admin_client()
|
||||
|
||||
self._stubs_v3()
|
||||
mock_auth, mock_auth_ref = self._stubs_v3(times=2)
|
||||
cfg.CONF.set_override('deferred_auth_method', 'trusts')
|
||||
cfg.CONF.set_override('trusts_delegated_roles', ['heat_stack_owner'])
|
||||
|
||||
self.mock_ks_v3_client.auth_ref = self.m.CreateMockAnything()
|
||||
self.mock_ks_v3_client.auth_ref.user_id = '5678'
|
||||
self.mock_ks_v3_client.auth_ref.project_id = '42'
|
||||
mock_auth_ref.user_id = '5678'
|
||||
mock_auth_ref.project_id = '42'
|
||||
|
||||
self.mock_ks_v3_client.trusts = self.m.CreateMockAnything()
|
||||
self.mock_ks_v3_client.trusts.create(
|
||||
trustor_user='5678',
|
||||
@@ -1366,7 +1365,6 @@ class KeystoneClientTest(common.HeatTestCase):
|
||||
heat_ks_client.delete_stack_domain_project(project_id='aprojectid')
|
||||
|
||||
def _stub_domain_user_pw_auth(self):
|
||||
self.m.StubOutWithMock(ks_auth_v3, 'Password')
|
||||
ks_auth_v3.Password(auth_url='http://server.test:5000/v3',
|
||||
user_id='duser',
|
||||
password='apassw',
|
||||
@@ -1375,7 +1373,6 @@ class KeystoneClientTest(common.HeatTestCase):
|
||||
|
||||
def test_stack_domain_user_token(self):
|
||||
"""Test stack_domain_user_token function."""
|
||||
self._stub_domain_user_pw_auth()
|
||||
dummysession = self.m.CreateMockAnything()
|
||||
dummyresp = self.m.CreateMockAnything()
|
||||
dummyresp.headers = {'X-Subject-Token': 'dummytoken'}
|
||||
@@ -1384,7 +1381,7 @@ class KeystoneClientTest(common.HeatTestCase):
|
||||
headers={'Accept': 'application/json'},
|
||||
json=mox.IgnoreArg()).AndReturn(dummyresp)
|
||||
self.m.StubOutWithMock(ks_session, 'Session')
|
||||
ks_session.Session(auth='dummyauth').AndReturn(dummysession)
|
||||
ks_session.Session.construct(mox.IsA(dict)).AndReturn(dummysession)
|
||||
self.m.ReplayAll()
|
||||
|
||||
ctx = utils.dummy_context()
|
||||
@@ -1421,9 +1418,8 @@ class KeystoneClientTest(common.HeatTestCase):
|
||||
Helper function for testing url_for depending on different ways to
|
||||
pass region name.
|
||||
"""
|
||||
self._stubs_v3()
|
||||
self.mock_ks_v3_client.service_catalog = self.m.CreateMockAnything()
|
||||
self.mock_ks_v3_client.service_catalog.url_for(
|
||||
mock_ks_auth, mock_auth_ref = self._stubs_v3(client=False)
|
||||
mock_ks_auth.get_endpoint(mox.IsA(ks_session.Session),
|
||||
**expected_kwargs).AndReturn(service_url)
|
||||
|
||||
self.m.ReplayAll()
|
||||
@@ -1525,7 +1521,6 @@ class KeystoneClientTestDomainName(KeystoneClientTest):
|
||||
self.mock_admin_client.auth_ref.user_id = '1234'
|
||||
|
||||
def _stub_domain_user_pw_auth(self):
|
||||
self.m.StubOutWithMock(ks_auth_v3, 'Password')
|
||||
ks_auth_v3.Password(auth_url='http://server.test:5000/v3',
|
||||
user_id='duser',
|
||||
password='apassw',
|
||||
|
||||
@@ -972,7 +972,7 @@ class StackTest(common.HeatTestCase):
|
||||
def test_no_auth_token(self):
|
||||
ctx = utils.dummy_context()
|
||||
ctx.auth_token = None
|
||||
self.stub_keystoneclient()
|
||||
self.stub_auth()
|
||||
|
||||
self.m.ReplayAll()
|
||||
stack = parser.Stack(ctx, 'test_stack', self.tmpl)
|
||||
|
||||
@@ -77,7 +77,7 @@ class swiftTest(common.HeatTestCase):
|
||||
self.m.StubOutWithMock(sc.Connection, 'delete_object')
|
||||
self.m.StubOutWithMock(sc.Connection, 'head_container')
|
||||
self.m.StubOutWithMock(sc.Connection, 'get_auth')
|
||||
self.stub_keystoneclient()
|
||||
self.stub_auth()
|
||||
|
||||
def create_resource(self, t, stack, resource_name):
|
||||
resource_defns = stack.t.resource_definitions(stack)
|
||||
|
||||
Reference in New Issue
Block a user