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
|
V3TokenlessAuth = v3.TokenlessAuth
|
||||||
"""See :class:`keystoneauth1.identity.v3.TokenlessAuth`"""
|
"""See :class:`keystoneauth1.identity.v3.TokenlessAuth`"""
|
||||||
|
|
||||||
|
V3ApplicationCredential = v3.ApplicationCredential
|
||||||
|
"""See :class:`keystoneauth1.identity.v3.ApplicationCredential`"""
|
||||||
|
|
||||||
__all__ = ('BaseIdentityPlugin',
|
__all__ = ('BaseIdentityPlugin',
|
||||||
'Password',
|
'Password',
|
||||||
'Token',
|
'Token',
|
||||||
@ -66,4 +69,5 @@ __all__ = ('BaseIdentityPlugin',
|
|||||||
'V3OidcAuthorizationCode',
|
'V3OidcAuthorizationCode',
|
||||||
'V3OidcAccessToken',
|
'V3OidcAccessToken',
|
||||||
'V3TOTP',
|
'V3TOTP',
|
||||||
'V3TokenlessAuth')
|
'V3TokenlessAuth',
|
||||||
|
'V3ApplicationCredential')
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from keystoneauth1.identity.v3.application_credential import * # noqa
|
||||||
from keystoneauth1.identity.v3.base import * # noqa
|
from keystoneauth1.identity.v3.base import * # noqa
|
||||||
from keystoneauth1.identity.v3.federation import * # noqa
|
from keystoneauth1.identity.v3.federation import * # noqa
|
||||||
from keystoneauth1.identity.v3.k2k 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
|
from keystoneauth1.identity.v3.tokenless_auth import * # noqa
|
||||||
|
|
||||||
|
|
||||||
__all__ = ('Auth',
|
__all__ = ('ApplicationCredential',
|
||||||
|
'ApplicationCredentialMethod',
|
||||||
|
|
||||||
|
'Auth',
|
||||||
'AuthConstructor',
|
'AuthConstructor',
|
||||||
'AuthMethod',
|
'AuthMethod',
|
||||||
'BaseAuth',
|
'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)
|
raise exceptions.OptionError(m)
|
||||||
|
|
||||||
return super(TokenlessAuth, self).load_from_options(**kwargs)
|
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_PASS = 'password'
|
||||||
|
|
||||||
|
TEST_APP_CRED_ID = 'appcredid'
|
||||||
|
TEST_APP_CRED_SECRET = 'secret'
|
||||||
|
|
||||||
TEST_SERVICE_CATALOG = [{
|
TEST_SERVICE_CATALOG = [{
|
||||||
"endpoints": [{
|
"endpoints": [{
|
||||||
"url": "http://cdn.admin-nets.local:8774/v1.0/",
|
"url": "http://cdn.admin-nets.local:8774/v1.0/",
|
||||||
@ -186,6 +189,35 @@ class V3IdentityPlugin(utils.TestCase):
|
|||||||
"self": "https://identity:5000/v3/projects",
|
"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):
|
def stub_auth(self, subject_token=None, **kwargs):
|
||||||
if not subject_token:
|
if not subject_token:
|
||||||
@ -370,6 +402,22 @@ class V3IdentityPlugin(utils.TestCase):
|
|||||||
domain_id='x', trust_id='x')
|
domain_id='x', trust_id='x')
|
||||||
self.assertRaises(exceptions.AuthorizationFailure, a.get_auth_ref, s)
|
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):
|
def _do_service_url_test(self, base_url, endpoint_filter):
|
||||||
self.stub_auth(json=self.TEST_RESPONSE_DICT)
|
self.stub_auth(json=self.TEST_RESPONSE_DICT)
|
||||||
self.stub_url('GET', ['path'],
|
self.stub_url('GET', ['path'],
|
||||||
|
@ -363,3 +363,67 @@ class V3TokenlessAuthTests(utils.TestCase):
|
|||||||
self.assertRaises(exceptions.OptionError,
|
self.assertRaises(exceptions.OptionError,
|
||||||
self.create,
|
self.create,
|
||||||
project_name=uuid.uuid4().hex)
|
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
|
v3tokenlessauth = keystoneauth1.loading._plugins.identity.v3:TokenlessAuth
|
||||||
v3adfspassword = keystoneauth1.extras._saml2._loading:ADFSPassword
|
v3adfspassword = keystoneauth1.extras._saml2._loading:ADFSPassword
|
||||||
v3samlpassword = keystoneauth1.extras._saml2._loading:Saml2Password
|
v3samlpassword = keystoneauth1.extras._saml2._loading:Saml2Password
|
||||||
|
v3applicationcredential = keystoneauth1.loading._plugins.identity.v3:ApplicationCredential
|
||||||
|
|
||||||
[build_sphinx]
|
[build_sphinx]
|
||||||
source-dir = doc/source
|
source-dir = doc/source
|
||||||
|
Loading…
x
Reference in New Issue
Block a user