oidc: add OidcAccessToken class to authenticate reusing an access token

Some services or users may have obtained an access token, so it would be
possible to authenticate using this token directly (for example a
service where the user has already logged in). This new class makes
possible to use an access token to authenticate directly with Keystone,
exchanging it for a Keystone token.

Closes-bug: 1583780
Change-Id: I5a31270194a3d1aa48de709dba49afde460731e2
This commit is contained in:
Alvaro Lopez Garcia 2016-05-19 17:48:41 +02:00
parent 1c07cddcb2
commit 553a523830
6 changed files with 110 additions and 6 deletions

View File

@ -43,6 +43,9 @@ V3OidcPassword = oidc.OidcPassword
V3OidcAuthorizationCode = oidc.OidcAuthorizationCode
"""See :class:`keystoneauth1.identity.v3.oidc.OidcAuthorizationCode`"""
V3OidcAccessToken = oidc.OidcAccessToken
"""See :class:`keystoneauth1.identity.v3.oidc.OidcAccessToken`"""
__all__ = ('BaseIdentityPlugin',
'Password',
'Token',
@ -51,4 +54,5 @@ __all__ = ('BaseIdentityPlugin',
'V3Password',
'V3Token',
'V3OidcPassword',
'V3OidcAuthorizationCode')
'V3OidcAuthorizationCode',
'V3OidcAccessToken')

View File

@ -16,7 +16,8 @@ from keystoneauth1 import access
from keystoneauth1.identity.v3 import federation
__all__ = ('OidcAuthorizationCode',
'OidcPassword')
'OidcPassword',
'OidcAccessToken')
class _OidcBase(federation.FederationBaseAuth):
@ -243,3 +244,56 @@ class OidcAuthorizationCode(_OidcBase):
# grab the unscoped token
return access.create(resp=response)
class OidcAccessToken(_OidcBase):
"""Implementation for OpenID Connect access token reuse."""
@positional(5)
def __init__(self, auth_url, identity_provider, protocol,
access_token, **kwargs):
"""The OpenID Connect plugin based on the Access Token.
It expects the following:
:param auth_url: URL of the Identity Service
:type auth_url: string
:param identity_provider: Name of the Identity Provider the client
will authenticate against
:type identity_provider: string
:param protocol: Protocol name as configured in keystone
:type protocol: string
:param access_token: OpenID Connect Access token
:type access_token: string
"""
super(OidcAccessToken, self).__init__(auth_url, identity_provider,
protocol,
client_id=None,
client_secret=None,
access_token_endpoint=None,
grant_type=None,
access_token_type=None,
**kwargs)
self.access_token = access_token
def get_unscoped_auth_ref(self, session):
"""Authenticate with OpenID Connect and get back claims.
We exchange the access token upon accessing the protected Keystone
endpoint (federated auth URL). This will trigger the OpenID Connect
Provider to perform a user introspection and retrieve information
(specified in the scope) about the user in the form of an OpenID
Connect Claim. These claims will be sent to Keystone in the form of
environment variables.
:param session: a session object to send out HTTP requests.
:type session: keystoneclient.session.Session
:returns: a token data representation
:rtype: :py:class:`keystoneauth1.access.AccessInfoV3`
"""
response = self._get_keystone_token(session, self.access_token)
return access.create(resp=response)

View File

@ -123,3 +123,20 @@ class OpenIDConnectAuthorizationCode(_OpenIDConnectBase):
])
return options
class OpenIDConnectAcessToken(loading.BaseFederationLoader):
@property
def plugin_class(self):
return identity.V3OidcAccessToken
@classmethod
def get_options(cls):
options = super(OpenIDConnectAcessToken, cls).get_options()
options.extend([
loading.StrOpt('access-token', secret=True,
help='OAuth 2.0 Access Token'),
])
return options

View File

@ -36,6 +36,7 @@ class AuthenticateOIDCTests(utils.TestCase):
self.PASSWORD = uuid.uuid4().hex
self.CLIENT_ID = uuid.uuid4().hex
self.CLIENT_SECRET = uuid.uuid4().hex
self.ACCESS_TOKEN = uuid.uuid4().hex
self.ACCESS_TOKEN_ENDPOINT = 'https://localhost:8020/oidc/token'
self.FEDERATION_AUTH_URL = '%s/%s' % (
self.AUTH_URL,
@ -63,6 +64,12 @@ class AuthenticateOIDCTests(utils.TestCase):
redirect_uri=self.REDIRECT_URL,
code=self.CODE)
self.oidc_token = oidc.OidcAccessToken(
self.AUTH_URL,
self.IDENTITY_PROVIDER,
self.PROTOCOL,
access_token=self.ACCESS_TOKEN)
class OIDCPasswordTests(AuthenticateOIDCTests):
@ -95,16 +102,14 @@ class OIDCPasswordTests(AuthenticateOIDCTests):
json=oidc_fixtures.UNSCOPED_TOKEN,
headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE})
# Prep all the values and send the request
access_token = uuid.uuid4().hex
res = self.oidc_password._get_keystone_token(self.session,
access_token)
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 ' + access_token}
headers = {'Authorization': 'Bearer ' + self.ACCESS_TOKEN}
self.assertEqual(headers['Authorization'],
res.request.headers['Authorization'])
@ -147,3 +152,17 @@ class OIDCAuthorizationGrantTests(AuthenticateOIDCTests):
self.assertEqual('POST', last_req.method)
encoded_payload = urllib.parse.urlencode(payload)
self.assertEqual(encoded_payload, last_req.body)
class AuthenticateOIDCTokenTests(AuthenticateOIDCTests):
def test_end_to_end_workflow(self):
"""Test full OpenID Connect workflow."""
# 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.oidc_token.get_unscoped_auth_ref(self.session)
self.assertEqual(KEYSTONE_TOKEN_VALUE, response.auth_token)

View File

@ -0,0 +1,9 @@
---
features:
- Added a new OidcAccessToken plugin, accessible via the 'v3oidcaccesstoken'
entry point, making possible to authenticate using an existing OpenID
Connect Access token.
fixes:
- >
[`bug 1583780 <https://bugs.launchpad.net/keystoneauth/+bug/1583780>`_]
OpenID connect support should include authenticating using directly an access token.

View File

@ -46,6 +46,7 @@ keystoneauth1.plugin =
v3token = keystoneauth1.loading._plugins.identity.v3:Token
v3oidcpassword = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectPassword
v3oidcauthcode = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectAuthorizationCode
v3oidcaccesstoken = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectAccessToken
v3oauth1 = keystoneauth1.extras.oauth1._loading:V3OAuth1
[build_sphinx]