diff options
Diffstat (limited to 'oauth2client/contrib/multistore_file.py')
-rw-r--r-- | oauth2client/contrib/multistore_file.py | 505 |
1 files changed, 0 insertions, 505 deletions
diff --git a/oauth2client/contrib/multistore_file.py b/oauth2client/contrib/multistore_file.py deleted file mode 100644 index 10f4cb4..0000000 --- a/oauth2client/contrib/multistore_file.py +++ /dev/null @@ -1,505 +0,0 @@ -# Copyright 2014 Google Inc. All rights reserved. -# -# 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. - -"""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. - } - } - ] - } - -""" - -import errno -import json -import logging -import os -import threading - -from oauth2client import client -from oauth2client import util -from oauth2client.contrib import locked_file - -__author__ = 'jbeda@google.com (Joe Beda)' - -logger = logging.getLogger(__name__) - -logger.warning( - 'The oauth2client.contrib.multistore_file module has been deprecated and ' - 'will be removed in the next release of oauth2client. Please migrate to ' - 'multiprocess_file_storage.') - -# A dict from 'filename'->_MultiStore instances -_multistores = {} -_multistores_lock = threading.Lock() - - -class Error(Exception): - """Base error for this module.""" - - -class NewerCredentialStoreError(Error): - """The credential store is a newer version than supported.""" - - -def _dict_to_tuple_key(dictionary): - """Converts a dictionary to a tuple that can be used as an immutable key. - - The resulting key is always sorted so that logically equivalent - dictionaries always produce an identical tuple for a key. - - Args: - dictionary: the dictionary to use as the key. - - Returns: - A tuple representing the dictionary in it's naturally sorted ordering. - """ - return tuple(sorted(dictionary.items())) - - -@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. - """ - multistore = _get_multistore(filename, warn_on_readonly=warn_on_readonly) - key = _dict_to_tuple_key(key_dict) - return multistore._get_storage(key) - - -@util.positional(1) -def get_all_credential_keys(filename, warn_on_readonly=True): - """Gets all the registered credential keys in the given Multistore. - - Args: - filename: The JSON file storing a set of credentials - warn_on_readonly: if True, log a warning if the store is readonly - - Returns: - A list of the credential keys present in the file. They are returned - as dictionaries that can be passed into - get_credential_storage_custom_key to get the actual credentials. - """ - multistore = _get_multistore(filename, warn_on_readonly=warn_on_readonly) - multistore._lock() - try: - return multistore._get_all_credential_keys() - finally: - multistore._unlock() - - -@util.positional(1) -def _get_multistore(filename, warn_on_readonly=True): - """A helper method to initialize the multistore with proper locking. - - Args: - filename: The JSON file storing a set of credentials - warn_on_readonly: if True, log a warning if the store is readonly - - Returns: - A multistore object - """ - 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() - return multistore - - -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 = locked_file.LockedFile(filename, 'r+', 'r') - 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(client.Storage): - """A Storage object that can 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(0o177) - try: - open(self._file.filename(), 'a+b').close() - finally: - os.umask(old_umask) - - def _lock(self): - """Lock the entire multistore.""" - self._thread_lock.acquire() - try: - self._file.open_and_lock() - except (IOError, OSError) as e: - if e.errno == errno.ENOSYS: - logger.warn('File system does not support locking the ' - 'credentials file.') - elif e.errno == errno.ENOLCK: - logger.warn('File system is out of resources for writing the ' - 'credentials file (is your disk full?).') - elif e.errno == errno.EDEADLK: - logger.warn('Lock contention on multistore file, opening ' - 'in read-only mode.') - elif e.errno == errno.EACCES: - logger.warn('Cannot access credentials file.') - else: - raise - 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 json.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) - json.dump(data, self._file.file_handle(), - sort_keys=True, indent=2, separators=(',', ': ')) - 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 {0}. ' - 'Only file_version of 1 is supported.'.format(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 = _dict_to_tuple_key(raw_key) - credential = None - credential = client.Credentials.new_from_json( - json.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 = json.loads(cred.to_json()) - raw_creds.append({'key': raw_key, 'credential': raw_cred}) - self._locked_json_write(raw_data) - - def _get_all_credential_keys(self): - """Gets all the registered credential keys in the multistore. - - Returns: - A list of dictionaries corresponding to all the keys currently - registered - """ - return [dict(key) for key in self._data.keys()] - - 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) |