public
Last active

Hacked version of "official" (but now unsupported) Facebook Python SDK to support OAuth 2.0

  • Download Gist
facebook.py
Python
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 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
#!/usr/bin/env python
#
# Copyright 2010 Facebook
#
# 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.
 
"""Python client library for the Facebook Platform.
 
This client library is designed to support the Graph API and the official
Facebook JavaScript SDK, which is the canonical way to implement
Facebook authentication. Read more about the Graph API at
http://developers.facebook.com/docs/api. You can download the Facebook
JavaScript SDK at http://github.com/facebook/connect-js/.
 
If your application is using Google AppEngine's webapp framework, your
usage of this module might look like this:
 
user = facebook.get_user_from_cookie(self.request.cookies, key, secret)
if user:
graph = facebook.GraphAPI(user["access_token"])
profile = graph.get_object("me")
friends = graph.get_connections("me", "friends")
 
"""
 
import cgi
import hashlib
import time
import urllib
 
# Find a JSON parser
try:
import json
_parse_json = lambda s: json.loads(s)
except ImportError:
try:
import simplejson
_parse_json = lambda s: simplejson.loads(s)
except ImportError:
# For Google AppEngine
from django.utils import simplejson
_parse_json = lambda s: simplejson.loads(s)
 
 
class GraphAPI(object):
"""A client for the Facebook Graph API.
 
See http://developers.facebook.com/docs/api for complete documentation
for the API.
 
The Graph API is made up of the objects in Facebook (e.g., people, pages,
events, photos) and the connections between them (e.g., friends,
photo tags, and event RSVPs). This client provides access to those
primitive types in a generic way. For example, given an OAuth access
token, this will fetch the profile of the active user and the list
of the user's friends:
 
graph = facebook.GraphAPI(access_token)
user = graph.get_object("me")
friends = graph.get_connections(user["id"], "friends")
 
You can see a list of all of the objects and connections supported
by the API at http://developers.facebook.com/docs/reference/api/.
 
You can obtain an access token via OAuth or by using the Facebook
JavaScript SDK. See http://developers.facebook.com/docs/authentication/
for details.
 
If you are using the JavaScript SDK, you can use the
get_user_from_cookie() method below to get the OAuth access token
for the active user from the cookie saved by the SDK.
"""
def __init__(self, access_token=None):
self.access_token = access_token
 
def get_object(self, id, **args):
"""Fetchs the given object from the graph."""
return self.request(id, args)
 
def get_objects(self, ids, **args):
"""Fetchs all of the given object from the graph.
 
We return a map from ID to object. If any of the IDs are invalid,
we raise an exception.
"""
args["ids"] = ",".join(ids)
return self.request("", args)
 
def get_connections(self, id, connection_name, **args):
"""Fetchs the connections for given object."""
return self.request(id + "/" + connection_name, args)
 
def put_object(self, parent_object, connection_name, **data):
"""Writes the given object to the graph, connected to the given parent.
 
For example,
 
graph.put_object("me", "feed", message="Hello, world")
 
writes "Hello, world" to the active user's wall. Likewise, this
will comment on a the first post of the active user's feed:
 
feed = graph.get_connections("me", "feed")
post = feed["data"][0]
graph.put_object(post["id"], "comments", message="First!")
 
See http://developers.facebook.com/docs/api#publishing for all of
the supported writeable objects.
 
Most write operations require extended permissions. For example,
publishing wall posts requires the "publish_stream" permission. See
http://developers.facebook.com/docs/authentication/ for details about
extended permissions.
"""
assert self.access_token, "Write operations require an access token"
return self.request(parent_object + "/" + connection_name, post_args=data)
 
def put_wall_post(self, message, attachment={}, profile_id="me"):
"""Writes a wall post to the given profile's wall.
 
We default to writing to the authenticated user's wall if no
profile_id is specified.
 
attachment adds a structured attachment to the status message being
posted to the Wall. It should be a dictionary of the form:
 
{"name": "Link name"
"link": "http://www.example.com/",
"caption": "{*actor*} posted a new review",
"description": "This is a longer description of the attachment",
"picture": "http://www.example.com/thumbnail.jpg"}
 
"""
return self.put_object(profile_id, "feed", message=message, **attachment)
 
