Implement HTTP Basic client support in keystoneauth1

A new basic auth plugin is added which enables HTTP Basic
authentication for standalone services. Like the noauth plugin, the
endpoint needs to be specified explicitly, along with the
username and password.

An example of a standalone server implementing HTTP Basic can be seen
in Ironic change https://review.opendev.org/#/c/727467/

Change-Id: Ib3f0a9c518d031a67f9605cf64a8a9cc81131ed3
Story: 2007656
Task: 39741
This commit is contained in:
Steve Baker 2020-05-26 09:17:27 +12:00
parent 646192d7fd
commit ff68663217
6 changed files with 190 additions and 0 deletions

View File

@ -226,6 +226,34 @@ you can continue as well:
sess = session.Session(auth=auth)
sess.get_token()
Standalone Plugins
------------------
Services can be deployed in a standalone environment where there is no integration
with an identity service. The following plugins are provided to support standalone
services:
- :py:class:`~keystoneauth1.http_basic.HTTPBasicAuth`: HTTP Basic authentication
- :py:class:`~keystoneauth1.noauth.NoAuth`: No authentication
Standalone plugins must be given an `endpoint` that points to the URL of the one
service being used, since there is no service catalog to look up endpoints::
from keystoneauth1 import session
from keystoneauth1 import noauth
auth = noauth.NoAuth(endpoint='http://hostname:6385/')
sess = session.Session(auth=auth)
:py:class:`~keystoneauth1.http_basic.HTTPBasicAuth` also requres a `username` and
`password`::
from keystoneauth1 import session
from keystoneauth1 import http_basic
auth = http_basic.HTTPBasicAuth(endpoint='http://hostname:6385/',
username='myUser',
password='myPassword')
sess = session.Session(auth=auth)
Federation
==========
@ -374,6 +402,8 @@ possible to specify a plugin to load via name. The authentication options that
are available are then specific to the plugin that you specified. Currently the
authentication plugins that are available in `keystoneauth` are:
- http_basic: :py:class:`keystoneauth1.http_basic.HTTPBasicAuth`
- none: :py:class:`keystoneauth1.noauth.NoAuth`
- password: :py:class:`keystoneauth1.identity.generic.Password`
- token: :py:class:`keystoneauth1.identity.generic.Token`
- v2password: :py:class:`keystoneauth1.identity.v2.Password`

View File

@ -0,0 +1,55 @@
# 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 base64
from keystoneauth1 import plugin
AUTH_HEADER_NAME = 'Authorization'
class HTTPBasicAuth(plugin.BaseAuthPlugin):
"""A provider that will always use HTTP Basic authentication.
This is useful to unify session/adapter loading for services
that might be deployed in standalone mode.
"""
def __init__(self, endpoint=None, username=None, password=None):
super(HTTPBasicAuth, self).__init__()
self.endpoint = endpoint
self.username = username
self.password = password
def get_token(self, session, **kwargs):
if self.username is None or self.password is None:
return None
token = bytes('%s:%s' % (self.username, self.password),
encoding='utf-8')
encoded = base64.b64encode(token)
return str(encoded, encoding='utf-8')
def get_headers(self, session, **kwargs):
token = self.get_token(session)
if not token:
return None
auth = 'Basic %s' % token
return {AUTH_HEADER_NAME: auth}
def get_endpoint(self, session, **kwargs):
"""Return the supplied endpoint.
Using this plugin the same endpoint is returned regardless of the
parameters passed to the plugin. endpoint_override overrides the
endpoint specified when constructing the plugin.
"""
return kwargs.get('endpoint_override') or self.endpoint

View File

@ -0,0 +1,48 @@
# 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.
from keystoneauth1 import http_basic
from keystoneauth1 import loading
class HTTPBasicAuth(loading.BaseLoader):
"""Use HTTP Basic authentication to perform requests.
This can be used to instantiate clients for services deployed in
standalone mode.
There is no fetching a service catalog or determining scope information
and so it cannot be used by clients that expect to use this scope
information.
"""
@property
def plugin_class(self):
return http_basic.HTTPBasicAuth
def get_options(self):
options = super(HTTPBasicAuth, self).get_options()
options.extend([
loading.Opt('username',
help='Username',
deprecated=[loading.Opt('user-name')]),
loading.Opt('password',
secret=True,
prompt='Password: ',
help="User's password"),
loading.Opt('endpoint',
help='The endpoint that will always be used'),
])
return options

View File

@ -0,0 +1,49 @@
# 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.
from keystoneauth1 import http_basic
from keystoneauth1.loading._plugins import http_basic as loader
from keystoneauth1 import session
from keystoneauth1.tests.unit import utils
class HTTPBasicAuthTest(utils.TestCase):
TEST_URL = 'http://server/prefix'
def test_basic_case(self):
self.requests_mock.get(self.TEST_URL, text='body')
a = http_basic.HTTPBasicAuth(username='myName', password='myPassword')
s = session.Session(auth=a)
data = s.get(self.TEST_URL, authenticated=True)
self.assertEqual(data.text, 'body')
self.assertRequestHeaderEqual(
'Authorization', 'Basic bXlOYW1lOm15UGFzc3dvcmQ=')
self.assertIsNone(a.get_endpoint(s))
def test_basic_options(self):
opts = loader.HTTPBasicAuth().get_options()
self.assertEqual(['username', 'password', 'endpoint'],
[o.name for o in opts])
def test_get_endpoint(self):
a = http_basic.HTTPBasicAuth(endpoint=self.TEST_URL)
s = session.Session(auth=a)
self.assertEqual(self.TEST_URL, a.get_endpoint(s))
def test_get_endpoint_with_override(self):
a = http_basic.HTTPBasicAuth(endpoint=self.TEST_URL)
s = session.Session(auth=a)
self.assertEqual('foo', a.get_endpoint(s, endpoint_override='foo'))

View File

@ -0,0 +1,7 @@
---
features:
- |
A new ``http_basic`` auth plugin is added which enables HTTP Basic
authentication for standalone services. Like the ``noauth`` plugin, the
``endpoint`` needs to be specified explicitly, along with the
``username`` and ``password``.

View File

@ -42,6 +42,7 @@ betamax =
keystoneauth1.plugin =
none = keystoneauth1.loading._plugins.noauth:NoAuth
http_basic = keystoneauth1.loading._plugins.http_basic:HTTPBasicAuth
password = keystoneauth1.loading._plugins.identity.generic:Password
token = keystoneauth1.loading._plugins.identity.generic:Token
admin_token = keystoneauth1.loading._plugins.admin_token:AdminToken