Add functions for authenticating to Craton

This adds a cratonclient.auth with craton_auth and keystone_auth
functions to generate cratonclient.session.Session objects with
appropriate authentication plugins set-up.

Closes-bug: 1643961
Change-Id: I661a91241b96ca5c45a91a0add4f74c4ca7e6750
This commit is contained in:
Ian Cordasco 2016-11-23 15:16:28 -06:00
parent d899af309c
commit e4a4a98496
8 changed files with 489 additions and 66 deletions

198
cratonclient/auth.py Normal file
View File

@ -0,0 +1,198 @@
# Copyright (c) 2016 Rackspace
#
# 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.
"""Module that simplifies and unifies authentication for Craton."""
from keystoneauth1.identity.v3 import password as ksa_password
from keystoneauth1 import plugin
from keystoneauth1 import session as ksa_session
from cratonclient import exceptions as exc
def craton_auth(username, token, project_id, verify=True):
"""Configure a cratonclient Session to authenticate to Craton.
This will create, configure, and return a Session object that will use
Craton's built-in authentication method.
:param str username:
The username with which to authentiate against the API.
:param str token:
The token with which to authenticate against the API.
:param str project_id:
The project ID that the user belongs to.
:param bool verify:
(Optional) Whether or not to verify HTTPS certificates provided by the
server. Default: True
:returns:
Configured cratonclient session.
:rtype:
cratonclient.session.Session
Example:
.. code-block:: python
from cratonclient import auth
from cratonclient.v1 import client
craton = client.Client(session=auth.craton_auth(
username='demo',
token='demo',
project_id='b9f10eca66ac4c279c139d01e65f96b4',
))
"""
auth_plugin = CratonAuth(
username=username,
token=token,
project_id=project_id,
)
return create_session_with(auth_plugin, verify)
def keystone_auth(auth_url, username, password, verify=True,
project_name=None, project_id=None,
project_domain_name=None, project_domain_id=None,
user_domain_name=None, user_domain_id=None,
**auth_parameters):
r"""Configure a cratonclient Session to authenticate with Keystone.
This will create, configure, and return a Session using thet appropriate
Keystone authentication plugin to be able to communicate and authenticate
to Craton.
.. note::
Presently, this function supports only V3 Password based
authentication to Keystone. We also do not validate that you specify
required attributes. For example, Keystone will require you provide
``project_name`` or ``project_id`` but we will not enforce whether or
not you've specified one.
:param str auth_url:
The URL of the Keystone instance to authenticate to.
:param str username:
The username with which we will authenticate to Keystone.
:param str password:
The password used to authenticate to Keystone.
:param str project_name:
(Optional) The name of the project the user belongs to.
:param str project_id:
(Optional) The ID of the project the user belongs to.
:param str project_domain_name:
(Optional) The name of the project's domain.
:param str project_domain_id:
(Optional) The ID of the project's domain.
:param str user_domain_name:
(Optional) The name of the user's domain.
:param str user_domain_id:
(Optional) The ID of the user's domain.
:param bool verify:
(Optional) Whether or not to verify HTTPS certificates provided by the
server. Default: True
:param \*\*auth_parameters:
Any extra authentication parameters used to authenticate to Keystone.
See the Keystone documentation for usage of:
- ``trust_id``
- ``domain_id``
- ``domain_name``
- ``reauthenticate``
:returns:
Configured cratonclient session.
:rtype:
cratonclient.session.Session
Example:
.. code-block:: python
from cratonclient import auth
from cratonclient.v1 import client
craton = client.Client(session=auth.keystone_auth(
auth_url='https://keystone.cloud.org/v3',
username='admin',
password='s3cr373p@55w0rd',
project_name='admin',
project_domain_name='Default',
user_domain_name='Default',
))
"""
password_auth = ksa_password.Password(
auth_url=auth_url,
username=username,
password=password,
project_id=project_id,
project_name=project_name,
project_domain_id=project_domain_id,
project_domain_name=project_domain_name,
user_domain_id=user_domain_id,
user_domain_name=user_domain_name,
**auth_parameters
)
return create_session_with(password_auth, verify)
def create_session_with(auth_plugin, verify):
"""Create a cratonclient Session with the specified auth and verify values.
:param auth_plugin:
The authentication plugin to use with the keystoneauth1 Session
object.
:type auth_plugin:
keystoneauth1.plugin.BaseAuthPlugin
:param bool verify:
Whether or not to verify HTTPS certificates provided by the server.
:returns:
Configured cratonclient session.
:rtype:
cratonclient.session.Session
"""
from cratonclient import session
return session.Session(session=ksa_session.Session(
auth=auth_plugin,
verify=verify,
))
class CratonAuth(plugin.BaseAuthPlugin):
"""Custom authentication plugin for keystoneauth1.
This is specifically for the case where we're not using Keystone for
authentication.
"""
def __init__(self, username, project_id, token):
"""Initialize our craton authentication class."""
self.username = username
self.project_id = project_id
self.token = token
def get_token(self, session, **kwargs):
"""Return our token."""
return self.token
def get_headers(self, session, **kwargs):
"""Return the craton authentication headers."""
headers = super(CratonAuth, self).get_headers(session, **kwargs)
if headers is None:
# NOTE(sigmavirus24): This means that the token must be None. We
# should not allow this to go further. We're using built-in Craton
# authentication (not authenticating against Keystone) so we will
# be unable to authenticate.
raise exc.UnableToAuthenticate()
headers['X-Auth-User'] = self.username
headers['X-Auth-Project'] = '{}'.format(self.project_id)
return headers