def put_comment(self, object_id, message):
"""Writes the given comment on the given post."""
return self.put_object(object_id, "comments", message=message)
 
def put_like(self, object_id):
"""Likes the given post."""
return self.put_object(object_id, "likes")
 
def delete_object(self, id):
"""Deletes the object with the given ID from the graph."""
self.request(id, post_args={"method": "delete"})
 
def request(self, path, args=None, post_args=None):
"""Fetches the given path in the Graph API.
 
We translate args to a valid query string. If post_args is given,
we send a POST request to the given path with the given arguments.
"""
if not args: args = {}
if self.access_token:
if post_args is not None:
post_args["access_token"] = self.access_token
else:
args["access_token"] = self.access_token
post_data = None if post_args is None else urllib.urlencode(post_args)
file = urllib.urlopen("https://graph.facebook.com/" + path + "?" +
urllib.urlencode(args), post_data)
try:
response = _parse_json(file.read())
finally:
file.close()
if response.get("error"):
raise GraphAPIError(response["error"]["type"],
response["error"]["message"])
return response
 
 
class GraphAPIError(Exception):
def __init__(self, type, message):
Exception.__init__(self, message)
self.type = type
 
 
##### NEXT TWO FUNCTIONS PULLED FROM https://github.com/jgorset/facepy/blob/master/facepy/signed_request.py
 
import base64
import hmac
 
 
def urlsafe_b64decode(str):
"""Perform Base 64 decoding for strings with missing padding."""
 
l = len(str)
pl = l % 4
return base64.urlsafe_b64decode(str.ljust(l+pl, "="))
 
 
def parse_signed_request(signed_request, secret):
"""
Parse signed_request given by Facebook (usually via POST),
decrypt with app secret.
 
Arguments:
signed_request -- Facebook's signed request given through POST
secret -- Application's app_secret required to decrpyt signed_request
"""
 
if "." in signed_request:
esig, payload = signed_request.split(".")
else:
return {}
 
sig = urlsafe_b64decode(str(esig))
data = _parse_json(urlsafe_b64decode(str(payload)))
 
if not isinstance(data, dict):
raise SignedRequestError("Pyload is not a json string!")
return {}
 
if data["algorithm"].upper() == "HMAC-SHA256":
if hmac.new(secret, payload, hashlib.sha256).digest() == sig:
return data
 
else:
raise SignedRequestError("Not HMAC-SHA256 encrypted!")
 
return {}
 
 
 
def get_user_from_cookie(cookies, app_id, app_secret):
"""Parses the cookie set by the official Facebook JavaScript SDK.
 
cookies should be a dictionary-like object mapping cookie names to
cookie values.
 
If the user is logged in via Facebook, we return a dictionary with the
keys "uid" and "access_token". The former is the user's Facebook ID,
and the latter can be used to make authenticated requests to the Graph API.
If the user is not logged in, we return None.
 
Download the official Facebook JavaScript SDK at
http://github.com/facebook/connect-js/. Read more about Facebook
authentication at http://developers.facebook.com/docs/authentication/.
"""
 
cookie = cookies.get("fbsr_" + app_id, "")
if not cookie:
return None
 
response = parse_signed_request(cookie, app_secret)
if not response:
return None
 
args = dict(
code = response['code'],
client_id = app_id,
client_secret = app_secret,
redirect_uri = '',
)
 
file = urllib.urlopen("https://graph.facebook.com/oauth/access_token?" + urllib.urlencode(args))
try:
token_response = file.read()
finally:
file.close()
 
access_token = cgi.parse_qs(token_response)["access_token"][-1]
 
return dict(
uid = response["user_id"],
access_token = access_token,
)

line 219: replace json.loads() with _parse_json()

Best regards, Walter

I used your "hacked" version in my app and got a bug at line 273.

Obviously, you don't handle the case where there is no "access_token" attribute in token_response. This other version: https://github.com/martey/python-sdk/blob/master/src/facebook.py handles it.

Therefore I made a mix between both of your hacked version guys.

Excellent hacked version, thank you! A shame that the "official" repo is completely outdated.

This saved my ass, thanks you so much for contributing this kind of stuff.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.