oidc: implement client_credentials grant type
Change-Id: If1538726cb7e4cb87fad82c5daf50c67b161b52d
This commit is contained in:
parent
b6b2b12ef9
commit
e5fd66ca35
@ -37,6 +37,9 @@ Password = generic.Password
|
||||
Token = generic.Token
|
||||
"""See :class:`keystoneauth1.identity.generic.Token`"""
|
||||
|
||||
V3OidcClientCredentials = oidc.OidcClientCredentials
|
||||
"""See :class:`keystoneauth1.identity.v3.oidc.OidcClientCredentials`"""
|
||||
|
||||
V3OidcPassword = oidc.OidcPassword
|
||||
"""See :class:`keystoneauth1.identity.v3.oidc.OidcPassword`"""
|
||||
|
||||
|
@ -36,6 +36,7 @@ __all__ = ('Auth',
|
||||
'TokenMethod',
|
||||
|
||||
'OidcAuthorizationCode',
|
||||
'OidcClientCredentials',
|
||||
'OidcPassword',
|
||||
|
||||
'TOTPMethod',
|
||||
|
@ -331,6 +331,50 @@ class OidcPassword(_OidcBase):
|
||||
return payload
|
||||
|
||||
|
||||
class OidcClientCredentials(_OidcBase):
|
||||
"""Implementation for OpenID Connect Client Credentials."""
|
||||
|
||||
grant_type = 'client_credentials'
|
||||
|
||||
@positional(4)
|
||||
def __init__(self, auth_url, identity_provider, protocol,
|
||||
client_id, client_secret,
|
||||
access_token_endpoint=None,
|
||||
discovery_endpoint=None,
|
||||
access_token_type='access_token',
|
||||
**kwargs):
|
||||
"""The OpenID Client Credentials expects the following.
|
||||
|
||||
:param client_id: Client ID used to authenticate
|
||||
:type username: string
|
||||
|
||||
:param client_secret: Client Secret used to authenticate
|
||||
:type password: string
|
||||
"""
|
||||
super(OidcClientCredentials, self).__init__(
|
||||
auth_url=auth_url,
|
||||
identity_provider=identity_provider,
|
||||
protocol=protocol,
|
||||
client_id=client_id,
|
||||
client_secret=client_secret,
|
||||
access_token_endpoint=access_token_endpoint,
|
||||
discovery_endpoint=discovery_endpoint,
|
||||
access_token_type=access_token_type,
|
||||
**kwargs)
|
||||
|
||||
def get_payload(self, session):
|
||||
"""Get an authorization grant for the client credentials grant type.
|
||||
|
||||
:param session: a session object to send out HTTP requests.
|
||||
:type session: keystoneauth1.session.Session
|
||||
|
||||
:returns: a python dictionary containing the payload to be exchanged
|
||||
:rtype: dict
|
||||
"""
|
||||
payload = {'scope': self.scope}
|
||||
return payload
|
||||
|
||||
|
||||
class OidcAuthorizationCode(_OidcBase):
|
||||
"""Implementation for OpenID Connect Authorization Code."""
|
||||
|
||||
|
@ -123,6 +123,18 @@ class _OpenIDConnectBase(loading.BaseFederationLoader):
|
||||
return options
|
||||
|
||||
|
||||
class OpenIDConnectClientCredentials(_OpenIDConnectBase):
|
||||
|
||||
@property
|
||||
def plugin_class(self):
|
||||
return identity.V3OidcClientCredentials
|
||||
|
||||
def get_options(self):
|
||||
options = super(OpenIDConnectClientCredentials, self).get_options()
|
||||
|
||||
return options
|
||||
|
||||
|
||||
class OpenIDConnectPassword(_OpenIDConnectBase):
|
||||
|
||||
@property
|
||||
|
@ -180,6 +180,76 @@ class BaseOIDCTests(object):
|
||||
self.session)
|
||||
|
||||
|
||||
class OIDCClientCredentialsTests(BaseOIDCTests, utils.TestCase):
|
||||
def setUp(self):
|
||||
super(OIDCClientCredentialsTests, self).setUp()
|
||||
|
||||
self.GRANT_TYPE = 'client_credentials'
|
||||
|
||||
self.plugin = oidc.OidcClientCredentials(
|
||||
self.AUTH_URL,
|
||||
self.IDENTITY_PROVIDER,
|
||||
self.PROTOCOL,
|
||||
client_id=self.CLIENT_ID,
|
||||
client_secret=self.CLIENT_SECRET,
|
||||
access_token_endpoint=self.ACCESS_TOKEN_ENDPOINT,
|
||||
project_name=self.PROJECT_NAME)
|
||||
|
||||
def test_initial_call_to_get_access_token(self):
|
||||
"""Test initial call, expect JSON access token."""
|
||||
# Mock the output that creates the access token
|
||||
self.requests_mock.post(
|
||||
self.ACCESS_TOKEN_ENDPOINT,
|
||||
json=oidc_fixtures.ACCESS_TOKEN_VIA_PASSWORD_RESP)
|
||||
|
||||
# Prep all the values and send the request
|
||||
scope = 'profile email'
|
||||
payload = {'grant_type': self.GRANT_TYPE, 'scope': scope}
|
||||
self.plugin._get_access_token(self.session, payload)
|
||||
|
||||
# Verify the request matches the expected structure
|
||||
last_req = self.requests_mock.last_request
|
||||
self.assertEqual(self.ACCESS_TOKEN_ENDPOINT, last_req.url)
|
||||
self.assertEqual('POST', last_req.method)
|
||||
encoded_payload = urllib.parse.urlencode(payload)
|
||||
self.assertEqual(encoded_payload, last_req.body)
|
||||
|
||||
def test_second_call_to_protected_url(self):
|
||||
"""Test subsequent call, expect Keystone token."""
|
||||
# Mock the output that creates the keystone token
|
||||
self.requests_mock.post(
|
||||
self.FEDERATION_AUTH_URL,
|
||||
json=oidc_fixtures.UNSCOPED_TOKEN,
|
||||
headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE})
|
||||
|
||||
res = self.plugin._get_keystone_token(self.session,
|
||||
self.ACCESS_TOKEN)
|
||||
|
||||
# Verify the request matches the expected structure
|
||||
self.assertEqual(self.FEDERATION_AUTH_URL, res.request.url)
|
||||
self.assertEqual('POST', res.request.method)
|
||||
|
||||
headers = {'Authorization': 'Bearer ' + self.ACCESS_TOKEN}
|
||||
self.assertEqual(headers['Authorization'],
|
||||
res.request.headers['Authorization'])
|
||||
|
||||
def test_end_to_end_workflow(self):
|
||||
"""Test full OpenID Connect workflow."""
|
||||
# Mock the output that creates the access token
|
||||
self.requests_mock.post(
|
||||
self.ACCESS_TOKEN_ENDPOINT,
|
||||
json=oidc_fixtures.ACCESS_TOKEN_VIA_PASSWORD_RESP)
|
||||
|
||||
# Mock the output that creates the keystone token
|
||||
self.requests_mock.post(
|
||||
self.FEDERATION_AUTH_URL,
|
||||
json=oidc_fixtures.UNSCOPED_TOKEN,
|
||||
headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE})
|
||||
|
||||
response = self.plugin.get_unscoped_auth_ref(self.session)
|
||||
self.assertEqual(KEYSTONE_TOKEN_VALUE, response.auth_token)
|
||||
|
||||
|
||||
class OIDCPasswordTests(BaseOIDCTests, utils.TestCase):
|
||||
def setUp(self):
|
||||
super(OIDCPasswordTests, self).setUp()
|
||||
|
@ -143,6 +143,42 @@ class OpenIDConnectBaseTests(object):
|
||||
self.assertIn('scope', [o.dest for o in options])
|
||||
|
||||
|
||||
class OpenIDConnectClientCredentialsTests(OpenIDConnectBaseTests,
|
||||
utils.TestCase):
|
||||
|
||||
plugin_name = "v3oidcclientcredentials"
|
||||
|
||||
def test_options(self):
|
||||
options = loading.get_plugin_loader(self.plugin_name).get_options()
|
||||
self.assertTrue(
|
||||
set(['openid-scope']).issubset(
|
||||
set([o.name for o in options]))
|
||||
)
|
||||
|
||||
def test_basic(self):
|
||||
access_token_endpoint = uuid.uuid4().hex
|
||||
scope = uuid.uuid4().hex
|
||||
identity_provider = uuid.uuid4().hex
|
||||
protocol = uuid.uuid4().hex
|
||||
scope = uuid.uuid4().hex
|
||||
client_id = uuid.uuid4().hex
|
||||
client_secret = uuid.uuid4().hex
|
||||
|
||||
oidc = self.create(identity_provider=identity_provider,
|
||||
protocol=protocol,
|
||||
access_token_endpoint=access_token_endpoint,
|
||||
client_id=client_id,
|
||||
client_secret=client_secret,
|
||||
scope=scope)
|
||||
|
||||
self.assertEqual(scope, oidc.scope)
|
||||
self.assertEqual(identity_provider, oidc.identity_provider)
|
||||
self.assertEqual(protocol, oidc.protocol)
|
||||
self.assertEqual(access_token_endpoint, oidc.access_token_endpoint)
|
||||
self.assertEqual(client_id, oidc.client_id)
|
||||
self.assertEqual(client_secret, oidc.client_secret)
|
||||
|
||||
|
||||
class OpenIDConnectPasswordTests(OpenIDConnectBaseTests, utils.TestCase):
|
||||
|
||||
plugin_name = "v3oidcpassword"
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- Add support for the Client Credentials OpenID Connect
|
||||
grant type.
|
@ -45,6 +45,7 @@ keystoneauth1.plugin =
|
||||
v2token = keystoneauth1.loading._plugins.identity.v2:Token
|
||||
v3password = keystoneauth1.loading._plugins.identity.v3:Password
|
||||
v3token = keystoneauth1.loading._plugins.identity.v3:Token
|
||||
v3oidcclientcredentials = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectClientCredentials
|
||||
v3oidcpassword = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectPassword
|
||||
v3oidcauthcode = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectAuthorizationCode
|
||||
v3oidcaccesstoken = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectAccessToken
|
||||
|
Loading…
Reference in New Issue
Block a user