View File

@ -14,11 +14,11 @@
"""Craton-specific session details."""
import logging
from keystoneauth1 import plugin
from keystoneauth1 import session as ksa_session
from requests import exceptions as requests_exc
import cratonclient
from cratonclient import auth
from cratonclient import exceptions as exc
LOG = logging.getLogger(__name__)
@ -46,16 +46,16 @@ class Session(object):
:param str project_id:
The user's project id in Craton.
"""
self._auth = None
if session is None:
self._auth = CratonAuth(username=username,
project_id=project_id,
token=token)
craton_user_agent = 'python-cratonclient/{0}'.format(
cratonclient.__version__)
session = ksa_session.Session(auth=self._auth,
user_agent=craton_user_agent)
_auth = auth.CratonAuth(
username=username,
project_id=project_id,
token=token,
)
session = ksa_session.Session(auth=_auth)
self._session = session
self._session.user_agent = 'python-cratonclient/{0}'.format(
cratonclient.__version__)
def delete(self, url, **kwargs):
"""Make a DELETE request with url and optional parameters.
@ -232,35 +232,3 @@ class Session(object):
raise exc.error_from(response)
return response
class CratonAuth(plugin.BaseAuthPlugin):
"""Custom authentication plugin for keystoneauth1.
This is specifically for the case where we're not using Keystone for
authentication.
"""
def __init__(self, username, project_id, token):
"""Initialize our craton authentication class."""
self.username = username
self.project_id = project_id
self.token = token
def get_token(self, session, **kwargs):
"""Return our token."""
return self.token
def get_headers(self, session, **kwargs):
"""Return the craton authentication headers."""
headers = super(CratonAuth, self).get_headers(session, **kwargs)
if headers is None:
# NOTE(sigmavirus24): This means that the token must be None. We
# should not allow this to go further. We're using built-in Craton
# authentication (not authenticating against Keystone) so we will
# be unable to authenticate.
raise exc.UnableToAuthenticate()
headers['X-Auth-User'] = self.username
headers['X-Auth-Project'] = '{}'.format(self.project_id)
return headers

View File

@ -0,0 +1,59 @@
# Copyright (c) 2016 Rackspace
#
# 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.
"""Integration tests for the cratonclient.auth module."""
from oslo_utils import uuidutils
from keystoneauth1.identity.v3 import password as ksa_password
from keystoneauth1 import session as ksa_session
from cratonclient import auth
from cratonclient import session
from cratonclient.tests import base
PROJECT_ID = uuidutils.generate_uuid()
class TestAuth(base.TestCase):
"""Integration tests for the auth module functions."""
def test_craton_auth_configures_craton_session(self):
"""Verify the configuration of a cratonclient Session."""
new_session = auth.craton_auth(
username='demo',
token='demo',
project_id=PROJECT_ID,
)
self.assertIsInstance(new_session, session.Session)
keystone_session = new_session._session
self.assertIsInstance(keystone_session, ksa_session.Session)
self.assertIsInstance(keystone_session.auth, auth.CratonAuth)
def test_keystone_auth_configures_craton_session(self):
"""Verify the configuration of a cratonclient Session."""
new_session = auth.keystone_auth(
auth_url='https://identity.openstack.org/v3',
username='admin',
password='adminPassword',
project_id=PROJECT_ID,
project_domain_name='Default',
user_domain_name='Default',
)
self.assertIsInstance(new_session, session.Session)
keystone_session = new_session._session
self.assertIsInstance(keystone_session, ksa_session.Session)
self.assertIsInstance(keystone_session.auth, ksa_password.Password)

View File

