Skip to content
This repository has been archived by the owner on Nov 5, 2019. It is now read-only.

Commit

Permalink
Adding dictionary storage for #319.
Browse files Browse the repository at this point in the history
* DictionaryStorage - implements an optionally-locked storage over a dictionary-like object.
* Moved optional locking logic into `Storage`, previously this has been duplicated in subclasses.
* Removed `flask_util.FlaskSessionStorage` and replaced it with `DictionaryStorage`.
  • Loading branch information
Jon Wayne Parrott committed Jan 5, 2016
1 parent 2421420 commit 1d4fd27
Show file tree
Hide file tree
Showing 11 changed files with 152 additions and 66 deletions.
7 changes: 7 additions & 0 deletions docs/source/oauth2client.contrib.dictionary_storage.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
oauth2client.contrib.dictionary_storage module
==============================================

.. automodule:: oauth2client.contrib.dictionary_storage
:members:
:undoc-members:
:show-inheritance:
1 change: 1 addition & 0 deletions docs/source/oauth2client.contrib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Submodules
.. toctree::

oauth2client.contrib.appengine
oauth2client.contrib.dictionary_storage
oauth2client.contrib.django_orm
oauth2client.contrib.flask_util
oauth2client.contrib.keyring_storage
Expand Down
16 changes: 13 additions & 3 deletions oauth2client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,21 +342,31 @@ class Storage(object):
such that multiple processes and threads can operate on a single
store.
"""
def __init__(self, lock=None):
"""Create a Storage instance.
Args:
lock: An optional threading.Lock-like object. Must implement at
least acquire() and release(). Does not need to be re-entrant.
"""
self._lock = lock

def acquire_lock(self):
"""Acquires any lock necessary to access this Storage.
This lock is not reentrant.
"""
pass
if self._lock is not None:
self._lock.acquire()

def release_lock(self):
"""Release the Storage lock.
Trying to release a lock that isn't held will result in a
RuntimeError.
RuntimeError in the case of a threading.Lock or multiprocessing.Lock.
"""
pass
if self._lock is not None:
self._lock.release()

def locked_get(self):
"""Retrieve credential.
Expand Down
2 changes: 2 additions & 0 deletions oauth2client/contrib/appengine.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,8 @@ def __init__(self, model, key_name, property_name, cache=None, user=None):
user: users.User object, optional. Can be used to grab user ID as a
key_name if no key name is specified.
"""
super(StorageByKeyName, self).__init__()

if key_name is None:
if user is None:
raise ValueError('StorageByKeyName called with no '
Expand Down
58 changes: 58 additions & 0 deletions oauth2client/contrib/dictionary_storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# 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.

"""Dictionary storage for OAuth2 Credentials."""

from oauth2client.client import OAuth2Credentials
from oauth2client.client import Storage


class DictionaryStorage(Storage):
"""Store and retrieve credentials to and from a dictionary-like object."""

def __init__(self, dictionary, key, lock=None):
"""Construct a DictionaryStorage instance.
Args:
dictionary: A dictionary or dictionary-like object.
key: A string or other hashable. The credentials will be stored in
``dictionary[key]``.
lock: An optional threading.Lock-like object. The lock will be
acquired before anything is written or read from the
dictionary.
"""
super(DictionaryStorage, self).__init__(lock=lock)
self._dictionary = dictionary
self._key = key

def locked_get(self):
"""Retrieve the credentials from the dictionary, if they exist."""
serialized = self._dictionary.get(self._key)

if serialized is None:
return None

credentials = OAuth2Credentials.from_json(serialized)
credentials.set_store(self)

return credentials

def locked_put(self, credentials):
"""Save the credentials to the dictionary."""
serialized = credentials.to_json()
self._dictionary[self._key] = serialized

def locked_delete(self):
"""Remove the credentials from the dictionary, if they exist."""
self._dictionary.pop(self._key, None)
1 change: 1 addition & 0 deletions oauth2client/contrib/django_orm.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def __init__(self, model_class, key_name, key_value, property_name):
property_name: string, name of the property that is an
CredentialsProperty
"""
super(Storage, self).__init__()
self.model_class = model_class
self.key_name = key_name
self.key_value = key_value
Expand Down
1 change: 1 addition & 0 deletions oauth2client/contrib/django_util/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class DjangoSessionStorage(client.Storage):
"""Storage implementation that uses Django sessions."""

def __init__(self, session):
super(DjangoSessionStorage, self).__init__()
self.session = session

