1# Copyright 2016 Google Inc. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""OAuth 2.0 utilities for SQLAlchemy.
16
17Utilities for using OAuth 2.0 in conjunction with a SQLAlchemy.
18
19Configuration
20=============
21
22In order to use this storage, you'll need to create table
23with :class:`oauth2client.contrib.sqlalchemy.CredentialsType` column.
24It's recommended to either put this column on some sort of user info
25table or put the column in a table with a belongs-to relationship to
26a user info table.
27
28Here's an example of a simple table with a :class:`CredentialsType`
29column that's related to a user table by the `user_id` key.
30
31.. code-block:: python
32
33    from sqlalchemy import Column, ForeignKey, Integer
34    from sqlalchemy.ext.declarative import declarative_base
35    from sqlalchemy.orm import relationship
36
37    from oauth2client.contrib.sqlalchemy import CredentialsType
38
39
40    Base = declarative_base()
41
42
43    class Credentials(Base):
44        __tablename__ = 'credentials'
45
46        user_id = Column(Integer, ForeignKey('user.id'))
47        credentials = Column(CredentialsType)
48
49
50    class User(Base):
51        id = Column(Integer, primary_key=True)
52        # bunch of other columns
53        credentials = relationship('Credentials')
54
55
56Usage
57=====
58
59With tables ready, you are now able to store credentials in database.
60We will reuse tables defined above.
61
62.. code-block:: python
63
64    from sqlalchemy.orm import Session
65
66    from oauth2client.client import OAuth2Credentials
67    from oauth2client.contrib.sql_alchemy import Storage
68
69    session = Session()
70    user = session.query(User).first()
71    storage = Storage(
72        session=session,
73        model_class=Credentials,
74        # This is the key column used to identify
75        # the row that stores the credentials.
76        key_name='user_id',
77        key_value=user.id,
78        property_name='credentials',
79    )
80
81    # Store
82    credentials = OAuth2Credentials(...)
83    storage.put(credentials)
84
85    # Retrieve
86    credentials = storage.get()
87
88    # Delete
89    storage.delete()
90
91"""
92
93from __future__ import absolute_import
94
95import sqlalchemy.types
96
97from oauth2client import client
98
99
100class CredentialsType(sqlalchemy.types.PickleType):
101    """Type representing credentials.
102
103    Alias for :class:`sqlalchemy.types.PickleType`.
104    """
105
106
107class Storage(client.Storage):
108    """Store and retrieve a single credential to and from SQLAlchemy.
109    This helper presumes the Credentials
110    have been stored as a Credentials column
111    on a db model class.
112    """
113
114    def __init__(self, session, model_class, key_name,
115                 key_value, property_name):
116        """Constructor for Storage.
117
118        Args:
119            session: An instance of :class:`sqlalchemy.orm.Session`.
120            model_class: SQLAlchemy declarative mapping.
121            key_name: string, key name for the entity that has the credentials
122            key_value: key value for the entity that has the credentials
123            property_name: A string indicating which property on the
124                           ``model_class`` to store the credentials.
125                           This property must be a
126                           :class:`CredentialsType` column.
127        """
128        super(Storage, self).__init__()
129
130        self.session = session
131        self.model_class = model_class
132        self.key_name = key_name
133        self.key_value = key_value
134        self.property_name = property_name
135
136    def locked_get(self):
137        """Retrieve stored credential.
138
139        Returns:
140            A :class:`oauth2client.Credentials` instance or `None`.
141        """
142        filters = {self.key_name: self.key_value}
143        query = self.session.query(self.model_class).filter_by(**filters)
144        entity = query.first()
145
146        if entity:
147            credential = getattr(entity, self.property_name)
148            if credential and hasattr(credential, 'set_store'):
149                credential.set_store(self)
150            return credential
151        else:
152            return None
153
154    def locked_put(self, credentials):
155        """Write a credentials to the SQLAlchemy datastore.
156
157        Args:
158            credentials: :class:`oauth2client.Credentials`
159        """
160        filters = {self.key_name: self.key_value}
161        query = self.session.query(self.model_class).filter_by(**filters)
162        entity = query.first()
163
164        if not entity:
165            entity = self.model_class(**filters)
166
167        setattr(entity, self.property_name, credentials)
168        self.session.add(entity)
169
170    def locked_delete(self):
171        """Delete credentials from the SQLAlchemy datastore."""
172        filters = {self.key_name: self.key_value}
173        self.session.query(self.model_class).filter_by(**filters).delete()
174