@ -0,0 +1,197 @@
# Copyright (c) 2016 Rackspace
#
# 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.
"""Unit tests for the cratonclient.auth module."""
import mock
from oslo_utils import uuidutils
from cratonclient import auth
from cratonclient.tests import base
USERNAME = 'test'
TOKEN = 'fake-token'
PROJECT_ID = uuidutils.generate_uuid()
class TestCreateSessionWith(base.TestCase):
""""Tests for the create_session_with function."""
def setUp(self):
"""Set up mocks to test the create_session_with function."""
super(TestCreateSessionWith, self).setUp()
self._session_mock = mock.patch('cratonclient.session.Session')
self.session_class = self._session_mock.start()
self.addCleanup(self._session_mock.stop)
self._ksa_session_mock = mock.patch('keystoneauth1.session.Session')
self.ksa_session_class = self._ksa_session_mock.start()
self.addCleanup(self._ksa_session_mock.stop)
def test_creates_sessions(self):
"""Verify we create cratonclient and keystoneauth Sesssions."""
auth_plugin = mock.Mock()
auth.create_session_with(auth_plugin, True)
self.ksa_session_class.assert_called_once_with(
auth=auth_plugin,
verify=True,
)
self.session_class.assert_called_once_with(
session=self.ksa_session_class.return_value
)
class TestCratonAuth(base.TestCase):
"""Tests for the craton_auth function."""
def setUp(self):
"""Set up mocks to test the craton_auth function."""
super(TestCratonAuth, self).setUp()
self._create_session_with_mock = mock.patch(
'cratonclient.auth.create_session_with'
)
self.create_session_with = self._create_session_with_mock.start()
self.addCleanup(self._create_session_with_mock.stop)
self._craton_auth_mock = mock.patch('cratonclient.auth.CratonAuth')
self.craton_auth_class = self._craton_auth_mock.start()
self.addCleanup(self._craton_auth_mock.stop)
def test_creates_craton_auth_ksa_plugin(self):
"""Verify we create a new instance of CratonAuth."""
auth.craton_auth(
username='demo',
token='demo',
project_id=PROJECT_ID,
)
self.craton_auth_class.assert_called_once_with(
username='demo',
token='demo',
project_id=PROJECT_ID,
)
def test_calls_create_session_with(self):
"""Verify we call create_session_with using the right parameters."""
auth.craton_auth(
username='demo',
token='demo',
project_id=PROJECT_ID,
verify=False,
)
self.create_session_with.assert_called_once_with(
self.craton_auth_class.return_value, False
)
class TestKeystoneAuth(base.TestCase):
"""Tests for the keystone_auth function."""
def setUp(self):
"""Set up mocks to test the keystone_auth function."""
super(TestKeystoneAuth, self).setUp()
self._create_session_with_mock = mock.patch(
'cratonclient.auth.create_session_with'
)
self.create_session_with = self._create_session_with_mock.start()
self.addCleanup(self._create_session_with_mock.stop)
self._ksa_password_mock = mock.patch(
'keystoneauth1.identity.v3.password.Password'
)
self.ksa_password_class = self._ksa_password_mock.start()
self.addCleanup(self._ksa_password_mock.stop)
def test_creates_ksa_password_plugin(self):
"""Verify we create a Password keystoneauth plugin."""
auth.keystone_auth(
auth_url='https://identity.openstack.org/v3',
username='admin',
password='adminPassword',
project_name='admin',
project_domain_name='Default',
user_domain_name='Default',
)
self.ksa_password_class.assert_called_once_with(
auth_url='https://identity.openstack.org/v3',
username='admin',
password='adminPassword',
project_name='admin',
project_domain_name='Default',
user_domain_name='Default',
project_id=None,
project_domain_id=None,
user_domain_id=None,
)
def test_calls_create_session_with(self):
"""Verify we call create_session_with using the right parameters."""
auth.keystone_auth(
auth_url='https://identity.openstack.org/v3',
username='admin',
password='adminPassword',
project_name='admin',
project_domain_name='Default',
user_domain_name='Default',
verify=False,
)
self.create_session_with.assert_called_once_with(
self.ksa_password_class.return_value, False
)
class TestCratonAuthPlugin(base.TestCase):
"""Craton authentication keystoneauth plugin tests."""
def test_stores_authentication_details(self):
"""Verify that our plugin stores auth details."""
plugin = auth.CratonAuth(
username=USERNAME,
project_id=PROJECT_ID,
token=TOKEN,
)
self.assertEqual(USERNAME, plugin.username)
self.assertEqual(PROJECT_ID, plugin.project_id)
self.assertEqual(TOKEN, plugin.token)
def test_generates_appropriate_headers(self):
"""Verify we generate the X-Auth-* headers."""
fake_session = object()
plugin = auth.CratonAuth(
username=USERNAME,
project_id=PROJECT_ID,
token=TOKEN,
)
self.assertDictEqual(
{
'X-Auth-Token': TOKEN,
'X-Auth-User': USERNAME,
'X-Auth-Project': '{}'.format(PROJECT_ID),
},
plugin.get_headers(fake_session)
)
def test_stores_token(self):
"""Verify get_token returns our token."""
fake_session = object()
plugin = auth.CratonAuth(
username=USERNAME,
project_id=PROJECT_ID,
token=TOKEN,
)
self.assertEqual(TOKEN, plugin.get_token(fake_session))

