Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(220)

Unified Diff: reviewbot/third_party/google-api-python-client/oauth2client/multistore_file.py

Issue 20515002: Add google-api-python-client in third_party/ (Closed) Base URL: https://src.chromium.org/chrome/trunk/tools/
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: reviewbot/third_party/google-api-python-client/oauth2client/multistore_file.py
===================================================================
--- reviewbot/third_party/google-api-python-client/oauth2client/multistore_file.py (revision 0)
+++ reviewbot/third_party/google-api-python-client/oauth2client/multistore_file.py (revision 0)
@@ -0,0 +1,409 @@
+# Copyright 2011 Google Inc. All Rights Reserved.
+
+"""Multi-credential file store with lock support.
+
+This module implements a JSON credential store where multiple
+credentials can be stored in one file. That file supports locking
+both in a single process and across processes.
+
+The credential themselves are keyed off of:
+* client_id
+* user_agent
+* scope
+
+The format of the stored data is like so:
+{
+ 'file_version': 1,
+ 'data': [
+ {
+ 'key': {
+ 'clientId': '<client id>',
+ 'userAgent': '<user agent>',
+ 'scope': '<scope>'
+ },
+ 'credential': {
+ # JSON serialized Credentials.
+ }
+ }
+ ]
+}
+"""
+
+__author__ = 'jbeda@google.com (Joe Beda)'
+
+import base64
+import errno
+import logging
+import os
+import threading
+
+from anyjson import simplejson
+from oauth2client.client import Storage as BaseStorage
+from oauth2client.client import Credentials
+from oauth2client import util
+from locked_file import LockedFile
+
+logger = logging.getLogger(__name__)
+
+# A dict from 'filename'->_MultiStore instances
+_multistores = {}
+_multistores_lock = threading.Lock()
+
+
+class Error(Exception):
+ """Base error for this module."""
+ pass
+
+
+class NewerCredentialStoreError(Error):
+ """The credential store is a newer version that supported."""
+ pass
+
+
+@util.positional(4)
+def get_credential_storage(filename, client_id, user_agent, scope,
+ warn_on_readonly=True):
+ """Get a Storage instance for a credential.
+
+ Args:
+ filename: The JSON file storing a set of credentials
+ client_id: The client_id for the credential
+ user_agent: The user agent for the credential
+ scope: string or iterable of strings, Scope(s) being requested
+ warn_on_readonly: if True, log a warning if the store is readonly
+
+ Returns:
+ An object derived from client.Storage for getting/setting the
+ credential.
+ """
+ # Recreate the legacy key with these specific parameters
+ key = {'clientId': client_id, 'userAgent': user_agent,
+ 'scope': util.scopes_to_string(scope)}
+ return get_credential_storage_custom_key(
+ filename, key, warn_on_readonly=warn_on_readonly)
+
+
+@util.positional(2)
+def get_credential_storage_custom_string_key(
+ filename, key_string, warn_on_readonly=True):
+ """Get a Storage instance for a credential using a single string as a key.
+
+ Allows you to provide a string as a custom key that will be used for
+ credential storage and retrieval.
+
+ Args:
+ filename: The JSON file storing a set of credentials
+ key_string: A string to use as the key for storing this credential.
+ warn_on_readonly: if True, log a warning if the store is readonly
+
+ Returns:
+ An object derived from client.Storage for getting/setting the
+ credential.
+ """
+ # Create a key dictionary that can be used
+ key_dict = {'key': key_string}
+ return get_credential_storage_custom_key(
+ filename, key_dict, warn_on_readonly=warn_on_readonly)
+
+
+@util.positional(2)
+def get_credential_storage_custom_key(
+ filename, key_dict, warn_on_readonly=True):
+ """Get a Storage instance for a credential using a dictionary as a key.
+
+ Allows you to provide a dictionary as a custom key that will be used for
+ credential storage and retrieval.
+
+ Args:
+ filename: The JSON file storing a set of credentials
+ key_dict: A dictionary to use as the key for storing this credential. There
+ is no ordering of the keys in the dictionary. Logically equivalent
+ dictionaries will produce equivalent storage keys.
+ warn_on_readonly: if True, log a warning if the store is readonly
+
+ Returns:
+ An object derived from client.Storage for getting/setting the
+ credential.
+ """
+ filename = os.path.expanduser(filename)
+ _multistores_lock.acquire()
+ try:
+ multistore = _multistores.setdefault(
+ filename, _MultiStore(filename, warn_on_readonly=warn_on_readonly))
+ finally:
+ _multistores_lock.release()
+ key = util.dict_to_tuple_key(key_dict)
+ return multistore._get_storage(key)
+
+
+class _MultiStore(object):
+ """A file backed store for multiple credentials."""
+
+ @util.positional(2)
+ def __init__(self, filename, warn_on_readonly=True):
+ """Initialize the class.
+
+ This will create the file if necessary.
+ """
+ self._file = LockedFile(filename, 'r+b', 'rb')
+ self._thread_lock = threading.Lock()
+ self._read_only = False
+ self._warn_on_readonly = warn_on_readonly
+
+ self._create_file_if_needed()
+
+ # Cache of deserialized store. This is only valid after the
+ # _MultiStore is locked or _refresh_data_cache is called. This is
+ # of the form of:
+ #
+ # ((key, value), (key, value)...) -> OAuth2Credential
+ #
+ # If this is None, then the store hasn't been read yet.
+ self._data = None
+
+ class _Storage(BaseStorage):
+ """A Storage object that knows how to read/write a single credential."""
+
+ def __init__(self, multistore, key):
+ self._multistore = multistore
+ self._key = key
+
+ def acquire_lock(self):
+ """Acquires any lock necessary to access this Storage.
+
+ This lock is not reentrant.
+ """
+ self._multistore._lock()
+
+ def release_lock(self):
+ """Release the Storage lock.
+
+ Trying to release a lock that isn't held will result in a
+ RuntimeError.
+ """
+ self._multistore._unlock()
+
+ def locked_get(self):
+ """Retrieve credential.
+
+ The Storage lock must be held when this is called.
+
+ Returns:
+ oauth2client.client.Credentials
+ """
+ credential = self._multistore._get_credential(self._key)
+ if credential:
+ credential.set_store(self)
+ return credential
+
+ def locked_put(self, credentials):
+ """Write a credential.
+
+ The Storage lock must be held when this is called.
+
+ Args:
+ credentials: Credentials, the credentials to store.
+ """
+ self._multistore._update_credential(self._key, credentials)
+
+ def locked_delete(self):
+ """Delete a credential.
+
+ The Storage lock must be held when this is called.
+
+ Args:
+ credentials: Credentials, the credentials to store.
+ """
+ self._multistore._delete_credential(self._key)
+
+ def _create_file_if_needed(self):
+ """Create an empty file if necessary.
+
+ This method will not initialize the file. Instead it implements a
+ simple version of "touch" to ensure the file has been created.
+ """
+ if not os.path.exists(self._file.filename()):
+ old_umask = os.umask(0177)
+ try:
+ open(self._file.filename(), 'a+b').close()
+ finally:
+ os.umask(old_umask)
+
+ def _lock(self):
+ """Lock the entire multistore."""
+ self._thread_lock.acquire()
+ self._file.open_and_lock()
+ if not self._file.is_locked():
+ self._read_only = True
+ if self._warn_on_readonly:
+ logger.warn('The credentials file (%s) is not writable. Opening in '
+ 'read-only mode. Any refreshed credentials will only be '
+ 'valid for this run.' % self._file.filename())
+ if os.path.getsize(self._file.filename()) == 0:
+ logger.debug('Initializing empty multistore file')
+ # The multistore is empty so write out an empty file.
+ self._data = {}
+ self._write()
+ elif not self._read_only or self._data is None:
+ # Only refresh the data if we are read/write or we haven't
+ # cached the data yet. If we are readonly, we assume is isn't
+ # changing out from under us and that we only have to read it
+ # once. This prevents us from whacking any new access keys that
+ # we have cached in memory but were unable to write out.
+ self._refresh_data_cache()
+
+ def _unlock(self):
+ """Release the lock on the multistore."""
+ self._file.unlock_and_close()
+ self._thread_lock.release()
+
+ def _locked_json_read(self):
+ """Get the raw content of the multistore file.
+
+ The multistore must be locked when this is called.
+
+ Returns:
+ The contents of the multistore decoded as JSON.
+ """
+ assert self._thread_lock.locked()
+ self._file.file_handle().seek(0)
+ return simplejson.load(self._file.file_handle())
+
+ def _locked_json_write(self, data):
+ """Write a JSON serializable data structure to the multistore.
+
+ The multistore must be locked when this is called.
+
+ Args:
+ data: The data to be serialized and written.
+ """
+ assert self._thread_lock.locked()
+ if self._read_only:
+ return
+ self._file.file_handle().seek(0)
+ simplejson.dump(data, self._file.file_handle(), sort_keys=True, indent=2)
+ self._file.file_handle().truncate()
+
+ def _refresh_data_cache(self):
+ """Refresh the contents of the multistore.
+
+ The multistore must be locked when this is called.
+
+ Raises:
+ NewerCredentialStoreError: Raised when a newer client has written the
+ store.
+ """
+ self._data = {}
+ try:
+ raw_data = self._locked_json_read()
+ except Exception:
+ logger.warn('Credential data store could not be loaded. '
+ 'Will ignore and overwrite.')
+ return
+
+ version = 0
+ try:
+ version = raw_data['file_version']
+ except Exception:
+ logger.warn('Missing version for credential data store. It may be '
+ 'corrupt or an old version. Overwriting.')
+ if version > 1:
+ raise NewerCredentialStoreError(
+ 'Credential file has file_version of %d. '
+ 'Only file_version of 1 is supported.' % version)
+
+ credentials = []
+ try:
+ credentials = raw_data['data']
+ except (TypeError, KeyError):
+ pass
+
+ for cred_entry in credentials:
+ try:
+ (key, credential) = self._decode_credential_from_json(cred_entry)
+ self._data[key] = credential
+ except:
+ # If something goes wrong loading a credential, just ignore it
+ logger.info('Error decoding credential, skipping', exc_info=True)
+
+ def _decode_credential_from_json(self, cred_entry):
+ """Load a credential from our JSON serialization.
+
+ Args:
+ cred_entry: A dict entry from the data member of our format
+
+ Returns:
+ (key, cred) where the key is the key tuple and the cred is the
+ OAuth2Credential object.
+ """
+ raw_key = cred_entry['key']
+ key = util.dict_to_tuple_key(raw_key)
+ credential = None
+ credential = Credentials.new_from_json(simplejson.dumps(cred_entry['credential']))
+ return (key, credential)
+
+ def _write(self):
+ """Write the cached data back out.
+
+ The multistore must be locked.
+ """
+ raw_data = {'file_version': 1}
+ raw_creds = []
+ raw_data['data'] = raw_creds
+ for (cred_key, cred) in self._data.items():
+ raw_key = dict(cred_key)
+ raw_cred = simplejson.loads(cred.to_json())
+ raw_creds.append({'key': raw_key, 'credential': raw_cred})
+ self._locked_json_write(raw_data)
+
+ def _get_credential(self, key):
+ """Get a credential from the multistore.
+
+ The multistore must be locked.
+
+ Args:
+ key: The key used to retrieve the credential
+
+ Returns:
+ The credential specified or None if not present
+ """
+ return self._data.get(key, None)
+
+ def _update_credential(self, key, cred):
+ """Update a credential and write the multistore.
+
+ This must be called when the multistore is locked.
+
+ Args:
+ key: The key used to retrieve the credential
+ cred: The OAuth2Credential to update/set
+ """
+ self._data[key] = cred
+ self._write()
+
+ def _delete_credential(self, key):
+ """Delete a credential and write the multistore.
+
+ This must be called when the multistore is locked.
+
+ Args:
+ key: The key used to retrieve the credential
+ """
+ try:
+ del self._data[key]
+ except KeyError:
+ pass
+ self._write()
+
+ def _get_storage(self, key):
+ """Get a Storage object to get/set a credential.
+
+ This Storage is a 'view' into the multistore.
+
+ Args:
+ key: The key used to retrieve the credential
+
+ Returns:
+ A Storage object that can be used to get/set this cred
+ """
+ return self._Storage(self, key)
Property changes on: reviewbot/third_party/google-api-python-client/oauth2client/multistore_file.py
___________________________________________________________________
Added: svn:eol-style
+ LF

Powered by Google App Engine
This is Rietveld 408576698