Merge "Create a discoverable plugin"
This commit is contained in:
commit
84c5ddddef
@ -230,6 +230,13 @@ def option_parser():
|
||||
default=env('OS_PASSWORD'),
|
||||
help='Authentication password (Env: OS_PASSWORD)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--os-access-info',
|
||||
dest='access_info',
|
||||
metavar='<access-info>',
|
||||
default=env('OS_ACCESS_INFO'),
|
||||
help='Access info (Env: OS_ACCESS_INFO)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--os-api-name',
|
||||
dest='user_preferences',
|
||||
|
78
openstack/auth/identity/discoverable.py
Normal file
78
openstack/auth/identity/discoverable.py
Normal file
@ -0,0 +1,78 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Identity discoverable authorization plugin must be constructed with an
|
||||
auhorization URL and a user id, user name or token. A user id or user name
|
||||
would also require a password. The arguments that apply to the selected v2
|
||||
or v3 plugin will be used. The rest of the arguments will be ignored. For
|
||||
example::
|
||||
|
||||
from openstack.auth.identity import discoverable
|
||||
from openstack import transport
|
||||
|
||||
args = {
|
||||
'password': 'openSesame',
|
||||
'auth_url': 'https://10.1.1.1:5000/v3/',
|
||||
'user_name': 'alibaba',
|
||||
}
|
||||
auth = discoverable.Auth(**args)
|
||||
xport = transport.Transport()
|
||||
accessInfo = auth.authorize(xport)
|
||||
"""
|
||||
|
||||
from openstack.auth.identity import base
|
||||
from openstack.auth.identity import v2
|
||||
from openstack.auth.identity import v3
|
||||
from openstack import exceptions
|
||||
|
||||
|
||||
class Auth(base.BaseIdentityPlugin):
|
||||
|
||||
#: Valid options for this plugin
|
||||
valid_options = list(set(v2.Auth.valid_options + v3.Auth.valid_options))
|
||||
|
||||
def __init__(self, auth_url=None, **auth_args):
|
||||
"""Construct an Identity Authentication Plugin.
|
||||
|
||||
This authorization plugin should be constructed with an auth_url
|
||||
and everything needed by either a v2 or v3 identity plugin.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authentication.
|
||||
|
||||
:raises TypeError: if a user_id, user_name or token is not provided.
|
||||
"""
|
||||
|
||||
super(Auth, self).__init__(auth_url=auth_url)
|
||||
|
||||
if not auth_url:
|
||||
msg = ("The authorization URL auth_url was not provided.")
|
||||
raise exceptions.AuthorizationFailure(msg)
|
||||
endpoint_version = auth_url.split('v')[-1][0]
|
||||
if endpoint_version == '2':
|
||||
plugin = v2.Auth
|
||||
else:
|
||||
plugin = v3.Auth
|
||||
valid_list = plugin.valid_options
|
||||
args = dict((n, auth_args[n]) for n in valid_list if n in auth_args)
|
||||
self.auth_plugin = plugin(auth_url, **args)
|
||||
|
||||
@property
|
||||
def token_url(self):
|
||||
"""The full URL where we will send authentication data."""
|
||||
return self.auth_plugin.token_url
|
||||
|
||||
def authorize(self, transport, **kwargs):
|
||||
return self.auth_plugin.authorize(transport, **kwargs)
|
||||
|
||||
def invalidate(self):
|
||||
return self.auth_plugin.invalidate()
|
@ -41,6 +41,7 @@ class Auth(base.BaseIdentityPlugin):
|
||||
|
||||
#: Valid options for this plugin
|
||||
valid_options = [
|
||||
'access_info',
|
||||
'auth_url',
|
||||
'user_name',
|
||||
'user_id',
|
||||
@ -53,6 +54,7 @@ class Auth(base.BaseIdentityPlugin):
|
||||
]
|
||||
|
||||
def __init__(self, auth_url,
|
||||
access_info=None,
|
||||
user_name=None,
|
||||
user_id=None,
|
||||
password='',
|
||||
@ -68,6 +70,7 @@ class Auth(base.BaseIdentityPlugin):
|
||||
:class:`~openstack.auth.identity.base.BaseIdentityPlugin`.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authorization.
|
||||
:param string access_info: Access info from previous authentication.
|
||||
:param string user_name: Username for authentication.
|
||||
:param string user_id: User ID for authentication.
|
||||
:param string password: Password for authentication.
|
||||
@ -86,6 +89,7 @@ class Auth(base.BaseIdentityPlugin):
|
||||
msg = 'You need to specify either a user_name, user_id or token'
|
||||
raise TypeError(msg)
|
||||
|
||||
self.access_info = access_info or None
|
||||
self.user_id = user_id
|
||||
self.user_name = user_name
|
||||
self.password = password
|
||||
@ -96,6 +100,9 @@ class Auth(base.BaseIdentityPlugin):
|
||||
|
||||
def authorize(self, transport, **kwargs):
|
||||
"""Obtain access information from an OpenStack Identity Service."""
|
||||
if self.token and self.access_info:
|
||||
return access.AccessInfoV2(**self.access_info)
|
||||
|
||||
headers = {'Accept': 'application/json'}
|
||||
url = self.auth_url.rstrip('/') + '/tokens'
|
||||
params = {'auth': self.get_auth_data(headers)}
|
||||
@ -132,6 +139,7 @@ class Auth(base.BaseIdentityPlugin):
|
||||
def invalidate(self):
|
||||
"""Invalidate the current authentication data."""
|
||||
if super(Auth, self).invalidate():
|
||||
self.access_info = None
|
||||
self.token = None
|
||||
return True
|
||||
return False
|
||||
|
@ -44,6 +44,7 @@ class Auth(base.BaseIdentityPlugin):
|
||||
|
||||
#: Valid options for this plugin
|
||||
valid_options = [
|
||||
'access_info',
|
||||
'auth_url',
|
||||
'domain_id',
|
||||
'domain_name',
|
||||
@ -62,6 +63,7 @@ class Auth(base.BaseIdentityPlugin):
|
||||
]
|
||||
|
||||
def __init__(self, auth_url,
|
||||
access_info=None,
|
||||
domain_id=None,
|
||||
domain_name=None,
|
||||
password='',
|
||||
@ -84,6 +86,7 @@ class Auth(base.BaseIdentityPlugin):
|
||||
base class :class:`~openstack.auth.identity.base.BaseIdentityPlugin`.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authentication.
|
||||
:param string access_info: Access info including service catalog.
|
||||
:param string domain_id: Domain ID for domain scoping.
|
||||
:param string domain_name: Domain name for domain scoping.
|
||||
:param string password: User password for authentication.
|
||||
@ -109,6 +112,7 @@ class Auth(base.BaseIdentityPlugin):
|
||||
msg = 'You need to specify either a user_name, user_id or token'
|
||||
raise TypeError(msg)
|
||||
|
||||
self.access_info = access_info
|
||||
self.domain_id = domain_id
|
||||
self.domain_name = domain_name
|
||||
self.project_domain_id = project_domain_id
|
||||
@ -128,6 +132,7 @@ class Auth(base.BaseIdentityPlugin):
|
||||
self.token_method = TokenMethod(token=token)
|
||||
self.auth_methods = [self.token_method]
|
||||
else:
|
||||
self.token_method = None
|
||||
self.auth_methods = [self.password_method]
|
||||
|
||||
@property
|
||||
@ -141,6 +146,10 @@ class Auth(base.BaseIdentityPlugin):
|
||||
body = {'auth': {'identity': {}}}
|
||||
ident = body['auth']['identity']
|
||||
|
||||
if self.token_method and self.access_info:
|
||||
return access.AccessInfoV3(self.token_method.token,
|
||||
**self.access_info)
|
||||
|
||||
for method in self.auth_methods:
|
||||
name, auth_data = method.get_auth_data(transport, self, headers)
|
||||
ident.setdefault('methods', []).append(name)
|
||||
@ -192,6 +201,7 @@ class Auth(base.BaseIdentityPlugin):
|
||||
"""Invalidate the current authentication data."""
|
||||
if super(Auth, self).invalidate():
|
||||
self.auth_methods = [self.password_method]
|
||||
self.access_info = None
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -63,7 +63,6 @@ import sys
|
||||
|
||||
from stevedore import driver
|
||||
|
||||
from openstack import exceptions
|
||||
from openstack import session
|
||||
from openstack import transport as xport
|
||||
|
||||
@ -142,15 +141,7 @@ class Connection(object):
|
||||
if authenticator:
|
||||
return authenticator
|
||||
if auth_plugin is None:
|
||||
if 'auth_url' not in auth_args:
|
||||
msg = ("auth_url was not provided.")
|
||||
raise exceptions.AuthorizationFailure(msg)
|
||||
auth_url = auth_args['auth_url']
|
||||
endpoint_version = auth_url.split('v')[-1][0]
|
||||
if endpoint_version == '2':
|
||||
auth_plugin = 'identity_v2'
|
||||
else:
|
||||
auth_plugin = 'identity_v3'
|
||||
auth_plugin = 'identity'
|
||||
|
||||
mgr = driver.DriverManager(
|
||||
namespace=self.AUTH_PLUGIN_NAMESPACE,
|
||||
|
97
openstack/tests/auth/identity/test_discoverable.py
Normal file
97
openstack/tests/auth/identity/test_discoverable.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
|
||||
import testtools
|
||||
|
||||
from openstack.auth.identity import discoverable
|
||||
from openstack import exceptions
|
||||
from openstack.tests.auth import common
|
||||
|
||||
|
||||
class TestDiscoverableAuth(testtools.TestCase):
|
||||
def test_valid_options(self):
|
||||
expected = [
|
||||
'access_info',
|
||||
'auth_url',
|
||||
'domain_id',
|
||||
'domain_name',
|
||||
'password',
|
||||
'project_domain_id',
|
||||
'project_domain_name',
|
||||
'project_id',
|
||||
'project_name',
|
||||
'reauthenticate',
|
||||
'token',
|
||||
'trust_id',
|
||||
'user_domain_id',
|
||||
'user_domain_name',
|
||||
'user_id',
|
||||
'user_name',
|
||||
]
|
||||
self.assertEqual(expected, sorted(discoverable.Auth.valid_options))
|
||||
|
||||
def test_create2(self):
|
||||
auth_args = {
|
||||
'auth_url': 'http://localhost/v2',
|
||||
'user_name': '1',
|
||||
'password': '2',
|
||||
}
|
||||
auth = discoverable.Auth(**auth_args)
|
||||
self.assertEqual('openstack.auth.identity.v2',
|
||||
auth.auth_plugin.__class__.__module__)
|
||||
|
||||
def test_create3(self):
|
||||
auth_args = {
|
||||
'auth_url': 'http://localhost/v3',
|
||||
'user_name': '1',
|
||||
'password': '2',
|
||||
}
|
||||
auth = discoverable.Auth(**auth_args)
|
||||
self.assertEqual('openstack.auth.identity.v3',
|
||||
auth.auth_plugin.__class__.__module__)
|
||||
|
||||
def test_create_who_knows(self):
|
||||
auth_args = {
|
||||
'auth_url': 'http://localhost:5000/',
|
||||
'user_name': '1',
|
||||
'password': '2',
|
||||
}
|
||||
auth = discoverable.Auth(**auth_args)
|
||||
self.assertEqual('openstack.auth.identity.v3',
|
||||
auth.auth_plugin.__class__.__module__)
|
||||
|
||||
def test_create_authenticator_no_nothing(self):
|
||||
self.assertRaises(
|
||||
exceptions.AuthorizationFailure,
|
||||
discoverable.Auth,
|
||||
)
|
||||
|
||||
def test_methods(self):
|
||||
auth_args = {
|
||||
'auth_url': 'http://localhost:5000/',
|
||||
'user_name': '1',
|
||||
'password': '2',
|
||||
}
|
||||
auth = discoverable.Auth(**auth_args)
|
||||
self.assertEqual('http://localhost:5000/auth/tokens', auth.token_url)
|
||||
xport = mock.MagicMock()
|
||||
xport.post = mock.Mock()
|
||||
response = mock.Mock()
|
||||
response.json = mock.Mock()
|
||||
response.json.return_value = common.TEST_RESPONSE_DICT_V3
|
||||
response.headers = {'X-Subject-Token': common.TEST_SUBJECT}
|
||||
xport.post.return_value = response
|
||||
|
||||
result = auth.authorize(xport)
|
||||
self.assertEqual(common.TEST_SUBJECT, result.auth_token)
|
||||
self.assertEqual(True, auth.invalidate())
|
@ -170,6 +170,20 @@ class TestV2Auth(testtools.TestCase):
|
||||
ecatalog['version'] = 'v2.0'
|
||||
self.assertEqual(ecatalog, resp._info)
|
||||
|
||||
def test_authorize_token_access_info(self):
|
||||
ecatalog = TEST_RESPONSE_DICT['access'].copy()
|
||||
ecatalog['version'] = 'v2.0'
|
||||
kargs = {
|
||||
'access_info': ecatalog,
|
||||
'token': common.TEST_TOKEN,
|
||||
}
|
||||
sot = v2.Auth(TEST_URL, **kargs)
|
||||
xport = self.create_mock_transport(TEST_RESPONSE_DICT)
|
||||
|
||||
resp = sot.authorize(xport)
|
||||
|
||||
self.assertEqual(ecatalog, resp._info)
|
||||
|
||||
def test_authorize_bad_response(self):
|
||||
kargs = {'token': common.TEST_TOKEN}
|
||||
sot = v2.Auth(TEST_URL, **kargs)
|
||||
@ -179,6 +193,7 @@ class TestV2Auth(testtools.TestCase):
|
||||
|
||||
def test_invalidate(self):
|
||||
kargs = {
|
||||
'access_info': {'a': 'b'},
|
||||
'password': common.TEST_PASS,
|
||||
'token': common.TEST_TOKEN,
|
||||
'user_name': common.TEST_USER,
|
||||
@ -194,11 +209,14 @@ class TestV2Auth(testtools.TestCase):
|
||||
expected = {'passwordCredentials': {'password': common.TEST_PASS,
|
||||
'username': common.TEST_USER}}
|
||||
headers = {}
|
||||
self.assertEqual(None, sot.token)
|
||||
self.assertEqual(None, sot.access_info)
|
||||
self.assertEqual(expected, sot.get_auth_data(headers))
|
||||
self.assertEqual({}, headers)
|
||||
|
||||
def test_valid_options(self):
|
||||
expected = [
|
||||
'access_info',
|
||||
'auth_url',
|
||||
'user_name',
|
||||
'user_id',
|
||||
|
@ -138,6 +138,21 @@ class TestV3Auth(testtools.TestCase):
|
||||
ecatalog['version'] = 'v3'
|
||||
self.assertEqual(ecatalog, resp._info)
|
||||
|
||||
def test_authorize_token_access_info(self):
|
||||
ecatalog = common.TEST_RESPONSE_DICT_V3['token'].copy()
|
||||
kargs = {
|
||||
'access_info': ecatalog,
|
||||
'token': common.TEST_TOKEN,
|
||||
}
|
||||
sot = v3.Auth(TEST_URL, **kargs)
|
||||
xport = self.create_mock_transport(common.TEST_RESPONSE_DICT_V3)
|
||||
|
||||
resp = sot.authorize(xport)
|
||||
|
||||
ecatalog['auth_token'] = common.TEST_TOKEN
|
||||
ecatalog['version'] = 'v3'
|
||||
self.assertEqual(ecatalog, resp._info)
|
||||
|
||||
def test_authorize_token_domain_id(self):
|
||||
kargs = {
|
||||
'domain_id': common.TEST_DOMAIN_ID,
|
||||
@ -317,6 +332,7 @@ class TestV3Auth(testtools.TestCase):
|
||||
'user_name': common.TEST_USER,
|
||||
'password': common.TEST_PASS,
|
||||
'token': common.TEST_TOKEN,
|
||||
'access_info': {},
|
||||
}
|
||||
sot = v3.Auth(TEST_URL, **kargs)
|
||||
self.assertEqual(1, len(sot.auth_methods))
|
||||
@ -325,6 +341,7 @@ class TestV3Auth(testtools.TestCase):
|
||||
|
||||
self.assertEqual(True, sot.invalidate())
|
||||
|
||||
self.assertEqual(None, sot.access_info)
|
||||
self.assertEqual(1, len(sot.auth_methods))
|
||||
auther = sot.auth_methods[0]
|
||||
self.assertEqual(common.TEST_USER, auther.user_name)
|
||||
@ -332,6 +349,7 @@ class TestV3Auth(testtools.TestCase):
|
||||
|
||||
def test_valid_options(self):
|
||||
expected = [
|
||||
'access_info',
|
||||
'auth_url',
|
||||
'domain_id',
|
||||
'domain_name',
|
||||
|
@ -62,24 +62,30 @@ class TestConnection(base.TestCase):
|
||||
self.assertEqual('1', conn.authenticator.password_method.user_name)
|
||||
self.assertEqual('2', conn.authenticator.password_method.password)
|
||||
|
||||
def test_create_authenticator_no_name_2(self):
|
||||
def test_create_authenticator_discoverable(self):
|
||||
auth_args = {
|
||||
'auth_url': '0',
|
||||
'user_name': '1',
|
||||
'password': '2',
|
||||
}
|
||||
conn = connection.Connection(transport='0', auth_plugin='identity',
|
||||
**auth_args)
|
||||
self.assertEqual('0', conn.authenticator.auth_url)
|
||||
self.assertEqual(
|
||||
'1',
|
||||
conn.authenticator.auth_plugin.password_method.user_name)
|
||||
self.assertEqual(
|
||||
'2',
|
||||
conn.authenticator.auth_plugin.password_method.password)
|
||||
|
||||
def test_create_authenticator_no_name(self):
|
||||
auth_args = {
|
||||
'auth_url': 'http://localhost/v2',
|
||||
'user_name': '1',
|
||||
'password': '2',
|
||||
}
|
||||
conn = connection.Connection(transport='0', **auth_args)
|
||||
self.assertEqual('openstack.auth.identity.v2',
|
||||
conn.authenticator.__class__.__module__)
|
||||
|
||||
def test_create_authenticator_no_name_3(self):
|
||||
auth_args = {
|
||||
'auth_url': 'http://localhost/v3',
|
||||
'user_name': '1',
|
||||
'password': '2',
|
||||
}
|
||||
conn = connection.Connection(transport='0', **auth_args)
|
||||
self.assertEqual('openstack.auth.identity.v3',
|
||||
self.assertEqual('openstack.auth.identity.discoverable',
|
||||
conn.authenticator.__class__.__module__)
|
||||
|
||||
def test_create_authenticator_no_nothing(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user