Merge "Add functions for authenticating to Craton"

This commit is contained in:
Jenkins 2016-12-06 18:35:47 +00:00 committed by Gerrit Code Review
commit 02a3acb4ef
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.""" """Craton-specific session details."""
import logging import logging
from keystoneauth1 import plugin
from keystoneauth1 import session as ksa_session from keystoneauth1 import session as ksa_session
from requests import exceptions as requests_exc from requests import exceptions as requests_exc
import cratonclient import cratonclient
from cratonclient import auth
from cratonclient import exceptions as exc from cratonclient import exceptions as exc
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -46,16 +46,16 @@ class Session(object):
:param str project_id: :param str project_id:
The user's project id in Craton. The user's project id in Craton.
""" """
self._auth = None
if session is None: if session is None:
self._auth = CratonAuth(username=username, _auth = auth.CratonAuth(
project_id=project_id, username=username,
token=token) project_id=project_id,
craton_user_agent = 'python-cratonclient/{0}'.format( token=token,
cratonclient.__version__) )
session = ksa_session.Session(auth=self._auth, session = ksa_session.Session(auth=_auth)
user_agent=craton_user_agent)
self._session = session self._session = session
self._session.user_agent = 'python-cratonclient/{0}'.format(
cratonclient.__version__)
def delete(self, url, **kwargs): def delete(self, url, **kwargs):
"""Make a DELETE request with url and optional parameters. """Make a DELETE request with url and optional parameters.
@ -232,35 +232,3 @@ class Session(object):
raise exc.error_from(response) raise exc.error_from(response)
return 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.""" """Session specific unit tests."""
from keystoneauth1 import session as ksa_session from keystoneauth1 import session as ksa_session
from cratonclient import auth
from cratonclient import session from cratonclient import session
from cratonclient.tests import base from cratonclient.tests import base
@ -28,9 +29,9 @@ class TestCratonAuth(base.TestCase):
def test_stores_authentication_details(self): def test_stores_authentication_details(self):
"""Verify that our plugin stores auth details.""" """Verify that our plugin stores auth details."""
plugin = session.CratonAuth(username=TEST_USERNAME_0, plugin = auth.CratonAuth(username=TEST_USERNAME_0,
project_id=TEST_PROJECT_0, project_id=TEST_PROJECT_0,
token=TEST_TOKEN_0) token=TEST_TOKEN_0)
self.assertEqual(TEST_USERNAME_0, plugin.username) self.assertEqual(TEST_USERNAME_0, plugin.username)
self.assertEqual(TEST_PROJECT_0, plugin.project_id) self.assertEqual(TEST_PROJECT_0, plugin.project_id)
self.assertEqual(TEST_TOKEN_0, plugin.token) self.assertEqual(TEST_TOKEN_0, plugin.token)
@ -38,9 +39,9 @@ class TestCratonAuth(base.TestCase):
def test_generates_appropriate_headers(self): def test_generates_appropriate_headers(self):
"""Verify we generate the X-Auth-* headers.""" """Verify we generate the X-Auth-* headers."""
fake_session = object() fake_session = object()
plugin = session.CratonAuth(username=TEST_USERNAME_0, plugin = auth.CratonAuth(username=TEST_USERNAME_0,
project_id=TEST_PROJECT_0, project_id=TEST_PROJECT_0,
token=TEST_TOKEN_0) token=TEST_TOKEN_0)
self.assertDictEqual( self.assertDictEqual(
{ {
'X-Auth-Token': TEST_TOKEN_0, 'X-Auth-Token': TEST_TOKEN_0,
@ -53,9 +54,9 @@ class TestCratonAuth(base.TestCase):
def test_stores_token(self): def test_stores_token(self):
"""Verify get_token returns our token.""" """Verify get_token returns our token."""
fake_session = object() fake_session = object()
plugin = session.CratonAuth(username=TEST_USERNAME_0, plugin = auth.CratonAuth(username=TEST_USERNAME_0,
project_id=TEST_PROJECT_0, project_id=TEST_PROJECT_0,
token=TEST_TOKEN_0) token=TEST_TOKEN_0)
self.assertEqual(TEST_TOKEN_0, plugin.get_token(fake_session)) self.assertEqual(TEST_TOKEN_0, plugin.get_token(fake_session))
@ -63,14 +64,6 @@ class TestCratonAuth(base.TestCase):
class TestSession(base.TestCase): class TestSession(base.TestCase):
"""Unit tests for cratonclient's Session abstraction.""" """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): def test_creates_keystoneauth_session(self):
"""Verify we default to keystoneauth sessions and semantics.""" """Verify we default to keystoneauth sessions and semantics."""
craton_session = session.Session(username=TEST_USERNAME_0, 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 You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive. contain the root `toctree` directive.
Welcome to python-cratonclient's documentation! =================================================
======================================================== Welcome to python-cratonclient's documentation!
=================================================
Contents: Contents:
@ -15,6 +16,7 @@ Contents:
installation installation
usage usage
v1-api-documentation v1-api-documentation
authentication-documentation
contributing contributing
Indices and tables Indices and tables

View File

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