This repository has been archived by the owner on Nov 5, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 430
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c82816c
commit c31ff3d
Showing
5 changed files
with
302 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
oauth2client.contrib.sqlalchemy module | ||
====================================== | ||
|
||
.. automodule:: oauth2client.contrib.sqlalchemy | ||
:members: | ||
:undoc-members: | ||
:show-inheritance: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
# 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. | ||
|
||
"""OAuth 2.0 utilities for SQLAlchemy. | ||
Utilities for using OAuth 2.0 in conjunction with a SQLAlchemy. | ||
Configuration | ||
============= | ||
In order to use this storage, you'll need to create table | ||
with :class:`oauth2client.contrib.sql_alchemy.CredentialsType` column. | ||
It's recommended to either put this column on some sort of user info | ||
table or put the column in a table with a belongs-to relationship to | ||
a user info table. | ||
Here's an example of a simple table with a :class:`CredentialsType` | ||
column that's related to a user table by the `user_id` key. | ||
.. code-block:: python | ||
from oauth2client.contrib.sql_alchemy import CredentialsType | ||
from sqlalchemy import Column, ForeignKey, Integer | ||
from sqlalchemy.ext.declarative import declarative_base | ||
from sqlalchemy.orm import relationship | ||
Base = declarative_base() | ||
class Credentials(Base): | ||
__tablename__ = 'credentials' | ||
user_id = Column(Integer, ForeignKey('user.id')) | ||
credentials = Column(CredentialsType) | ||
class User(Base): | ||
id = Column(Integer, primary_key=True) | ||
# bunch of other columns | ||
credentials = relationship('Credentials') | ||
Usage | ||
===== | ||
With tables ready, you are now able to store credentials in database. | ||
We will reuse tables defined above. | ||
.. code-block:: python | ||
from oauth2client.client import OAuth2Credentials | ||
from oauth2client.contrib.sql_alchemy import Storage | ||
from sqlalchemy.orm import Session | ||
session = Session() | ||
user = session.query(User).first() | ||
storage = Storage( | ||
session=session, | ||
model_class=Credentials, | ||
# This is the key column used to identify | ||
# the row that stores the credentials. | ||
key_name='user_id', | ||
key_value=user.id, | ||
property_name='credentials', | ||
) | ||
# Store | ||
credentials = OAuth2Credentials(...) | ||
storage.put(credentials) | ||
# Retrieve | ||
credentials = storage.get() | ||
# Delete | ||
storage.delete() | ||
""" | ||
|
||
from __future__ import absolute_import | ||
|
||
from oauth2client.client import Storage as BaseStorage | ||
from sqlalchemy.types import PickleType | ||
|
||
|
||
class CredentialsType(PickleType): | ||
"""Type representing credentials. | ||
Alias for :class:`sqlalchemy.types.PickleType`. | ||
""" | ||
|
||
|
||
class Storage(BaseStorage): | ||
"""Store and retrieve a single credential to and from SQLAlchemy. | ||
This helper presumes the Credentials | ||
have been stored as a Credentials column | ||
on a db model class. | ||
""" | ||
|
||
def __init__(self, session, model_class, key_name, | ||
key_value, property_name): | ||
"""Constructor for Storage. | ||
Args: | ||
session: An instance of :class:`sqlalchemy.orm.Session`. | ||
model_class: SQLAlchemy declarative mapping. | ||
key_name: string, key name for the entity that has the credentials | ||
key_value: key value for the entity that has the credentials | ||
property_name: A string indicating which property on the | ||
``model_class`` to store the credentials. | ||
This property must be a | ||
:class:`CredentialsType` column. | ||
""" | ||
super(Storage, self).__init__() | ||
|
||
self.session = session | ||
self.model_class = model_class | ||
self.key_name = key_name | ||
self.key_value = key_value | ||
self.property_name = property_name | ||
|
||
def locked_get(self): | ||
"""Retrieve stored credential. | ||
Returns: | ||
A :class:`oauth2client.Credentials` instance or `None`. | ||
""" | ||
credential = None | ||
|
||
session = self.session | ||
query = {self.key_name: self.key_value} | ||
|
||
entity = session.query(self.model_class).filter_by(**query).first() | ||
if entity: | ||
credential = getattr(entity, self.property_name) | ||
if credential and hasattr(credential, 'set_store'): | ||
credential.set_store(self) | ||
|
||
return credential | ||
|
||
def locked_put(self, credentials): | ||
"""Write a credentials to the SQLAlchemy datastore. | ||
Args: | ||
credentials: :class:`oauth2client.Credentials` | ||
""" | ||
session = self.session | ||
query = {self.key_name: self.key_value} | ||
|
||
entity = session.query(self.model_class).filter_by(**query).first() | ||
if not entity: | ||
entity = self.model_class(**query) | ||
|
||
setattr(entity, self.property_name, credentials) | ||
session.add(entity) | ||
|
||
def locked_delete(self): | ||
"""Delete credentials from the SQLAlchemy datastore.""" | ||
|
||
session = self.session | ||
query = {self.key_name: self.key_value} | ||
session.query(self.model_class).filter_by(**query).delete() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
# 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. | ||
|
||
import datetime | ||
import unittest | ||
|
||
from sqlalchemy import Column, create_engine, Integer | ||
from sqlalchemy.ext.declarative import declarative_base | ||
from sqlalchemy.orm import sessionmaker | ||
|
||
from oauth2client import GOOGLE_TOKEN_URI | ||
from oauth2client.client import OAuth2Credentials | ||
from oauth2client.contrib.sqlalchemy import CredentialsType, Storage | ||
|
||
Base = declarative_base() | ||
|
||
|
||
class DummyModel(Base): | ||
__tablename__ = 'dummy' | ||
|
||
id = Column(Integer, primary_key=True) | ||
key = Column(Integer) # we will query against this, because of ROWID | ||
credentials = Column(CredentialsType) | ||
|
||
|
||
class TestSQLAlchemyStorage(unittest.TestCase): | ||
engine = create_engine('sqlite://') | ||
|
||
@classmethod | ||
def setUpClass(cls): | ||
Base.metadata.create_all(cls.engine) | ||
cls.session = sessionmaker(bind=cls.engine) | ||
|
||
def setUp(self): | ||
self.credentials = OAuth2Credentials( | ||
access_token='token', | ||
client_id='client_id', | ||
client_secret='client_secret', | ||
refresh_token='refresh_token', | ||
token_expiry=datetime.datetime.utcnow(), | ||
token_uri=GOOGLE_TOKEN_URI, | ||
user_agent='DummyAgent', | ||
) | ||
|
||
def tearDown(self): | ||
session = self.session() | ||
session.query(DummyModel).filter_by(key=1).delete() | ||
session.commit() | ||
|
||
def compare_credentials(self, result): | ||
self.assertEqual(result.access_token, self.credentials.access_token) | ||
self.assertEqual(result.client_id, self.credentials.client_id) | ||
self.assertEqual(result.client_secret, self.credentials.client_secret) | ||
self.assertEqual(result.refresh_token, self.credentials.refresh_token) | ||
self.assertEqual(result.token_expiry, self.credentials.token_expiry) | ||
self.assertEqual(result.token_uri, self.credentials.token_uri) | ||
self.assertEqual(result.user_agent, self.credentials.user_agent) | ||
|
||
def test_get(self): | ||
session = self.session() | ||
session.add(DummyModel( | ||
key=1, | ||
credentials=self.credentials, | ||
)) | ||
session.commit() | ||
|
||
ret = Storage( | ||
session=session, | ||
model_class=DummyModel, | ||
key_name='key', | ||
key_value=1, | ||
property_name='credentials', | ||
).get() | ||
|
||
self.compare_credentials(ret) | ||
|
||
def test_put(self): | ||
session = self.session() | ||
Storage( | ||
session=session, | ||
model_class=DummyModel, | ||
key_name='key', | ||
key_value=1, | ||
property_name='credentials', | ||
).put(self.credentials) | ||
session.commit() | ||
|
||
ret = session.query(DummyModel).filter_by(key=1).first() | ||
self.compare_credentials(ret.credentials) | ||
|
||
def test_delete(self): | ||
session = self.session() | ||
session.add(DummyModel( | ||
key=1, | ||
credentials=self.credentials, | ||
)) | ||
session.commit() | ||
|
||
q = session.query(DummyModel).filter_by(key=1) | ||
self.assertTrue(q.first() is not None) | ||
Storage( | ||
session=session, | ||
model_class=DummyModel, | ||
key_name='key', | ||
key_value=1, | ||
property_name='credentials', | ||
).delete() | ||
session.commit() | ||
self.assertTrue(q.first() is None) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ basedeps = mock>=1.3.0 | |
nose | ||
flask | ||
unittest2 | ||
sqlalchemy | ||
deps = {[testenv]basedeps} | ||
django | ||
keyring | ||
|