Create V2 Auth Plugins
Extract the authentication code from a v2 client and move it to a series of auth plugins. Auth plugins each represent one method of authenticating with a server and there is a factory method on the base class to select the appropriate plugin from a group of arguments. When a v2 client wants to do authentication it will create a new v2 auth plugin, do the authentication and then take that result for the client to use. Change-Id: I4dd7474643ed5c2a3204ea2ec56029f926010c2c blueprint: auth-plugins
This commit is contained in:
parent
80ef7644c1
commit
3ddd1e6beb
@ -22,6 +22,9 @@ LOG = logging.getLogger(__name__)
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseIdentityPlugin(base.BaseAuthPlugin):
|
||||
|
||||
# we count a token as valid if it is valid for at least this many seconds
|
||||
MIN_TOKEN_LIFE_SECONDS = 1
|
||||
|
||||
def __init__(self,
|
||||
auth_url=None,
|
||||
username=None,
|
||||
@ -32,13 +35,15 @@ class BaseIdentityPlugin(base.BaseAuthPlugin):
|
||||
super(BaseIdentityPlugin, self).__init__()
|
||||
|
||||
self.auth_url = auth_url
|
||||
self.auth_ref = None
|
||||
|
||||
# NOTE(jamielennox): DEPRECATED. The following should not really be set
|
||||
# here but handled by the individual auth plugin.
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.token = token
|
||||
self.trust_id = trust_id
|
||||
|
||||
self.auth_ref = None
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_auth_ref(self, session, **kwargs):
|
||||
"""Obtain a token from an OpenStack Identity Service.
|
||||
@ -48,11 +53,40 @@ class BaseIdentityPlugin(base.BaseAuthPlugin):
|
||||
This function should not be called independently and is expected to be
|
||||
invoked via the do_authenticate function.
|
||||
|
||||
This function will be invoked if the AcessInfo object cached by the
|
||||
plugin is not valid. Thus plugins should always fetch a new AccessInfo
|
||||
when invoked. If you are looking to just retrieve the current auth
|
||||
data then you should use get_access.
|
||||
|
||||
:raises HTTPError: An error from an invalid HTTP response.
|
||||
|
||||
:returns AccessInfo: Token access information.
|
||||
"""
|
||||
|
||||
def get_token(self, session, **kwargs):
|
||||
if not self.auth_ref or self.auth_ref.will_expire_soon(1):
|
||||
"""Return a valid auth token.
|
||||
|
||||
If a valid token is not present then a new one will be fetched using
|
||||
the session and kwargs.
|
||||
|
||||
:raises HTTPError: An error from an invalid HTTP response.
|
||||
|
||||
:return string: A valid token.
|
||||
"""
|
||||
return self.get_access(session, **kwargs).auth_token
|
||||
|
||||
def get_access(self, session, **kwargs):
|
||||
"""Fetch or return a current AccessInfo object.
|
||||
|
||||
If a valid AccessInfo is present then it is returned otherwise kwargs
|
||||
and session are used to fetch a new one.
|
||||
|
||||
:raises HTTPError: An error from an invalid HTTP response.
|
||||
|
||||
:returns AccessInfo: Valid AccessInfo
|
||||
"""
|
||||
if (not self.auth_ref or
|
||||
self.auth_ref.will_expire_soon(self.MIN_TOKEN_LIFE_SECONDS)):
|
||||
self.auth_ref = self.get_auth_ref(session, **kwargs)
|
||||
|
||||
return self.auth_ref.auth_token
|
||||
return self.auth_ref
|
||||
|
129
keystoneclient/auth/identity/v2.py
Normal file
129
keystoneclient/auth/identity/v2.py
Normal file
@ -0,0 +1,129 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import abc
|
||||
|
||||
import six
|
||||
|
||||
from keystoneclient import access
|
||||
from keystoneclient.auth.identity import base
|
||||
from keystoneclient import exceptions
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Auth(base.BaseIdentityPlugin):
|
||||
|
||||
@staticmethod
|
||||
def factory(auth_url, **kwargs):
|
||||
"""Construct a plugin appropriate to your available arguments.
|
||||
|
||||
This function should only be used for loading authentication from a
|
||||
config file or other source where you do not know the type of plugin
|
||||
that is required.
|
||||
|
||||
If you know the style of authorization you require then you should
|
||||
construct that plugin directly.
|
||||
|
||||
:raises NoMatchingPlugin: if a plugin cannot be constructed.
|
||||
|
||||
return Auth: a plugin that can be passed to a session.
|
||||
"""
|
||||
username = kwargs.pop('username', None)
|
||||
password = kwargs.pop('password', None)
|
||||
token = kwargs.pop('token', None)
|
||||
|
||||
if token:
|
||||
return Token(auth_url, token, **kwargs)
|
||||
elif username and password:
|
||||
return Password(auth_url, username, password, **kwargs)
|
||||
|
||||
msg = 'A username and password or token is required.'
|
||||
raise exceptions.NoMatchingPlugin(msg)
|
||||
|
||||
def __init__(self, auth_url,
|
||||
trust_id=None,
|
||||
tenant_id=None,
|
||||
tenant_name=None):
|
||||
"""Construct an Identity V2 Authentication Plugin.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authorization.
|
||||
:param string trust_id: Trust ID for trust scoping.
|
||||
:param string tenant_id: Tenant ID for project scoping.
|
||||
:param string tenant_name: Tenant name for project scoping.
|
||||
"""
|
||||
super(Auth, self).__init__(auth_url=auth_url)
|
||||
|
||||
self.trust_id = trust_id
|
||||
self.tenant_id = tenant_id
|
||||
self.tenant_name = tenant_name
|
||||
|
||||
def get_auth_ref(self, session, **kwargs):
|
||||
headers = {}
|
||||
url = self.auth_url + '/tokens'
|
||||
params = {'auth': self.get_auth_data(headers)}
|
||||
|
||||
if self.tenant_id:
|
||||
params['auth']['tenantId'] = self.tenant_id
|
||||
elif self.tenant_name:
|
||||
params['auth']['tenantName'] = self.tenant_name
|
||||
if self.trust_id:
|
||||
params['auth']['trust_id'] = self.trust_id
|
||||
|
||||
resp = session.post(url, json=params, headers=headers,
|
||||
authenticated=False)
|
||||
return access.AccessInfoV2(**resp.json()['access'])
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_auth_data(self, headers=None):
|
||||
"""Return the authentication section of an auth plugin.
|
||||
|
||||
:param dict headers: The headers that will be sent with the auth
|
||||
request if a plugin needs to add to them.
|
||||
:return dict: A dict of authentication data for the auth type.
|
||||
"""
|
||||
|
||||
|
||||
class Password(Auth):
|
||||
|
||||
def __init__(self, auth_url, username, password, **kwargs):
|
||||
"""A plugin for authenticating with a username and password.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authorization.
|
||||
:param string username: Username for authentication.
|
||||
:param string password: Password for authentication.
|
||||
"""
|
||||
super(Password, self).__init__(auth_url, **kwargs)
|
||||
self.username = username
|
||||
self.password = password
|
||||
|
||||
def get_auth_data(self, headers=None):
|
||||
return {'passwordCredentials': {'username': self.username,
|
||||
'password': self.password}}
|
||||
|
||||
|
||||
class Token(Auth):
|
||||
|
||||
def __init__(self, auth_url, token, **kwargs):
|
||||
"""A plugin for authenticating with an existing token.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authorization.
|
||||
:param string token: Existing token for authentication.
|
||||
"""
|
||||
super(Token, self).__init__(auth_url, **kwargs)
|
||||
self.token = token
|
||||
|
||||
def get_auth_data(self, headers=None):
|
||||
if headers is not None:
|
||||
headers['X-Auth-Token'] = self.token
|
||||
return {'token': {'id': self.token}}
|
@ -51,3 +51,8 @@ class VersionNotAvailable(DiscoveryFailure):
|
||||
|
||||
class MissingAuthPlugin(ClientException):
|
||||
"""An authenticated request is required but no plugin available."""
|
||||
|
||||
|
||||
class NoMatchingPlugin(ClientException):
|
||||
"""There were no auth plugins that could be created from the parameters
|
||||
provided."""
|
||||
|
@ -314,8 +314,17 @@ class Session(object):
|
||||
user_agent=kwargs.pop('user_agent', None))
|
||||
|
||||
def get_token(self):
|
||||
"""Return a token as provided by the auth plugin."""
|
||||
"""Return a token as provided by the auth plugin.
|
||||
|
||||
:raises AuthorizationFailure: if a new token fetch fails.
|
||||
|
||||
:returns string: A valid token.
|
||||
"""
|
||||
if not self.auth:
|
||||
raise exceptions.MissingAuthPlugin("Token Required")
|
||||
|
||||
return self.auth.get_token(self)
|
||||
try:
|
||||
return self.auth.get_token(self)
|
||||
except exceptions.HTTPError as exc:
|
||||
raise exceptions.AuthorizationFailure("Authentication failure: "
|
||||
"%s" % exc)
|
||||
|
Loading…
Reference in New Issue
Block a user