summaryrefslogtreecommitdiff
path: root/keystoneclient/client.py
diff options
context:
space:
mode:
authorGuang Yee <guang.yee@hp.com>2012-11-08 16:32:17 -0800
committerGuang Yee <guang.yee@hp.com>2012-12-04 09:18:20 -0800
commit5939541bc771e1205394b05e757d7b23b3aca862 (patch)
tree999460b6e5aeaf2e96ebd9f56181f6b9a211eaba /keystoneclient/client.py
parent94cbb616634f89fd35c2f9d62041d63e8d79bde3 (diff)
downloadpython-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.py153
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