diff options
author | Guang Yee <guang.yee@hp.com> | 2012-11-08 16:32:17 -0800 |
---|---|---|
committer | Guang Yee <guang.yee@hp.com> | 2012-12-04 09:18:20 -0800 |
commit | 5939541bc771e1205394b05e757d7b23b3aca862 (patch) | |
tree | 999460b6e5aeaf2e96ebd9f56181f6b9a211eaba /keystoneclient/client.py | |
parent | 94cbb616634f89fd35c2f9d62041d63e8d79bde3 (diff) | |
download | python-keystoneclient-5939541bc771e1205394b05e757d7b23b3aca862.tar.gz |
bug-1040361: use keyring to store tokens
User can optionally turn off keyring by specifying the --no-cache option.
It can also be disabled with environment variable OS-NO-CACHE.
Change-Id: I8935260bf7fd6befa14798da9b4d02c81e65c417
Diffstat (limited to 'keystoneclient/client.py')
-rw-r--r-- | keystoneclient/client.py | 153 |
1 files changed, 150 insertions, 3 deletions
diff --git a/keystoneclient/client.py b/keystoneclient/client.py index de86258..e3bd623 100644 --- a/keystoneclient/client.py +++ b/keystoneclient/client.py @@ -32,6 +32,16 @@ from keystoneclient import exceptions _logger = logging.getLogger(__name__) +# keyring init +keyring_available = True +try: + import keyring + import pickle +except ImportError: + _logger.warning('Failed to load keyring modules.') + keyring_available = False + + class HTTPClient(httplib2.Http): USER_AGENT = 'python-keystoneclient' @@ -40,7 +50,8 @@ class HTTPClient(httplib2.Http): password=None, auth_url=None, region_name=None, timeout=None, endpoint=None, token=None, cacert=None, key=None, cert=None, insecure=False, original_ip=None, debug=False, - auth_ref=None): + auth_ref=None, use_keyring=True, force_new_token=False, + stale_duration=None): super(HTTPClient, self).__init__(timeout=timeout, ca_certs=cacert) if cert: if key: @@ -95,8 +106,141 @@ class HTTPClient(httplib2.Http): _logger.setLevel(logging.DEBUG) _logger.addHandler(ch) - def authenticate(self): - """ Authenticate against the Identity API. + # keyring setup + self.use_keyring = use_keyring and keyring_available + self.force_new_token = force_new_token + self.stale_duration = stale_duration or access.STALE_TOKEN_DURATION + self.stale_duration = int(self.stale_duration) + + def authenticate(self, username=None, password=None, tenant_name=None, + tenant_id=None, auth_url=None, token=None): + """ Authenticate user. + + Uses the data provided at instantiation to authenticate against + the Keystone server. This may use either a username and password + or token for authentication. If a tenant name or id was provided + then the resulting authenticated client will be scoped to that + tenant and contain a service catalog of available endpoints. + + With the v2.0 API, if a tenant name or ID is not provided, the + authenication token returned will be 'unscoped' and limited in + capabilities until a fully-scoped token is acquired. + + If successful, sets the self.auth_ref and self.auth_token with + the returned token. If not already set, will also set + self.management_url from the details provided in the token. + + :returns: ``True`` if authentication was successful. + :raises: AuthorizationFailure if unable to authenticate or validate + the existing authorization token + :raises: ValueError if insufficient parameters are used. + + If keyring is used, token is retrieved from keyring instead. + Authentication will only be necessary if any of the following + conditions are met: + + * keyring is not used + * if token is not found in keyring + * if token retrieved from keyring is expired or about to + expired (as determined by stale_duration) + * if force_new_token is true + + """ + auth_url = auth_url or self.auth_url + username = username or self.username + password = password or self.password + tenant_name = tenant_name or self.tenant_name + tenant_id = tenant_id or self.tenant_id + token = token or self.auth_token + + (keyring_key, auth_ref) = self.get_auth_ref_from_keyring(auth_url, + username, + tenant_name, + tenant_id, + token) + new_token_needed = False + if auth_ref is None or self.force_new_token: + new_token_needed = True + raw_token = self.get_raw_token_from_identity_service(auth_url, + username, + password, + tenant_name, + tenant_id, + token) + self.auth_ref = access.AccessInfo(**raw_token) + else: + self.auth_ref = auth_ref + self.process_token() + if new_token_needed: + self.store_auth_ref_into_keyring(keyring_key) + return True + + def _build_keyring_key(self, auth_url, username, tenant_name, + tenant_id, token): + """ Create a unique key for keyring. + + Used to store and retrieve auth_ref from keyring. + + """ + keys = [auth_url, username, tenant_name, tenant_id, token] + for index, key in enumerate(keys): + if key is None: + keys[index] = '?' + keyring_key = '/'.join(keys) + return keyring_key + + def get_auth_ref_from_keyring(self, auth_url, username, tenant_name, + tenant_id, token): + """ Retrieve auth_ref from keyring. + + If auth_ref is found in keyring, (keyring_key, auth_ref) is returned. + Otherwise, (keyring_key, None) is returned. + + :returns: (keyring_key, auth_ref) or (keyring_key, None) + + """ + keyring_key = None + auth_ref = None + if self.use_keyring: + keyring_key = self._build_keyring_key(auth_url, username, + tenant_name, tenant_id, + token) + try: + auth_ref = keyring.get_password("keystoneclient_auth", + keyring_key) + if auth_ref: + auth_ref = pickle.loads(auth_ref) + if auth_ref.will_expire_soon(self.stale_duration): + # token has expired, don't use it + auth_ref = None + except Exception as e: + auth_ref = None + _logger.warning('Unable to retrieve token from keyring %s' % ( + e)) + return (keyring_key, auth_ref) + + def store_auth_ref_into_keyring(self, keyring_key): + """ Store auth_ref into keyring. + + """ + if self.use_keyring: + try: + keyring.set_password("keystoneclient_auth", + keyring_key, + pickle.dumps(self.auth_ref)) + except Exception as e: + _logger.warning("Failed to store token into keyring %s" % (e)) + + def process_token(self): + """ Extract and process information from the new auth_ref. + + """ + raise NotImplementedError + + def get_raw_token_from_identity_service(self, auth_url, username=None, + password=None, tenant_name=None, + tenant_id=None, token=None): + """ Authenticate against the Identity API and get a token. Not implemented here because auth protocols should be API version-specific. @@ -104,6 +248,9 @@ class HTTPClient(httplib2.Http): Expected to authenticate or validate an existing authentication reference already associated with the client. Invoking this call *always* makes a call to the Keystone. + + :returns: ``raw token`` + """ raise NotImplementedError |