def locked_get(self):
Expand Down
33 changes: 2 additions & 31 deletions oauth2client/contrib/flask_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,8 @@ def requires_calendar():
raise ImportError('The flask utilities require flask 0.9 or newer.')

from oauth2client.client import FlowExchangeError
from oauth2client.client import OAuth2Credentials
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.client import Storage
from oauth2client.contrib.dictionary_storage import DictionaryStorage
from oauth2client import clientsecrets


Expand Down Expand Up @@ -264,7 +263,7 @@ def init_app(self, app, scopes=None, client_secrets_file=None,
self.flow_kwargs = kwargs

if storage is None:
storage = FlaskSessionStorage()
storage = DictionaryStorage(session, _CREDENTIALS_KEY)
self.storage = storage

if scopes is None:
Expand Down Expand Up @@ -548,31 +547,3 @@ def http(self, *args, **kwargs):
if not self.credentials:
raise ValueError('No credentials available.')
return self.credentials.authorize(httplib2.Http(*args, **kwargs))


class FlaskSessionStorage(Storage):
"""Storage implementation that uses Flask sessions.
Note that flask's default sessions are signed but not encrypted. Users
can see their own credentials and non-https connections can intercept user
credentials. We strongly recommend using a server-side session
implementation.
"""

def locked_get(self):
serialized = session.get(_CREDENTIALS_KEY)

if serialized is None:
return None

credentials = OAuth2Credentials.from_json(serialized)
credentials.set_store(self)

return credentials

def locked_put(self, credentials):
session[_CREDENTIALS_KEY] = credentials.to_json()

def locked_delete(self):
if _CREDENTIALS_KEY in session:
del session[_CREDENTIALS_KEY]
17 changes: 1 addition & 16 deletions oauth2client/contrib/keyring_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,24 +59,9 @@ def __init__(self, service_name, user_name):
credentials are stored.
user_name: string, The name of the user to store credentials for.
"""
super(Storage, self).__init__(lock=threading.Lock())
self._service_name = service_name
self._user_name = user_name
self._lock = threading.Lock()

def acquire_lock(self):
"""Acquires any lock necessary to access this Storage.
This lock is not reentrant.
"""
self._lock.acquire()

def release_lock(self):
"""Release the Storage lock.
Trying to release a lock that isn't held will result in a
RuntimeError.
"""
self._lock.release()

def locked_get(self):
"""Retrieve Credential from file.
Expand Down
17 changes: 1 addition & 16 deletions oauth2client/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,14 @@ class Storage(BaseStorage):
"""Store and retrieve a single credential to and from a file."""

def __init__(self, filename):
super(Storage, self).__init__(lock=threading.Lock())
self._filename = filename
self._lock = threading.Lock()

def _validate_file(self):
if os.path.islink(self._filename):
raise CredentialsFileSymbolicLinkError(
'File: %s is a symbolic link.' % self._filename)

def acquire_lock(self):
"""Acquires any lock necessary to access this Storage.
This lock is not reentrant.
"""
self._lock.acquire()

def release_lock(self):
"""Release the Storage lock.
Trying to release a lock that isn't held will result in a
RuntimeError.
"""
self._lock.release()

def locked_get(self):
"""Retrieve Credential from file.
Expand Down
65 changes: 65 additions & 0 deletions tests/contrib/test_dictionary_storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# 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.

"""Unit tests for oauth2client.dictionary_storage"""

import unittest

from oauth2client import GOOGLE_TOKEN_URI
from oauth2client.client import OAuth2Credentials
from oauth2client.contrib.dictionary_storage import DictionaryStorage


__author__ = '[email protected] (Jon Wayne Parrott)'


class DictionaryStorageTests(unittest.TestCase):

def _generate_credentials(self, scopes=None):
return OAuth2Credentials(
'access_tokenz',
'client_idz',
'client_secretz',
'refresh_tokenz',
'3600',
GOOGLE_TOKEN_URI,
'Test',
id_token={
'sub': '123',
'email': '[email protected]'
},
scopes=scopes)

def test_string_key(self):
dictionary = {}
key = 'credentials'
credentials = self._generate_credentials()
storage = DictionaryStorage(dictionary, key)

storage.put(credentials)

self.assertTrue(key in dictionary)

retrieved = storage.get()

self.assertEqual(retrieved.access_token, credentials.access_token)

storage.delete()

self.assertTrue(key not in dictionary)
self.assertTrue(storage.get() is None)


if __name__ == '__main__': # pragma: NO COVER
unittest.main()

0 comments on commit 1d4fd27

Please sign in to comment.