20 __authors__ = [ |
20 __authors__ = [ |
21 '"Todd Larsen" <tlarsen@google.com>', |
21 '"Todd Larsen" <tlarsen@google.com>', |
22 ] |
22 ] |
23 |
23 |
24 |
24 |
|
25 import re |
|
26 |
25 from google.appengine.api import users |
27 from google.appengine.api import users |
|
28 from google.appengine.ext import db |
26 |
29 |
27 from soc.logic import out_of_band |
30 from soc.logic import out_of_band |
28 |
31 |
29 import soc.models.user |
32 import soc.models.user |
|
33 |
|
34 |
|
35 def getUserKeyNameFromId(id): |
|
36 """Return a Datastore key_name for a User derived from a Google Account. |
|
37 |
|
38 Args: |
|
39 id: a Google Account (users.User) object |
|
40 """ |
|
41 if not id: |
|
42 return None |
|
43 |
|
44 return 'User:%s' % id.email() |
30 |
45 |
31 |
46 |
32 def getIdIfMissing(id): |
47 def getIdIfMissing(id): |
33 """Gets Google Account of logged-in user (possibly None) if id is false. |
48 """Gets Google Account of logged-in user (possibly None) if id is false. |
34 |
49 |
36 accepts an optional id argument from the caller (such as one looked up |
51 accepts an optional id argument from the caller (such as one looked up |
37 already by another view that decides to "forward" the request to this |
52 already by another view that decides to "forward" the request to this |
38 other view). |
53 other view). |
39 |
54 |
40 Args: |
55 Args: |
41 id: a Google Account object, or None |
56 id: a Google Account (users.User) object, or None |
42 |
57 |
43 Returns: |
58 Returns: |
44 If id is non-false, it is simply returned; otherwise, the Google Account |
59 If id is non-false, it is simply returned; otherwise, the Google Account |
45 of currently logged-in user is returned (which could be None if no user |
60 of currently logged-in user is returned (which could be None if no user |
46 is logged in). |
61 is logged in). |
49 # id not initialized, so check if a Google Account is currently logged in |
64 # id not initialized, so check if a Google Account is currently logged in |
50 id = users.get_current_user() |
65 id = users.get_current_user() |
51 |
66 |
52 return id |
67 return id |
53 |
68 |
54 |
69 |
55 def getUserFromId(id): |
70 def getUserFromId(id): |
56 """Returns User entity for a Google Account, or None if not found. |
71 """Returns User entity for a Google Account, or None if not found. |
57 |
72 |
58 Args: |
73 Args: |
59 id: a Google Account object |
74 id: a Google Account (users.User) object |
60 """ |
75 """ |
61 return soc.models.user.User.gql('WHERE id = :1', id).get() |
76 # first, attempt a lookup by User:id key name |
62 |
77 key_name = getUserKeyNameFromId(id) |
63 |
78 |
|
79 if key_name: |
|
80 user = soc.models.user.User.get_by_key_name(key_name) |
|
81 else: |
|
82 user = None |
|
83 |
|
84 if user: |
|
85 return user |
|
86 |
|
87 # email address may have changed, so query the id property |
|
88 user = soc.models.user.User.gql('WHERE id = :1', id).get() |
|
89 |
|
90 if user: |
|
91 return user |
|
92 |
|
93 # last chance: perhaps the User changed their email address at some point |
|
94 user = soc.models.user.User.gql('WHERE former_ids = :1', id).get() |
|
95 |
|
96 return user |
|
97 |
|
98 |
64 def getUserIfMissing(user, id): |
99 def getUserIfMissing(user, id): |
65 """Conditionally returns User entity for a Google Account. |
100 """Conditionally returns User entity for a Google Account. |
66 |
101 |
67 This function is used to look up the User entity corresponding to the |
102 This function is used to look up the User entity corresponding to the |
68 supplied Google Account *if* the user parameter is false (usually None). |
103 supplied Google Account *if* the user parameter is false (usually None). |
72 one looked up already by another view that decides to "forward" the |
107 one looked up already by another view that decides to "forward" the |
73 HTTP request to this other view). |
108 HTTP request to this other view). |
74 |
109 |
75 Args: |
110 Args: |
76 user: None (usually), or an existing User entity |
111 user: None (usually), or an existing User entity |
77 id: a Google Account object |
112 id: a Google Account (users.User) object |
78 |
113 |
79 Returns: |
114 Returns: |
80 * user (which may have already been None if passed in that way by the |
115 * user (which may have already been None if passed in that way by the |
81 caller) if id is false or user is non-false |
116 caller) if id is false or user is non-false |
82 * results of getUserFromId() if user is false and id is non-false |
117 * results of getUserFromId() if user is false and id is non-false |
98 return True |
133 return True |
99 else: |
134 else: |
100 return False |
135 return False |
101 |
136 |
102 |
137 |
|
138 def isIdDeveloper(id=None): |
|
139 """Returns True if Google Account is a Developer with special privileges. |
|
140 |
|
141 Args: |
|
142 id: a Google Account (users.User) object; if id is not supplied, |
|
143 the current logged-in user is checked using the App Engine Users API. |
|
144 THIS ARGUMENT IS CURRENTLY IGNORED AND ONLY THE CURRENTLY LOGGED-IN |
|
145 USER IS CHECKED! |
|
146 |
|
147 See the TODO in the code below... |
|
148 """ |
|
149 if not id: |
|
150 return users.is_current_user_admin() |
|
151 |
|
152 # TODO(tlarsen): this Google App Engine function only checks the currently |
|
153 # logged in user. There needs to be another way to do this, such as a |
|
154 # field in the User Model... |
|
155 return users.is_current_user_admin() |
|
156 |
|
157 |
|
158 LINKNAME_PATTERN = r'''(?x) |
|
159 ^ |
|
160 [0-9a-z] # start with ASCII digit or lowercase |
|
161 ( |
|
162 [0-9a-z] # additional ASCII digit or lowercase |
|
163 | # -OR- |
|
164 _[0-9a-z] # underscore and ASCII digit or lowercase |
|
165 )* # zero or more of OR group |
|
166 $ |
|
167 ''' |
|
168 |
|
169 LINKNAME_REGEX = re.compile(LINKNAME_PATTERN) |
|
170 |
|
171 def isLinkNameFormatValid(link_name): |
|
172 """Returns True if link_name is in a valid format. |
|
173 |
|
174 Args: |
|
175 link_name: link name used in URLs to identify user |
|
176 """ |
|
177 if LINKNAME_REGEX.match(link_name): |
|
178 return True |
|
179 return False |
|
180 |
|
181 |
103 def getUserFromLinkName(link_name): |
182 def getUserFromLinkName(link_name): |
104 """Returns User entity for link_name or None if not found. |
183 """Returns User entity for link_name or None if not found. |
105 |
184 |
106 Args: |
185 Args: |
107 link_name: link name used in URLs to identify user |
186 link_name: link name used in URLs to identify user |
134 return link_name_user |
213 return link_name_user |
135 |
214 |
136 # else: a link name was supplied, but there is no User that has it |
215 # else: a link name was supplied, but there is no User that has it |
137 raise out_of_band.ErrorResponse( |
216 raise out_of_band.ErrorResponse( |
138 'There is no user with a "link name" of "%s".' % link_name, status=404) |
217 'There is no user with a "link name" of "%s".' % link_name, status=404) |
|
218 |
|
219 |
|
220 def doesLinkNameBelongToId(link_name, id=None): |
|
221 """Returns True if supplied link name belongs to supplied Google Account. |
|
222 |
|
223 Args: |
|
224 link_name: link name used in URLs to identify user |
|
225 id: a Google Account object; optional, current logged-in user will |
|
226 be used (or False will be returned if no user is logged in) |
|
227 """ |
|
228 id = getIdIfMissing(id) |
|
229 |
|
230 if not id: |
|
231 # id not supplied and no Google Account logged in, so link name cannot |
|
232 # belong to an unspecified User |
|
233 return False |
|
234 |
|
235 user = getUserFromId(id) |
|
236 |
|
237 if not user: |
|
238 # no User corresponding to id Google Account, so no link name at all |
|
239 return False |
|
240 |
|
241 if user.link_name != link_name: |
|
242 # User exists for id, but does not have this link name |
|
243 return False |
|
244 |
|
245 return True # link_name does actually belong to this Google Account |
|
246 |
|
247 |
|
248 def updateOrCreateUserFromId(id, **user_properties): |
|
249 """Update existing User entity, or create new one with supplied properties. |
|
250 |
|
251 Args: |
|
252 id: a Google Account object |
|
253 **user_properties: keyword arguments that correspond to User entity |
|
254 properties and their values |
|
255 |
|
256 Returns: |
|
257 the User entity corresponding to the Google Account, with any supplied |
|
258 properties changed, or a new User entity now associated with the Google |
|
259 Account and with the supplied properties |
|
260 """ |
|
261 # attempt to retrieve the existing User |
|
262 user = getUserFromId(id) |
|
263 |
|
264 if not user: |
|
265 # user did not exist, so create one in a transaction |
|
266 key_name = getUserKeyNameFromId(id) |
|
267 user = soc.models.user.User.get_or_insert( |
|
268 key_name, id=id, **user_properties) |
|
269 |
|
270 # there is no way to be sure if get_or_insert() returned a new User or |
|
271 # got an existing one due to a race, so update with user_properties anyway, |
|
272 # in a transaction |
|
273 return updateUserProperties(user, **user_properties) |
|
274 |
|
275 |
|
276 def updateUserProperties(user, **user_properties): |
|
277 """Update existing User entity using supplied User properties. |
|
278 |
|
279 Args: |
|
280 user: a User entity |
|
281 **user_properties: keyword arguments that correspond to User entity |
|
282 properties and their values |
|
283 |
|
284 Returns: |
|
285 the original User entity with any supplied properties changed |
|
286 """ |
|
287 def update(): |
|
288 return _unsafeUpdateUserProperties(user, **user_properties) |
|
289 |
|
290 return db.run_in_transaction(update) |
|
291 |
|
292 |
|
293 def _unsafeUpdateUserProperties(user, **user_properties): |
|
294 """(see updateUserProperties) |
|
295 |
|
296 Like updateUserProperties(), but not run within a transaction. |
|
297 """ |
|
298 properties = user.properties() |
|
299 |
|
300 for prop in properties.values(): |
|
301 if prop.name in user_properties: |
|
302 if prop.name == 'former_ids': |
|
303 # former_ids cannot be overwritten directly |
|
304 continue |
|
305 |
|
306 value = user_properties[prop.name] |
|
307 |
|
308 if prop.name == 'id': |
|
309 old_id = user.id |
|
310 |
|
311 if value != old_id: |
|
312 user.former_ids.append(old_id) |
|
313 |
|
314 prop.__set__(user, value) |
|
315 |
|
316 user.put() |
|
317 return user |