Create Authentication Plugins

Provides the framework for creating authentication plugins and using
them from a session object.

To allow this system to co-exist with the original client there is a bit
of a hack. The client object itself is now also an authentication
plugin, that supports the original client pattern. If a client is
created without a session object then that session object uses the
client as it's authentication plugin.

Change-Id: I682c8dcd3705148aaa804a91f4ed48a5b74bdc12
blueprint: auth-plugins
This commit is contained in:
Jamie Lennox 2013-12-09 16:46:09 +10:00
parent 6e21c94bb5
commit 015213d1ee
6 changed files with 133 additions and 3 deletions

View File

View File

@ -0,0 +1,38 @@
# 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
@six.add_metaclass(abc.ABCMeta)
class BaseAuthPlugin(object):
"""The basic structure of an authentication plugin."""
@abc.abstractmethod
def get_token(self, session, **kwargs):
"""Obtain a token.
How the token is obtained is up to the plugin. If it is still valid
it may be re-used, retrieved from cache or invoke an authentication
request against a server.
There are no required kwargs. They are passed directly to the auth
plugin and they are implementation specific.
Returning None will indicate that no token was able to be retrieved.
:param session: A session object so the plugin can make HTTP calls.
:return string: A token to use.
"""

View File

View File

@ -0,0 +1,60 @@
# 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 logging
import six
from keystoneclient.auth import base
LOG = logging.getLogger(__name__)
@six.add_metaclass(abc.ABCMeta)
class BaseIdentityPlugin(base.BaseAuthPlugin):
def __init__(self,
auth_url=None,
username=None,
password=None,
token=None,
trust_id=None):
super(BaseIdentityPlugin, self).__init__()
self.auth_url = auth_url
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.
This method is overridden by the various token version plugins.
This function should not be called independently and is expected to be
invoked via the do_authenticate function.
:returns AccessInfo: Token access information.
"""
def get_token(self, session, **kwargs):
if not self.auth_ref or self.auth_ref.will_expire_soon(1):
self.auth_ref = self.get_auth_ref(session, **kwargs)
return self.auth_ref.auth_token

View File

@ -49,3 +49,7 @@ class DiscoveryFailure(ClientException):
class VersionNotAvailable(DiscoveryFailure):
"""Discovery failed as the version you requested is not available."""
class MissingAuthPlugin(ClientException):
"""An authenticated request is required but no plugin available."""

View File

@ -37,14 +37,18 @@ class Session(object):
REDIRECT_STATUSES = (301, 302, 303, 305, 307)
DEFAULT_REDIRECT_LIMIT = 30
def __init__(self, session=None, original_ip=None, verify=True, cert=None,
timeout=None, user_agent=None,
def __init__(self, auth=None, session=None, original_ip=None, verify=True,
cert=None, timeout=None, user_agent=None,
redirect=DEFAULT_REDIRECT_LIMIT):
"""Maintains client communication state and common functionality.
As much as possible the parameters to this class reflect and are passed
directly to the requests library.
:param auth: An authentication plugin to authenticate the session with.
(optional, defaults to None)
:param requests.Session session: A requests session object that can be
used for issuing requests. (optional)
:param string original_ip: The original IP of the requesting user
which will be sent to identity service in a
'Forwarded' header. (optional)
@ -74,6 +78,7 @@ class Session(object):
if not session:
session = requests.Session()
self.auth = auth
self.session = session
self.original_ip = original_ip
self.verify = verify
@ -89,7 +94,8 @@ class Session(object):
self.user_agent = user_agent
def request(self, url, method, json=None, original_ip=None,
user_agent=None, redirect=None, **kwargs):
user_agent=None, redirect=None, authenticated=None,
**kwargs):
"""Send an HTTP request with the specified characteristics.
Wrapper around `requests.Session.request` to handle tasks such as
@ -111,6 +117,10 @@ class Session(object):
can be followed by a request. Either an
integer for a specific count or True/False
for forever/never. (optional)
:param bool authenticated: True if a token should be attached to this
request, False if not or None for attach if
an auth_plugin is available.
(optional, defaults to None)
:param kwargs: any other parameter that can be passed to
requests.Session.request (such as `headers`). Except:
'data' will be overwritten by the data in 'json' param.
@ -125,6 +135,17 @@ class Session(object):
headers = kwargs.setdefault('headers', dict())
if authenticated is None:
authenticated = self.auth is not None
if authenticated:
token = self.get_token()
if not token:
raise exceptions.AuthorizationFailure("No token Available")
headers['X-Auth-Token'] = token
if self.cert:
kwargs.setdefault('cert', self.cert)
@ -286,3 +307,10 @@ class Session(object):
session=kwargs.pop('session', None),
original_ip=kwargs.pop('original_ip', None),
user_agent=kwargs.pop('user_agent', None))
def get_token(self):
"""Return a token as provided by the auth plugin."""
if not self.auth:
raise exceptions.MissingAuthPlugin("Token Required")
return self.auth.get_token(self)