Add support for application credentials
Add new auth classes and loading options for application credentials. Change-Id: If267c17eecc2c4acaf62e27276afc185c1ae3616
This commit is contained in:
parent
b7496d8b23
commit
c2ae9e298e
@ -55,6 +55,9 @@ V3TOTP = v3.TOTP
|
||||
V3TokenlessAuth = v3.TokenlessAuth
|
||||
"""See :class:`keystoneauth1.identity.v3.TokenlessAuth`"""
|
||||
|
||||
V3ApplicationCredential = v3.ApplicationCredential
|
||||
"""See :class:`keystoneauth1.identity.v3.ApplicationCredential`"""
|
||||
|
||||
__all__ = ('BaseIdentityPlugin',
|
||||
'Password',
|
||||
'Token',
|
||||
@ -66,4 +69,5 @@ __all__ = ('BaseIdentityPlugin',
|
||||
'V3OidcAuthorizationCode',
|
||||
'V3OidcAccessToken',
|
||||
'V3TOTP',
|
||||
'V3TokenlessAuth')
|
||||
'V3TokenlessAuth',
|
||||
'V3ApplicationCredential')
|
||||
|
@ -10,6 +10,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneauth1.identity.v3.application_credential import * # noqa
|
||||
from keystoneauth1.identity.v3.base import * # noqa
|
||||
from keystoneauth1.identity.v3.federation import * # noqa
|
||||
from keystoneauth1.identity.v3.k2k import * # noqa
|
||||
@ -20,7 +21,10 @@ from keystoneauth1.identity.v3.totp import * # noqa
|
||||
from keystoneauth1.identity.v3.tokenless_auth import * # noqa
|
||||
|
||||
|
||||
__all__ = ('Auth',
|
||||
__all__ = ('ApplicationCredential',
|
||||
'ApplicationCredentialMethod',
|
||||
|
||||
'Auth',
|
||||
'AuthConstructor',
|
||||
'AuthMethod',
|
||||
'BaseAuth',
|
||||
|
89
keystoneauth1/identity/v3/application_credential.py
Normal file
89
keystoneauth1/identity/v3/application_credential.py
Normal file
@ -0,0 +1,89 @@
|
||||
# Copyright 2018 SUSE Linux GmbH
|
||||
#
|
||||
# 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.identity.v3 import base
|
||||
|
||||
|
||||
__all__ = ('ApplicationCredentialMethod', 'ApplicationCredential')
|
||||
|
||||
|
||||
class ApplicationCredentialMethod(base.AuthMethod):
|
||||
"""Construct a User/Passcode based authentication method.
|
||||
|
||||
:param string application_credential_secret: Application credential secret.
|
||||
:param string application_credential_id: Application credential id.
|
||||
:param string application_credential_name: The name of the application
|
||||
credential, if an ID is not
|
||||
provided.
|
||||
:param string username: Username for authentication, if an application
|
||||
credential ID is not provided.
|
||||
:param string user_id: User ID for authentication, if an application
|
||||
credential ID is not provided.
|
||||
:param string user_domain_id: User's domain ID for authentication, if an
|
||||
application credential ID is not provided.
|
||||
:param string user_domain_name: User's domain name for authentication, if
|
||||
an application credential ID is not
|
||||
provided.
|
||||
"""
|
||||
|
||||
_method_parameters = ['application_credential_secret',
|
||||
'application_credential_id',
|
||||
'application_credential_name',
|
||||
'user_id',
|
||||
'username',
|
||||
'user_domain_id',
|
||||
'user_domain_name']
|
||||
|
||||
def get_auth_data(self, session, auth, headers, **kwargs):
|
||||
auth_data = {'secret': self.application_credential_secret}
|
||||
|
||||
if self.application_credential_id:
|
||||
auth_data['id'] = self.application_credential_id
|
||||
else:
|
||||
auth_data['name'] = self.application_credential_name
|
||||
auth_data['user'] = {}
|
||||
if self.user_id:
|
||||
auth_data['user']['id'] = self.user_id
|
||||
elif self.username:
|
||||
auth_data['user']['name'] = self.username
|
||||
|
||||
if self.user_domain_id:
|
||||
auth_data['user']['domain'] = {'id': self.user_domain_id}
|
||||
elif self.user_domain_name:
|
||||
auth_data['user']['domain'] = {
|
||||
'name': self.user_domain_name}
|
||||
|
||||
return 'application_credential', auth_data
|
||||
|
||||
def get_cache_id_elements(self):
|
||||
return dict(('application_credential_%s' % p, getattr(self, p))
|
||||
for p in self._method_parameters)
|
||||
|
||||
|
||||
class ApplicationCredential(base.AuthConstructor):
|
||||
"""A plugin for authenticating with an application credential.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authentication.
|
||||
:param string application_credential_secret: Application credential secret.
|
||||
:param string application_credential_id: Application credential ID.
|
||||
:param string application_credential_name: Application credential name.
|
||||
:param string username: Username for authentication.
|
||||
:param string user_id: User ID for authentication.
|
||||
:param string user_domain_id: User's domain ID for authentication.
|
||||
:param string user_domain_name: User's domain name for authentication.
|
||||
:param bool reauthenticate: Allow fetching a new token if the current one
|
||||
is going to expire. (optional) default True
|
||||
"""
|
||||
|
||||
_auth_method_class = ApplicationCredentialMethod
|
@ -254,3 +254,43 @@ class TokenlessAuth(loading.BaseLoader):
|
||||
raise exceptions.OptionError(m)
|
||||
|
||||
return super(TokenlessAuth, self).load_from_options(**kwargs)
|
||||
|
||||
|
||||
class ApplicationCredential(loading.BaseV3Loader):
|
||||
|
||||
@property
|
||||
def plugin_class(self):
|
||||
return identity.V3ApplicationCredential
|
||||
|
||||
def get_options(self):
|
||||
options = super(ApplicationCredential, self).get_options()
|
||||
_add_common_identity_options(options)
|
||||
|
||||
options.extend([
|
||||
loading.Opt('application_credential_secret', secret=True,
|
||||
required=True,
|
||||
help="Application credential auth secret"),
|
||||
]),
|
||||
options.extend([
|
||||
loading.Opt('application_credential_id',
|
||||
help='Application credential ID'),
|
||||
]),
|
||||
options.extend([
|
||||
loading.Opt('application_credential_name',
|
||||
help='Application credential name'),
|
||||
])
|
||||
|
||||
return options
|
||||
|
||||
def load_from_options(self, **kwargs):
|
||||
_assert_identity_options(kwargs)
|
||||
if (not kwargs.get('application_credential_id') and
|
||||
not kwargs.get('application_credential_name')):
|
||||
m = ('You must provide either an application credential ID or an '
|
||||
'application credential name and user.')
|
||||
raise exceptions.OptionError(m)
|
||||
if not kwargs.get('application_credential_secret'):
|
||||
m = ('You must provide an auth secret.')
|
||||
raise exceptions.OptionError(m)
|
||||
|
||||
return super(ApplicationCredential, self).load_from_options(**kwargs)
|
||||
|
@ -33,6 +33,9 @@ class V3IdentityPlugin(utils.TestCase):
|
||||
|
||||
TEST_PASS = 'password'
|
||||
|
||||
TEST_APP_CRED_ID = 'appcredid'
|
||||
TEST_APP_CRED_SECRET = 'secret'
|
||||
|
||||
TEST_SERVICE_CATALOG = [{
|
||||
"endpoints": [{
|
||||
"url": "http://cdn.admin-nets.local:8774/v1.0/",
|
||||
@ -186,6 +189,35 @@ class V3IdentityPlugin(utils.TestCase):
|
||||
"self": "https://identity:5000/v3/projects",
|
||||
}
|
||||
}
|
||||
self.TEST_APP_CRED_TOKEN_RESPONSE = {
|
||||
"token": {
|
||||
"methods": [
|
||||
"application_credential"
|
||||
],
|
||||
|
||||
"expires_at": "2020-01-01T00:00:10.000123Z",
|
||||
"project": {
|
||||
"domain": {
|
||||
"id": self.TEST_DOMAIN_ID,
|
||||
"name": self.TEST_DOMAIN_NAME
|
||||
},
|
||||
"id": self.TEST_TENANT_ID,
|
||||
"name": self.TEST_TENANT_NAME
|
||||
},
|
||||
"user": {
|
||||
"domain": {
|
||||
"id": self.TEST_DOMAIN_ID,
|
||||
"name": self.TEST_DOMAIN_NAME
|
||||
},
|
||||
"id": self.TEST_USER,
|
||||
"name": self.TEST_USER
|
||||
},
|
||||
"issued_at": "2013-05-29T16:55:21.468960Z",
|
||||
"catalog": self.TEST_SERVICE_CATALOG,
|
||||
"service_providers": self.TEST_SERVICE_PROVIDERS,
|
||||
"application_credential_restricted": True
|
||||
},
|
||||
}
|
||||
|
||||
def stub_auth(self, subject_token=None, **kwargs):
|
||||
if not subject_token:
|
||||
@ -370,6 +402,22 @@ class V3IdentityPlugin(utils.TestCase):
|
||||
domain_id='x', trust_id='x')
|
||||
self.assertRaises(exceptions.AuthorizationFailure, a.get_auth_ref, s)
|
||||
|
||||
def test_application_credential_method(self):
|
||||
self.stub_auth(json=self.TEST_APP_CRED_TOKEN_RESPONSE)
|
||||
ac = v3.ApplicationCredential(
|
||||
self.TEST_URL, application_credential_id=self.TEST_APP_CRED_ID,
|
||||
application_credential_secret=self.TEST_APP_CRED_SECRET)
|
||||
req = {'auth': {'identity':
|
||||
{'methods': ['application_credential'],
|
||||
'application_credential': {
|
||||
'id': self.TEST_APP_CRED_ID,
|
||||
'secret': self.TEST_APP_CRED_SECRET}}}}
|
||||
s = session.Session(auth=ac)
|
||||
self.assertEqual({'X-Auth-Token': self.TEST_TOKEN},
|
||||
s.get_auth_headers())
|
||||
self.assertRequestBodyIs(json=req)
|
||||
self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN)
|
||||
|
||||
def _do_service_url_test(self, base_url, endpoint_filter):
|
||||
self.stub_auth(json=self.TEST_RESPONSE_DICT)
|
||||
self.stub_url('GET', ['path'],
|
||||
|
@ -363,3 +363,67 @@ class V3TokenlessAuthTests(utils.TestCase):
|
||||
self.assertRaises(exceptions.OptionError,
|
||||
self.create,
|
||||
project_name=uuid.uuid4().hex)
|
||||
|
||||
|
||||
class V3ApplicationCredentialTests(utils.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(V3ApplicationCredentialTests, self).setUp()
|
||||
|
||||
self.auth_url = uuid.uuid4().hex
|
||||
|
||||
def create(self, **kwargs):
|
||||
kwargs.setdefault('auth_url', self.auth_url)
|
||||
loader = loading.get_plugin_loader('v3applicationcredential')
|
||||
return loader.load_from_options(**kwargs)
|
||||
|
||||
def test_basic(self):
|
||||
id = uuid.uuid4().hex
|
||||
secret = uuid.uuid4().hex
|
||||
|
||||
app_cred = self.create(application_credential_id=id,
|
||||
application_credential_secret=secret)
|
||||
|
||||
ac_method = app_cred.auth_methods[0]
|
||||
|
||||
self.assertEqual(id, ac_method.application_credential_id)
|
||||
self.assertEqual(secret, ac_method.application_credential_secret)
|
||||
|
||||
def test_with_name(self):
|
||||
name = uuid.uuid4().hex
|
||||
secret = uuid.uuid4().hex
|
||||
username = uuid.uuid4().hex
|
||||
user_domain_id = uuid.uuid4().hex
|
||||
|
||||
app_cred = self.create(application_credential_name=name,
|
||||
application_credential_secret=secret,
|
||||
username=username,
|
||||
user_domain_id=user_domain_id)
|
||||
|
||||
ac_method = app_cred.auth_methods[0]
|
||||
|
||||
self.assertEqual(name, ac_method.application_credential_name)
|
||||
self.assertEqual(secret, ac_method.application_credential_secret)
|
||||
self.assertEqual(username, ac_method.username)
|
||||
self.assertEqual(user_domain_id, ac_method.user_domain_id)
|
||||
|
||||
def test_without_user_domain(self):
|
||||
self.assertRaises(exceptions.OptionError,
|
||||
self.create,
|
||||
application_credential_name=uuid.uuid4().hex,
|
||||
username=uuid.uuid4().hex,
|
||||
application_credential_secret=uuid.uuid4().hex)
|
||||
|
||||
def test_without_name_or_id(self):
|
||||
self.assertRaises(exceptions.OptionError,
|
||||
self.create,
|
||||
username=uuid.uuid4().hex,
|
||||
user_domain_id=uuid.uuid4().hex,
|
||||
application_credential_secret=uuid.uuid4().hex)
|
||||
|
||||
def test_without_secret(self):
|
||||
self.assertRaises(exceptions.OptionError,
|
||||
self.create,
|
||||
application_credential_id=uuid.uuid4().hex,
|
||||
username=uuid.uuid4().hex,
|
||||
user_domain_id=uuid.uuid4().hex)
|
||||
|
@ -56,6 +56,7 @@ keystoneauth1.plugin =
|
||||
v3tokenlessauth = keystoneauth1.loading._plugins.identity.v3:TokenlessAuth
|
||||
v3adfspassword = keystoneauth1.extras._saml2._loading:ADFSPassword
|
||||
v3samlpassword = keystoneauth1.extras._saml2._loading:Saml2Password
|
||||
v3applicationcredential = keystoneauth1.loading._plugins.identity.v3:ApplicationCredential
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
|
Loading…
Reference in New Issue
Block a user