summaryrefslogtreecommitdiff
path: root/keystoneclient/contrib
diff options
context:
space:
mode:
authorSteve Martinelli <stevemar@ca.ibm.com>2014-11-15 05:47:32 -0500
committerSteve Martinelli <stevemar@ca.ibm.com>2015-06-17 11:15:03 -0400
commit02f07cfb493b2b81ab4e64d3d674a0ea6af7500b (patch)
tree687d518395efa2a08ae2a4e743fb6fac5bef652d /keystoneclient/contrib
parent54d5b1a4cabb9902b79dafc879d49b4f2b84fb72 (diff)
downloadpython-keystoneclient-02f07cfb493b2b81ab4e64d3d674a0ea6af7500b.tar.gz
Add openid connect client support
This patch allows a federated user to obtain an unscoped token by providing login credentials for a keystone identity provider. The current implementation should work with any properly configured openid connect provider. partially implements bp openid-connect Change-Id: Iade52b5c1432d64582cbaa8bac41ac6366c210f9
Diffstat (limited to 'keystoneclient/contrib')
-rw-r--r--keystoneclient/contrib/auth/v3/oidc.py189
1 files changed, 189 insertions, 0 deletions
diff --git a/keystoneclient/contrib/auth/v3/oidc.py b/keystoneclient/contrib/auth/v3/oidc.py
new file mode 100644
index 0000000..6105e06
--- /dev/null
+++ b/keystoneclient/contrib/auth/v3/oidc.py
@@ -0,0 +1,189 @@
+# 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 oslo_config import cfg
+
+from keystoneclient import access
+from keystoneclient.auth.identity.v3 import federated
+from keystoneclient import utils
+
+
+class OidcPassword(federated.FederatedBaseAuth):
+ """Implement authentication plugin for OpenID Connect protocol.
+
+ OIDC or OpenID Connect is a protocol for federated authentication.
+
+ The OpenID Connect specification can be found at::
+ ``http://openid.net/specs/openid-connect-core-1_0.html``
+ """
+
+ @classmethod
+ def get_options(cls):
+ options = super(OidcPassword, cls).get_options()
+ options.extend([
+ cfg.StrOpt('username', help='Username'),
+ cfg.StrOpt('password', help='Password'),
+ cfg.StrOpt('client-id', help='OAuth 2.0 Client ID'),
+ cfg.StrOpt('client-secret', help='OAuth 2.0 Client Secret'),
+ cfg.StrOpt('access-token-endpoint',
+ help='OpenID Connect Provider Token Endpoint'),
+ cfg.StrOpt('scope', default="profile",
+ help='OpenID Connect scope that is requested from OP')
+ ])
+ return options
+
+ @utils.positional(4)
+ def __init__(self, auth_url, identity_provider, protocol,
+ username, password, client_id, client_secret,
+ access_token_endpoint, scope='profile',
+ grant_type='password'):
+ """The OpenID Connect plugin 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 username: Username used to authenticate
+ :type username: string
+
+ :param password: Password used to authenticate
+ :type password: string
+
+ :param client_id: OAuth 2.0 Client ID
+ :type client_id: string
+
+ :param client_secret: OAuth 2.0 Client Secret
+ :type client_secret: string
+
+ :param access_token_endpoint: OpenID Connect Provider Token Endpoint,
+ for example:
+ https://localhost:8020/oidc/OP/token
+ :type access_token_endpoint: string
+
+ :param scope: OpenID Connect scope that is requested from OP,
+ defaults to "profile", for example: "profile email"
+ :type scope: string
+
+ :param grant_type: OpenID Connect grant type, it represents the flow
+ that is used to talk to the OP. Valid values are:
+ "authorization_code", "refresh_token", or
+ "password".
+ :type grant_type: string
+ """
+ super(OidcPassword, self).__init__(auth_url, identity_provider,
+ protocol)
+ self.username = username
+ self.password = password
+ self.client_id = client_id
+ self.client_secret = client_secret
+ self.access_token_endpoint = access_token_endpoint
+ self.scope = scope
+ self.grant_type = grant_type
+
+ def get_unscoped_auth_ref(self, session):
+ """Authenticate with OpenID Connect and get back claims.
+
+ This is a multi-step process. First an access token must be retrieved,
+ to do this, the username and password, the OpenID Connect client ID
+ and secret, and the access token endpoint must be known.
+
+ Secondly, we then 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:`keystoneclient.access.AccessInfo`
+ """
+
+ # get an access token
+ client_auth = (self.client_id, self.client_secret)
+ payload = {'grant_type': self.grant_type, 'username': self.username,
+ 'password': self.password, 'scope': self.scope}
+ response = self._get_access_token(session, client_auth, payload,
+ self.access_token_endpoint)
+ access_token = response.json()['access_token']
+
+ # use access token against protected URL
+ headers = {'Authorization': 'Bearer ' + access_token}
+ response = self._get_keystone_token(session, headers,
+ self.federated_token_url)
+
+ # grab the unscoped token
+ token = response.headers['X-Subject-Token']
+ token_json = response.json()['token']
+ return access.AccessInfoV3(token, **token_json)
+
+ def _get_access_token(self, session, client_auth, payload,
+ access_token_endpoint):
+ """Exchange a variety of user supplied values for an access token.
+
+ :param session: a session object to send out HTTP requests.
+ :type session: keystoneclient.session.Session
+
+ :param client_auth: a tuple representing client id and secret
+ :type client_auth: tuple
+
+ :param payload: a dict containing various OpenID Connect values, for
+ example::
+ {'grant_type': 'password', 'username': self.username,
+ 'password': self.password, 'scope': self.scope}
+ :type payload: dict
+
+ :param access_token_endpoint: URL to use to get an access token, for
+ example: https://localhost/oidc/token
+ :type access_token_endpoint: string
+ """
+ op_response = session.post(self.access_token_endpoint,
+ requests_auth=client_auth,
+ data=payload,
+ authenticated=False)
+ return op_response
+
+ def _get_keystone_token(self, session, headers, federated_token_url):
+ """Exchange an acess token for a keystone token.
+
+ By Sending the access token in an `Authorization: Bearer` header, to
+ an OpenID Connect protected endpoint (Federated Token URL). The
+ OpenID Connect server will use the access token to look up information
+ about the authenticated user (this technique is called instrospection).
+ The output of the instrospection will be an OpenID Connect Claim, that
+ will be used against the mapping engine. Should the mapping engine
+ succeed, a Keystone token will be presented to the user.
+
+ :param session: a session object to send out HTTP requests.
+ :type session: keystoneclient.session.Session
+
+ :param headers: an Authorization header containing the access token.
+ :type headers_: dict
+
+ :param federated_auth_url: Protected URL for federated authentication,
+ for example: https://localhost:5000/v3/\
+ OS-FEDERATION/identity_providers/bluepages/\
+ protocols/oidc/auth
+ :type federated_auth_url: string
+ """
+ auth_response = session.post(self.federated_token_url,
+ headers=headers,
+ authenticated=False)
+ return auth_response