-
Notifications
You must be signed in to change notification settings - Fork 0
/
facebook_util.py
197 lines (148 loc) · 6.74 KB
/
facebook_util.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import os
import Cookie
import logging
import unicodedata
import urllib2
from google.appengine.api import urlfetch
from app import App
import facebook
import layer_cache
import request_cache
FACEBOOK_ID_PREFIX = "http://facebookid.khanacademy.org/"
# TODO(benkomalo): rename these methods to have consistent naming
# ("facebook" vs "fb")
def is_facebook_user_id(user_id):
return user_id.startswith(FACEBOOK_ID_PREFIX)
def get_facebook_nickname_key(user_id):
return "facebook_nickname_%s" % user_id
@request_cache.cache_with_key_fxn(get_facebook_nickname_key)
@layer_cache.cache_with_key_fxn(
get_facebook_nickname_key,
layer=layer_cache.Layers.Memcache | layer_cache.Layers.Datastore,
persist_across_app_versions=True)
def get_facebook_nickname(user_id):
id = user_id.replace(FACEBOOK_ID_PREFIX, "")
graph = facebook.GraphAPI()
try:
profile = graph.get_object(id)
# Workaround http://code.google.com/p/googleappengine/issues/detail?id=573
# Bug fixed, utf-8 and nonascii is okay
return unicodedata.normalize('NFKD', profile["name"]).encode('utf-8', 'ignore')
except (facebook.GraphAPIError, urlfetch.DownloadError, AttributeError, urllib2.HTTPError):
# In the event of an FB error, don't cache the result.
return layer_cache.UncachedResult(user_id)
def get_current_facebook_user_id_from_cookies():
return get_user_id_from_profile(get_profile_from_cookies())
def delete_fb_cookies(handler):
""" Given the request handler, have it send headers to delete all FB cookies
associated with Khan Academy. """
if App.facebook_app_id:
# Note that Facebook also sets cookies on ".www.khanacademy.org"
# and "www.khanacademy.org" so we need to clear both.
handler.delete_cookie_including_dot_domain('fbsr_' + App.facebook_app_id)
handler.delete_cookie_including_dot_domain('fbm_' + App.facebook_app_id)
def get_facebook_user_id_from_oauth_map(oauth_map):
if oauth_map:
profile = _get_profile_from_fb_token(oauth_map.facebook_access_token)
return get_user_id_from_profile(profile)
return None
def get_fb_email_from_oauth_map(oauth_map):
"""Return the e-mail of the current logged in Facebook user, if possible.
A user's Facebook e-mail is the one specified as her primary e-mail account
in Facebook (not [email protected]).
This may return None if no valid Facebook credentials were found in the
OAuthmap.
"""
if oauth_map:
profile = _get_profile_from_fb_token(oauth_map.facebook_access_token)
if profile:
return profile.get("email", None)
return None
def get_user_id_from_profile(profile):
if profile is not None and "name" in profile and "id" in profile:
# Workaround http://code.google.com/p/googleappengine/issues/detail?id=573
name = unicodedata.normalize('NFKD', profile["name"]).encode('utf-8', 'ignore')
user_id = FACEBOOK_ID_PREFIX + profile["id"]
# Cache any future lookup of current user's facebook nickname in this request
request_cache.set(get_facebook_nickname_key(user_id), name)
return user_id
return None
def get_fb_email_from_cookies():
"""Return the e-mail of the current logged in Facebook user, if possible.
A user's Facebook e-mail is the one specified as her primary e-mail account
in Facebook (not [email protected]).
This may return None if no valid Facebook credentials were found, or
if the user did not allow us to see her e-mail address.
"""
profile = get_profile_from_cookies()
if profile:
return profile.get("email", None)
return None
def get_profile_from_cookies():
if App.facebook_app_secret is None:
return None
cookies = None
try:
cookies = Cookie.BaseCookie(os.environ.get('HTTP_COOKIE',''))
except Cookie.CookieError, error:
logging.debug("Ignoring Cookie Error, skipping Facebook login: '%s'" % error)
if cookies is None:
return None
morsel_key = "fbsr_" + App.facebook_app_id
morsel = cookies.get(morsel_key)
if morsel:
return get_profile_from_cookie_key_value(morsel_key, morsel.value)
return None
def get_profile_cache_key(cookie_key, cookie_value):
return "facebook:profile_from_cookie:%s" % cookie_value
@request_cache.cache_with_key_fxn(key_fxn = get_profile_cache_key)
@layer_cache.cache_with_key_fxn(
key_fxn = get_profile_cache_key,
layer = layer_cache.Layers.Memcache,
persist_across_app_versions=True)
def get_profile_from_cookie_key_value(cookie_key, cookie_value):
""" Communicate with Facebook to get a FB profile associated with
the specific cookie value.
Because this talks to Facebook via an HTTP request, this is cached
in memcache to avoid constant communication while a FB user is
browsing the site. If we encounter an error or fail to load
a Facebook profile, the results are not cached in memcache.
However, we also cache in request_cache because if we fail to load
a Facebook profile, we only want to do that once per request.
"""
fb_auth_dict = facebook.get_user_from_cookie_patched(
{ cookie_key: cookie_value },
App.facebook_app_id,
App.facebook_app_secret)
if fb_auth_dict:
profile = _get_profile_from_fb_token(fb_auth_dict["access_token"])
if profile:
return profile
# Don't cache any missing results in memcache
return layer_cache.UncachedResult(None)
def _get_profile_from_fb_token(access_token):
if App.facebook_app_secret is None:
return None
if not access_token:
logging.debug("Empty access token")
return None
profile = None
c_facebook_tries_left = 4
while not profile and c_facebook_tries_left > 0:
try:
graph = facebook.GraphAPI(access_token)
profile = graph.get_object("me")
except (facebook.GraphAPIError, urlfetch.DownloadError, AttributeError, urllib2.HTTPError), error:
if type(error) == urllib2.HTTPError and error.code == 400:
c_facebook_tries_left = 0
logging.error(("Ignoring '%s'. Assuming access_token " +
"is no longer valid: %s") % (error, access_token))
else:
c_facebook_tries_left -= 1
if c_facebook_tries_left > 1:
logging.info("Ignoring Facebook graph error '%s'. Tries left: %s" % (error, c_facebook_tries_left))
elif c_facebook_tries_left > 0:
logging.warning("Ignoring Facebook graph error '%s'. Last try." % error)
else:
logging.error("Last Facebook graph try failed with error '%s'." % error)
return profile