Session layer with base authenticator
The session layer provides an authenticated HTTP layer for the SDK. The session is created with a transport and an authenticator. All requests are passed a service identifier as well as the normal HTTP parameters. The authenticator is used to get the token and get the endpoint for the service. The service identifier is used to get the endpoint associated with a service. The service identifier has a service type, visibiltiy and region. This code is based on the keystoneclient, but it has been simplified and some renaming has been done. Change-Id: I7184c733a5593dffb07ce4fc77e126a043274fea
This commit is contained in:
parent
f47198fca6
commit
40b23d8299
0
openstack/auth/__init__.py
Normal file
0
openstack/auth/__init__.py
Normal file
55
openstack/auth/base.py
Normal file
55
openstack/auth/base.py
Normal 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 abc
|
||||
import six
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseAuthenticator(object):
|
||||
"""The basic structure of an authenticator."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_token(self, transport, **kwargs):
|
||||
"""Obtain a token.
|
||||
|
||||
How the token is obtained is up to the authenticator. 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 implementation specific to
|
||||
an authenticator.
|
||||
|
||||
An authenticator may raise an exception if it fails to retrieve a
|
||||
token.
|
||||
|
||||
:param transport: A transport object so the authenticator can make
|
||||
HTTP calls.
|
||||
:return string: A token to use.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_endpoint(self, transport, service, **kwargs):
|
||||
"""Return an endpoint for the client.
|
||||
|
||||
There are no required keyword arguments to ``get_endpoint`` as an
|
||||
authenticator should use best effort with the information available to
|
||||
determine the endpoint.
|
||||
|
||||
:param Transport transport: A transport object so the authenticator
|
||||
can make HTTP calls
|
||||
:param ServiceIdentifier service: The object that identifies the
|
||||
service for the authenticator.
|
||||
|
||||
:returns string: The base URL that will be used to talk to the
|
||||
required service or None if not available.
|
||||
"""
|
32
openstack/auth/service.py
Normal file
32
openstack/auth/service.py
Normal file
@ -0,0 +1,32 @@
|
||||
# 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.
|
||||
|
||||
|
||||
class ServiceIdentifier(object):
|
||||
"""The basic structure of an authentication plugin."""
|
||||
|
||||
PUBLIC = 'public'
|
||||
INTERNAL = 'internal'
|
||||
ADMIN = 'admin'
|
||||
VISIBILITY = [PUBLIC, INTERNAL, ADMIN]
|
||||
|
||||
def __init__(self, service_type, visibility=PUBLIC, region=None):
|
||||
"""" Create a service identifier.
|
||||
|
||||
:param string service_type: The desired type of service.
|
||||
:param string visibility: The exposure of the endpoint. Should be
|
||||
`public` (default), `internal` or `admin`.
|
||||
:param string region: The desired region (optional).
|
||||
"""
|
||||
self.service_type = service_type
|
||||
self.visibility = visibility
|
||||
self.region = region
|
75
openstack/session.py
Normal file
75
openstack/session.py
Normal file
@ -0,0 +1,75 @@
|
||||
# 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 logging
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Session(object):
|
||||
|
||||
def __init__(self, transport, authenticator):
|
||||
"""Maintains client communication session.
|
||||
|
||||
Session layer which uses the transport for communication. The
|
||||
authenticator also uses the transport to keep authenticated.
|
||||
|
||||
:param transport: A transport layer for the session.
|
||||
:param authenticator: An authenticator to authenticate the session.
|
||||
"""
|
||||
self.transport = transport
|
||||
self.authenticator = authenticator
|
||||
|
||||
def _request(self, service, path, method, authenticate=True, **kwargs):
|
||||
"""Send an HTTP request with the specified characteristics.
|
||||
|
||||
Handle a session level request.
|
||||
|
||||
:param ServiceIdentifier service: Object that identifies service to
|
||||
the authenticator.
|
||||
:type service: :class:`openstack.auth.service.ServiceIdentifier`
|
||||
:param string path: Path relative to authentictor base url.
|
||||
:param string method: The http method to use. (eg. 'GET', 'POST').
|
||||
:param bool authenticate: True if a token should be attached
|
||||
:param kwargs: any other parameter that can be passed to transport
|
||||
and authenticator.
|
||||
|
||||
:returns: The response to the request.
|
||||
"""
|
||||
|
||||
headers = kwargs.setdefault('headers', dict())
|
||||
if authenticate:
|
||||
token = self.authenticator.get_token(self.transport)
|
||||
if token:
|
||||
headers['X-Auth-Token'] = token
|
||||
|
||||
url = self.authenticator.get_endpoint(self.transport, service) + path
|
||||
return self.transport.request(method, url, **kwargs)
|
||||
|
||||
def head(self, service, path, **kwargs):
|
||||
return self._request(service, path, 'HEAD', **kwargs)
|
||||
|
||||
def get(self, service, path, **kwargs):
|
||||
return self._request(service, path, 'GET', **kwargs)
|
||||
|
||||
def post(self, service, path, **kwargs):
|
||||
return self._request(service, path, 'POST', **kwargs)
|
||||
|
||||
def put(self, service, path, **kwargs):
|
||||
return self._request(service, path, 'PUT', **kwargs)
|
||||
|
||||
def delete(self, service, path, **kwargs):
|
||||
return self._request(service, path, 'DELETE', **kwargs)
|
||||
|
||||
def patch(self, service, path, **kwargs):
|
||||
return self._request(service, path, 'PATCH', **kwargs)
|
25
openstack/tests/fakes.py
Normal file
25
openstack/tests/fakes.py
Normal file
@ -0,0 +1,25 @@
|
||||
# Copyright 2010-2011 OpenStack Foundation
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 mock
|
||||
|
||||
|
||||
class FakeTransport(mock.Mock):
|
||||
RESPONSE = mock.Mock('200 OK')
|
||||
|
||||
def __init__(self):
|
||||
super(FakeTransport, self).__init__()
|
||||
self.request = mock.Mock()
|
||||
self.request.return_value = self.RESPONSE
|
97
openstack/tests/test_session.py
Normal file
97
openstack/tests/test_session.py
Normal file
@ -0,0 +1,97 @@
|
||||
# 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 mock
|
||||
|
||||
from openstack.auth import service
|
||||
from openstack import session
|
||||
from openstack.tests import base
|
||||
from openstack.tests import fakes
|
||||
|
||||
|
||||
class FakeAuthenticator(mock.Mock):
|
||||
TOKEN = 'fake_token'
|
||||
ENDPOINT = 'http://www.example.com/endpoint'
|
||||
|
||||
def __init__(self):
|
||||
super(FakeAuthenticator, self).__init__()
|
||||
self.get_token = mock.Mock()
|
||||
self.get_token.return_value = self.TOKEN
|
||||
self.get_endpoint = mock.Mock()
|
||||
self.get_endpoint.return_value = self.ENDPOINT
|
||||
|
||||
|
||||
class TestSession(base.TestCase):
|
||||
|
||||
TEST_PATH = '/test/path'
|
||||
|
||||
def setUp(self):
|
||||
super(TestSession, self).setUp()
|
||||
self.xport = fakes.FakeTransport()
|
||||
self.auth = FakeAuthenticator()
|
||||
self.serv = service.ServiceIdentifier('identity')
|
||||
self.sess = session.Session(self.xport, self.auth)
|
||||
self.expected = {'headers': {'X-Auth-Token': self.auth.TOKEN}}
|
||||
|
||||
def test_head(self):
|
||||
resp = self.sess.head(self.serv, self.TEST_PATH)
|
||||
|
||||
self.assertEqual(self.xport.RESPONSE, resp)
|
||||
self.auth.get_token.assert_called_with(self.xport)
|
||||
self.auth.get_endpoint.assert_called_with(self.xport, self.serv)
|
||||
url = self.auth.ENDPOINT + self.TEST_PATH
|
||||
self.xport.request.assert_called_with('HEAD', url, **self.expected)
|
||||
|
||||
def test_get(self):
|
||||
resp = self.sess.get(self.serv, self.TEST_PATH)
|
||||
|
||||
self.assertEqual(self.xport.RESPONSE, resp)
|
||||
self.auth.get_token.assert_called_with(self.xport)
|
||||
self.auth.get_endpoint.assert_called_with(self.xport, self.serv)
|
||||
url = self.auth.ENDPOINT + self.TEST_PATH
|
||||
self.xport.request.assert_called_with('GET', url, **self.expected)
|
||||
|
||||
def test_post(self):
|
||||
resp = self.sess.post(self.serv, self.TEST_PATH)
|
||||
|
||||
self.assertEqual(self.xport.RESPONSE, resp)
|
||||
self.auth.get_token.assert_called_with(self.xport)
|
||||
self.auth.get_endpoint.assert_called_with(self.xport, self.serv)
|
||||
url = self.auth.ENDPOINT + self.TEST_PATH
|
||||
self.xport.request.assert_called_with('POST', url, **self.expected)
|
||||
|
||||
def test_put(self):
|
||||
resp = self.sess.put(self.serv, self.TEST_PATH)
|
||||
|
||||
self.assertEqual(self.xport.RESPONSE, resp)
|
||||
self.auth.get_token.assert_called_with(self.xport)
|
||||
self.auth.get_endpoint.assert_called_with(self.xport, self.serv)
|
||||
url = self.auth.ENDPOINT + self.TEST_PATH
|
||||
self.xport.request.assert_called_with('PUT', url, **self.expected)
|
||||
|
||||
def test_delete(self):
|
||||
resp = self.sess.delete(self.serv, self.TEST_PATH)
|
||||
|
||||
self.assertEqual(self.xport.RESPONSE, resp)
|
||||
self.auth.get_token.assert_called_with(self.xport)
|
||||
self.auth.get_endpoint.assert_called_with(self.xport, self.serv)
|
||||
url = self.auth.ENDPOINT + self.TEST_PATH
|
||||
self.xport.request.assert_called_with('DELETE', url, **self.expected)
|
||||
|
||||
def test_patch(self):
|
||||
resp = self.sess.patch(self.serv, self.TEST_PATH)
|
||||
|
||||
self.assertEqual(self.xport.RESPONSE, resp)
|
||||
self.auth.get_token.assert_called_with(self.xport)
|
||||
self.auth.get_endpoint.assert_called_with(self.xport, self.serv)
|
||||
url = self.auth.ENDPOINT + self.TEST_PATH
|
||||
self.xport.request.assert_called_with('PATCH', url, **self.expected)
|
@ -3,6 +3,7 @@ hacking>=0.0.8,<0.9
|
||||
coverage>=3.6
|
||||
discover
|
||||
fixtures>=0.3.14
|
||||
mock>=1.0
|
||||
httpretty>=0.8.0
|
||||
python-subunit
|
||||
sphinx>=1.1.2
|
||||
|
Loading…
Reference in New Issue
Block a user