diff --git a/docs/source/oauth2client.contrib.dictionary_storage.rst b/docs/source/oauth2client.contrib.dictionary_storage.rst new file mode 100644 index 0000000..1b59a2c --- /dev/null +++ b/docs/source/oauth2client.contrib.dictionary_storage.rst @@ -0,0 +1,7 @@ +oauth2client.contrib.dictionary_storage module +============================================== + +.. automodule:: oauth2client.contrib.dictionary_storage + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/oauth2client.contrib.rst b/docs/source/oauth2client.contrib.rst index 6ef8974..0caaa29 100644 --- a/docs/source/oauth2client.contrib.rst +++ b/docs/source/oauth2client.contrib.rst @@ -15,6 +15,7 @@ Submodules oauth2client.contrib.appengine oauth2client.contrib.devshell + oauth2client.contrib.dictionary_storage oauth2client.contrib.django_orm oauth2client.contrib.flask_util oauth2client.contrib.gce diff --git a/oauth2client/contrib/dictionary_storage.py b/oauth2client/contrib/dictionary_storage.py new file mode 100644 index 0000000..8d8e6cf --- /dev/null +++ b/oauth2client/contrib/dictionary_storage.py @@ -0,0 +1,66 @@ +# Copyright 2016 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. + + 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. + """ + + def __init__(self, dictionary, key, lock=None): + """Construct a DictionaryStorage instance.""" + 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. + + Returns: A :class:`oauth2client.client.OAuth2Credentials` instance. + """ + 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. + + Args: + credentials: A :class:`oauth2client.client.OAuth2Credentials` + instance. + """ + 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) diff --git a/tests/contrib/test_dictionary_storage.py b/tests/contrib/test_dictionary_storage.py new file mode 100644 index 0000000..0cb975f --- /dev/null +++ b/tests/contrib/test_dictionary_storage.py @@ -0,0 +1,110 @@ +# Copyright 2016 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.contrib.dictionary_storage""" + +import unittest2 + +from oauth2client import GOOGLE_TOKEN_URI +from oauth2client.client import OAuth2Credentials +from oauth2client.contrib.dictionary_storage import DictionaryStorage + + +def _generate_credentials(scopes=None): + return OAuth2Credentials( + 'access_tokenz', + 'client_idz', + 'client_secretz', + 'refresh_tokenz', + '3600', + GOOGLE_TOKEN_URI, + 'Test', + id_token={ + 'sub': '123', + 'email': 'user@example.com' + }, + scopes=scopes) + + +class DictionaryStorageTests(unittest2.TestCase): + + def test_constructor_defaults(self): + dictionary = {} + key = 'test-key' + storage = DictionaryStorage(dictionary, key) + + self.assertEqual(dictionary, storage._dictionary) + self.assertEqual(key, storage._key) + self.assertIsNone(storage._lock) + + def test_constructor_explicit(self): + dictionary = {} + key = 'test-key' + storage = DictionaryStorage(dictionary, key) + + lock = object() + storage = DictionaryStorage(dictionary, key, lock=lock) + self.assertEqual(storage._lock, lock) + + def test_get(self): + credentials = _generate_credentials() + dictionary = {} + key = 'credentials' + storage = DictionaryStorage(dictionary, key) + + self.assertIsNone(storage.get()) + + dictionary[key] = credentials.to_json() + returned = storage.get() + + self.assertIsNotNone(returned) + self.assertEqual(returned.access_token, credentials.access_token) + self.assertEqual(returned.id_token, credentials.id_token) + self.assertEqual(returned.refresh_token, credentials.refresh_token) + self.assertEqual(returned.client_id, credentials.client_id) + + def test_put(self): + credentials = _generate_credentials() + dictionary = {} + key = 'credentials' + storage = DictionaryStorage(dictionary, key) + + storage.put(credentials) + returned = storage.get() + + self.assertIn(key, dictionary) + self.assertIsNotNone(returned) + self.assertEqual(returned.access_token, credentials.access_token) + self.assertEqual(returned.id_token, credentials.id_token) + self.assertEqual(returned.refresh_token, credentials.refresh_token) + self.assertEqual(returned.client_id, credentials.client_id) + + def test_delete(self): + credentials = _generate_credentials() + dictionary = {} + key = 'credentials' + storage = DictionaryStorage(dictionary, key) + + storage.put(credentials) + + self.assertIn(key, dictionary) + + storage.delete() + + self.assertNotIn(key, dictionary) + self.assertIsNone(storage.get()) + + +if __name__ == '__main__': # pragma: NO COVER + unittest2.main()