Remove the executable bit from the guestbook high-replication demo.
[gae-samples.git] / openid-consumer / store.py
blobf5f5d120441fef2bbd53b1ce00c2a3fc9e32e855
1 #!/usr/bin/python
3 # Copyright 2007, Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """
18 An OpenIDStore implementation that uses the datastore as its backing store.
19 Stores associations, nonces, and authentication tokens.
21 OpenIDStore is an interface from JanRain's OpenID python library:
22 http://openidenabled.com/python-openid/
24 For more, see openid/store/interface.py in that library.
25 """
27 import datetime
29 from openid.association import Association as OpenIDAssociation
30 from openid.store.interface import OpenIDStore
31 from openid.store import nonce
32 from google.appengine.ext import db
34 # number of associations and nonces to clean up in a single request.
35 CLEANUP_BATCH_SIZE = 50
38 class Association(db.Model):
39 """An association with another OpenID server, either a consumer or a provider.
40 """
41 url = db.LinkProperty()
42 handle = db.StringProperty()
43 association = db.TextProperty()
44 created = db.DateTimeProperty(auto_now_add=True)
47 class UsedNonce(db.Model):
48 """An OpenID nonce that has been used.
49 """
50 server_url = db.LinkProperty()
51 timestamp = db.DateTimeProperty()
52 salt = db.StringProperty()
55 class DatastoreStore(OpenIDStore):
56 """An OpenIDStore implementation that uses the datastore. See
57 openid/store/interface.py for in-depth descriptions of the methods.
59 They follow the OpenID python library's style, not Google's style, since
60 they override methods defined in the OpenIDStore class.
61 """
63 def storeAssociation(self, server_url, association):
64 """
65 This method puts a C{L{Association <openid.association.Association>}}
66 object into storage, retrievable by server URL and handle.
67 """
68 assoc = Association(url=server_url,
69 handle=association.handle,
70 association=association.serialize())
71 assoc.put()
73 def getAssociation(self, server_url, handle=None):
74 """
75 This method returns an C{L{Association <openid.association.Association>}}
76 object from storage that matches the server URL and, if specified, handle.
77 It returns C{None} if no such association is found or if the matching
78 association is expired.
80 If no handle is specified, the store may return any association which
81 matches the server URL. If multiple associations are valid, the
82 recommended return value for this method is the one that will remain valid
83 for the longest duration.
84 """
85 query = Association.all().filter('url', server_url)
86 if handle:
87 query.filter('handle', handle)
89 results = query.fetch(1)
90 if results:
91 association = OpenIDAssociation.deserialize(results[0].association)
92 if association.getExpiresIn() > 0:
93 # hasn't expired yet
94 return association
96 return None
98 def removeAssociation(self, server_url, handle):
99 """
100 This method removes the matching association if it's found, and returns
101 whether the association was removed or not.
103 query = Association.gql('WHERE url = :1 AND handle = :2',
104 server_url, handle)
105 return self._delete_first(query)
107 def useNonce(self, server_url, timestamp, salt):
108 """Called when using a nonce.
110 This method should return C{True} if the nonce has not been
111 used before, and store it for a while to make sure nobody
112 tries to use the same value again. If the nonce has already
113 been used or the timestamp is not current, return C{False}.
115 You may use L{openid.store.nonce.SKEW} for your timestamp window.
117 @change: In earlier versions, round-trip nonces were used and
118 a nonce was only valid if it had been previously stored
119 with C{storeNonce}. Version 2.0 uses one-way nonces,
120 requiring a different implementation here that does not
121 depend on a C{storeNonce} call. (C{storeNonce} is no
122 longer part of the interface.)
124 @param server_url: The URL of the server from which the nonce
125 originated.
127 @type server_url: C{str}
129 @param timestamp: The time that the nonce was created (to the
130 nearest second), in seconds since January 1 1970 UTC.
131 @type timestamp: C{int}
133 @param salt: A random string that makes two nonces from the
134 same server issued during the same second unique.
135 @type salt: str
137 @return: Whether or not the nonce was valid.
139 @rtype: C{bool}
141 query = UsedNonce.gql(
142 'WHERE server_url = :1 AND salt = :2 AND timestamp >= :3',
143 server_url, salt, self._expiration_datetime())
144 return query.fetch(1) == []
146 def cleanupNonces(self):
147 """Remove expired nonces from the store.
149 Discards any nonce from storage that is old enough that its
150 timestamp would not pass L{useNonce}.
152 This method is not called in the normal operation of the
153 library. It provides a way for store admins to keep
154 their storage from filling up with expired data.
156 @return: the number of nonces expired.
157 @returntype: int
159 query = UsedNonce.gql('WHERE timestamp < :1', self._expiration_datetime())
160 return self._cleanup_batch(query)
162 def cleanupAssociations(self):
163 """Remove expired associations from the store.
165 This method is not called in the normal operation of the
166 library. It provides a way for store admins to keep
167 their storage from filling up with expired data.
169 @return: the number of associations expired.
170 @returntype: int
172 query = Association.gql('WHERE created < :1', self._expiration_datetime())
173 return self._cleanup_batch(query)
175 def cleanup(self):
176 """Shortcut for C{L{cleanupNonces}()}, C{L{cleanupAssociations}()}.
178 This method is not called in the normal operation of the
179 library. It provides a way for store admins to keep
180 their storage from filling up with expired data.
182 return self.cleanupNonces(), self.cleanupAssociations()
184 def _delete_first(self, query):
185 """Deletes the first result for the given query.
187 Returns True if an entity was deleted, false if no entity could be deleted
188 or if the query returned no results.
190 results = query.fetch(1)
192 if results:
193 try:
194 results[0].delete()
195 return True
196 except db.Error:
197 return False
198 else:
199 return False
201 def _cleanup_batch(self, query):
202 """Deletes the first batch of entities that match the given query.
204 Returns the number of entities that were deleted.
206 to_delete = list(query.fetch(CLEANUP_BATCH_SIZE))
208 # can't use batch delete since they're all root entities :/
209 for entity in to_delete:
210 entity.delete()
212 return len(to_delete)
214 def _expiration_datetime(self):
215 """Returns the current expiration date for nonces and associations.
217 return datetime.datetime.now() - datetime.timedelta(seconds=nonce.SKEW)