View File

@ -14,6 +14,7 @@
"""Session specific unit tests."""
from keystoneauth1 import session as ksa_session
from cratonclient import auth
from cratonclient import session
from cratonclient.tests import base
@ -28,9 +29,9 @@ class TestCratonAuth(base.TestCase):
def test_stores_authentication_details(self):
"""Verify that our plugin stores auth details."""
plugin = session.CratonAuth(username=TEST_USERNAME_0,
project_id=TEST_PROJECT_0,
token=TEST_TOKEN_0)
plugin = auth.CratonAuth(username=TEST_USERNAME_0,
project_id=TEST_PROJECT_0,
token=TEST_TOKEN_0)
self.assertEqual(TEST_USERNAME_0, plugin.username)
self.assertEqual(TEST_PROJECT_0, plugin.project_id)
self.assertEqual(TEST_TOKEN_0, plugin.token)
@ -38,9 +39,9 @@ class TestCratonAuth(base.TestCase):
def test_generates_appropriate_headers(self):
"""Verify we generate the X-Auth-* headers."""
fake_session = object()
plugin = session.CratonAuth(username=TEST_USERNAME_0,
project_id=TEST_PROJECT_0,
token=TEST_TOKEN_0)
plugin = auth.CratonAuth(username=TEST_USERNAME_0,
project_id=TEST_PROJECT_0,
token=TEST_TOKEN_0)
self.assertDictEqual(
{
'X-Auth-Token': TEST_TOKEN_0,
@ -53,9 +54,9 @@ class TestCratonAuth(base.TestCase):
def test_stores_token(self):
"""Verify get_token returns our token."""
fake_session = object()
plugin = session.CratonAuth(username=TEST_USERNAME_0,
project_id=TEST_PROJECT_0,
token=TEST_TOKEN_0)
plugin = auth.CratonAuth(username=TEST_USERNAME_0,
project_id=TEST_PROJECT_0,
token=TEST_TOKEN_0)
self.assertEqual(TEST_TOKEN_0, plugin.get_token(fake_session))
@ -63,14 +64,6 @@ class TestCratonAuth(base.TestCase):
class TestSession(base.TestCase):
"""Unit tests for cratonclient's Session abstraction."""
def test_creates_craton_auth_plugin(self):
"""Verify we default to using keystoneauth plugin auth."""
craton_session = session.Session(username=TEST_USERNAME_0,
project_id=TEST_PROJECT_0,
token=TEST_TOKEN_0)
self.assertIsInstance(craton_session._auth, session.CratonAuth)
def test_creates_keystoneauth_session(self):
"""Verify we default to keystoneauth sessions and semantics."""
craton_session = session.Session(username=TEST_USERNAME_0,

View File

@ -0,0 +1,7 @@
=================================
cratonclient.auth Documentation
=================================
.. autofunction:: cratonclient.auth.craton_auth
.. autofunction:: cratonclient.auth.keystone_auth

View File

@ -3,8 +3,9 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to python-cratonclient's documentation!
========================================================
=================================================
Welcome to python-cratonclient's documentation!
=================================================
Contents:
@ -15,6 +16,7 @@ Contents:
installation
usage
v1-api-documentation
authentication-documentation
contributing
Indices and tables

View File

@ -35,12 +35,12 @@ authentication, you need only do the following:
.. code-block:: python
from cratonclient import session
from cratonclient import auth
from cratonclient.v1 import client
craton_session = session.Session(
craton_session = auth.craton_auth(
username=USERNAME,
password=TOKEN,
token=TOKEN,
project_id=PROJECT_ID,
)
@ -74,10 +74,10 @@ Then, we need to do the following:
from keystoneauth1.identity.v3 import password as password_auth
from keystoneauth1 import session as ksa_session
from cratonclient import session
from cratonclient import auth
from cratonclient.v1 import client
_auth = password_auth.Password(
craton_session = auth.keystone_auth(
auth_url=AUTH_URL,
password=PASSWORD,
username=USERNAME,
@ -85,7 +85,6 @@ Then, we need to do the following:
project_name=PROJECT_NAME,
project_domain_name=PROJECT_DOMAIN_NAME,
)
craton_session = session.Session(session=ksa_session.Session(auth=_auth))
craton = client.Client(
session=craton_session,
url=URL,