Skip to content

Commit

Permalink
First stab at adding OAuth 2.0 support to pyfacebook.
Browse files Browse the repository at this point in the history
This is very very early code, and is based on a code base consisting
of a merge of pretty much any change I could find in a forked
project on github.  Therefore I'm certain to have introduced many
bugs that I won't've caught while testing my limited use case.

However, adding:

FACEBOOK_OAUTH2=True

into settings.py and creating a view something like:

def app_url(path):
    """Convert a site path to the equivalent facebook app url"""
    return '%s%s' % (FACEBOOK_APP_URL, path)

@facebook.require_login(next=app_url, required_permissions=('email','user_photos'))
def canvaspage(request):
:

Should work!  Use at your own risk, but feel free to merge and improve!
  • Loading branch information
woodcoder committed May 19, 2010
1 parent 9df59be commit 781173c
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 43 deletions.
2 changes: 1 addition & 1 deletion README
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Luke Worth (luke.worth@gmail.com)
Andreas Cederström (andreas@klydd.se)
Samuel Hoffstaetter (samuel@hoffstaetter.com)
Andreas Ehn (ehn@a8n.se)
Lee Li <shuge.lee@gmail.com>
Lee Li (shuge.lee@gmail.com)

http://github.com/sciyoshi/pyfacebook/

Expand Down
138 changes: 110 additions & 28 deletions facebook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
import urllib2
import httplib
import hmac
import hashlib
try:
import hashlib
except ImportError:
Expand All @@ -60,7 +59,6 @@
import binascii
import urlparse
import mimetypes
import time

# try to use simplejson first, otherwise fallback to XML
RESPONSE_FORMAT = 'JSON'
Expand Down Expand Up @@ -112,7 +110,7 @@ def urlread(url, data=None):

__all__ = ['Facebook','create_hmac']

VERSION = '0.1'
VERSION = '2.0'

FACEBOOK_URL = 'http://api.facebook.com/restserver.php'
FACEBOOK_VIDEO_URL = 'http://api-video.facebook.com/restserver.php'
Expand Down Expand Up @@ -805,7 +803,7 @@ def __fixup_param(name, klass, options, param):
else:
default = None
if param is None:
if klass is list:
if klass is list and default:
param = default[:]
else:
param = default
Expand All @@ -831,7 +829,8 @@ def facebook_method(self, *args, **kwargs):
raise TypeError("missing parameter %s" % param[0])

for name, klass, options in param_data:
value = __fixup_param(name, klass, options, params.get(name))
if name in params:
params[name] = __fixup_param(name, klass, options, params[name])

return self(method_name, params)

Expand Down Expand Up @@ -1187,6 +1186,10 @@ class Facebook(object):
api_key
Your API key, as set in the constructor.
app_id
Your application id, as set in the constructor or fetched from
fb_sig_app_id request parameter.
app_name
Your application's name, i.e. the APP_NAME in http://apps.facebook.com/APP_NAME/ if
this is for an internal web application. Optional, but useful for automatic redirects
Expand Down Expand Up @@ -1233,6 +1236,15 @@ class Facebook(object):
locale
The user's locale. Default: 'en_US'
oauth2:
Whether to use the new OAuth 2.0 authentication mechanism. Default: False
oauth2_token:
The current OAuth 2.0 token.
oauth2_token_expires:
The UNIX time when the OAuth 2.0 token expires (seconds).
page_id
Set to the page_id of the current page (if any)
Expand Down Expand Up @@ -1260,7 +1272,10 @@ class Facebook(object):
"""

def __init__(self, api_key, secret_key, auth_token=None, app_name=None, callback_path=None, internal=None, proxy=None, facebook_url=None, facebook_secure_url=None, generate_session_secret=0):
def __init__(self, api_key, secret_key, auth_token=None, app_name=None,
callback_path=None, internal=None, proxy=None,
facebook_url=None, facebook_secure_url=None,
generate_session_secret=0, app_id=None, oauth2=False):
"""
Initializes a new Facebook object which provides wrappers for the Facebook API.
Expand All @@ -1279,6 +1294,10 @@ def __init__(self, api_key, secret_key, auth_token=None, app_name=None, callback
"""
self.api_key = api_key
self.secret_key = secret_key
self.app_id = app_id
self.oauth2 = oauth2
self.oauth2_token = None
self.oauth2_token_expires = None
self.session_key = None
self.session_key_expires = None
self.auth_token = auth_token
Expand Down Expand Up @@ -1383,10 +1402,13 @@ def _build_post_args(self, method, args=None):
args[arg[0]] = str(arg[1]).lower()

args['method'] = method
args['api_key'] = self.api_key
args['v'] = '1.0'
args['format'] = RESPONSE_FORMAT
args['sig'] = self._hash_args(args)
if self.oauth2 and self.oauth2_token:
args['access_token'] = self.oauth2_token
else:
args['api_key'] = self.api_key
args['v'] = '1.0'
args['sig'] = self._hash_args(args)

return args

Expand All @@ -1401,6 +1423,7 @@ def _add_session_args(self, args=None):
#some calls don't need a session anymore. this might be better done in the markup
#raise RuntimeError('Session key not set. Make sure auth.getSession has been called.')

# TODO no need for session_key in oauth2?
args['session_key'] = self.session_key
args['call_id'] = str(int(time.time() * 1000))

Expand Down Expand Up @@ -1472,19 +1495,52 @@ def __call__(self, method=None, args=None, secure=False):
if self.proxy:
proxy_handler = urllib2.ProxyHandler(self.proxy)
opener = urllib2.build_opener(proxy_handler)
if secure:
if self.oauth2 or secure:
response = opener.open(self.facebook_secure_url, post_data).read()
else:
response = opener.open(self.facebook_url, post_data).read()
else:
if secure:
if self.oauth2 or secure:
response = urlread(self.facebook_secure_url, post_data)
else:
response = urlread(self.facebook_url, post_data)

print args
print response

return self._parse_response(response, method)


def get_oauth2_token(self, code, next, required_permissions=None):
"""
We've called authorize, and received a code, now we need to convert
this to an access_token
"""
args = {
'client_id': self.app_id,
'client_secret': self.secret_key,
'redirect_uri': next,
'code': code
}

if required_permissions:
args['scope'] = ",".join(required_permissions)

url = self.get_graph_url('oauth/access_token', **args)

if self.proxy:
proxy_handler = urllib2.ProxyHandler(self.proxy)
opener = urllib2.build_opener(proxy_handler)
response = opener.open(url).read()
else:
response = urlread(url)

result = urlparse.parse_qs(response)
self.oauth2_token = result['access_token'][0]
self.oauth2_token_expires = time.time() + int(result['expires'][0])


# URL helpers
def get_url(self, page, **args):
"""
Expand All @@ -1503,6 +1559,14 @@ def get_app_url(self, path=''):
return 'http://apps.facebook.com/%s/%s' % (self.app_name, path)


def get_graph_url(self, path='', **args):
"""
Returns the URL for the graph API with the supplied path and parameters
"""
return 'https://graph.facebook.com/%s?%s' % (path, urllib.urlencode(args))


def get_add_url(self, next=None):
"""
Returns the URL that the user should be redirected to in order to add the application.
Expand Down Expand Up @@ -1542,24 +1606,38 @@ def get_login_url(self, next=None, popup=False, canvas=True,
required_permissions -- permission required by the application
"""
args = {'api_key': self.api_key, 'v': '1.0'}

if next is not None:
args['next'] = next

if canvas is True:
args['canvas'] = 1

if popup is True:
args['popup'] = 1

if required_permissions:
args['req_perms'] = ",".join(required_permissions)
if self.oauth2:
args = {
'client_id': self.app_id,
'redirect_uri': next
}

if required_permissions:
args['scope'] = ",".join(required_permissions)

if self.auth_token is not None:
args['auth_token'] = self.auth_token

return self.get_url('login', **args)
if popup:
args['display'] = 'popup'

return self.get_graph_url('oauth/authorize', **args)
else:
args = {'api_key': self.api_key, 'v': '1.0'}

if next is not None:
args['next'] = next

if canvas is True:
args['canvas'] = 1

if popup is True:
args['popup'] = 1

if required_permissions:
args['req_perms'] = ",".join(required_permissions)

if self.auth_token is not None:
args['auth_token'] = self.auth_token

return self.get_url('login', **args)


def login(self, popup=False):
Expand Down Expand Up @@ -1688,6 +1766,10 @@ def check_session(self, request, params=None):
else:
self._friends = []

# If app_id is not set explicitly, pick it up from the param
if not self.app_id and 'app_id' in params:
self.app_id = params['app_id']

if 'session_key' in params:
self.session_key = params['session_key']
if 'user' in params:
Expand Down
70 changes: 57 additions & 13 deletions facebook/djangofb/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
import time
import datetime
import facebook

Expand Down Expand Up @@ -91,10 +92,53 @@ def newview(request, *args, **kwargs):
except ValueError:
session_check = False

# If using OAuth 2.0 and we've been accepted by the user
if fb.oauth2 and fb.added:
# See if we've got this user's token
if 'oauth2_token' in request.session:
fb.oauth2_token = request.session['oauth2_token']
fb.oauth2_token_expires = request.session['oauth2_token_expires']
session_check = True

print 'in session %s' % fb.added
print fb.oauth2_token
print '%s < %s' % (fb.oauth2_token_expires, time.time())

# We've got a code from a login, convert it to a access_token
elif 'code' in request.GET:
fb.get_oauth2_token(request.GET['code'], next=next,
required_permissions=required_permissions)

print 'from code %s' % fb.added
print fb.oauth2_token
print '%s < %s' % (fb.oauth2_token_expires, time.time())

request.session['oauth2_token'] = fb.oauth2_token
request.session['oauth2_token_expires'] = fb.oauth2_token_expires
session_check = True

# No sign of a token
else:
session_check = False

# Got a token, but it's expired
if 'oauth2_token_expires' in request.session and fb.oauth2_token_expires < time.time():
del request.session['oauth2_token']
del request.session['oauth2_token_expires']
session_check = False

if session_check and required_permissions:
req_perms = set(required_permissions)
perms = set(fb.ext_perms)
has_permissions = req_perms.issubset(perms)

# Let's do a real check, because ext_perms doesn't give full picture
if not has_permissions:
fql_perms = fb.fql.query('select %s from permissions where uid=%s' % (",".join(required_permissions), fb.uid))[0]
for permission, allowed in fql_perms.items():
if allowed == 1:
perms.add(permission)
has_permissions = req_perms.issubset(perms)
else:
has_permissions = True

Expand Down Expand Up @@ -185,20 +229,18 @@ def newview(request, *args, **kwargs):

# try to preserve the argspecs
try:
from functools import wraps
import decorator
except ImportError:
pass
else:
# Does not preserve arguments but the docstrings, function names etc
def updater(func):
@wraps(func)
def updated(view):
@wraps(view)
def anon(*args, **kwargs):
return view(*args, **kwargs)
return anon
return updated

# Can this be done with functools.wraps, but maintaining kwargs?
def updater(f):
def updated(*args, **kwargs):
original = f(*args, **kwargs)
def newdecorator(view):
return decorator.new_wrapper(original(view), view)
return decorator.new_wrapper(newdecorator, original)
return decorator.new_wrapper(updated, f)
require_login = updater(require_login)
require_add = updater(require_add)

Expand All @@ -214,12 +256,14 @@ class FacebookMiddleware(object):
"""

def __init__(self, api_key=None, secret_key=None, app_name=None, callback_path=None, internal=None):
def __init__(self, api_key=None, secret_key=None, app_name=None,
callback_path=None, internal=None, oauth2=None):
self.api_key = api_key or settings.FACEBOOK_API_KEY
self.secret_key = secret_key or settings.FACEBOOK_SECRET_KEY
self.app_name = app_name or getattr(settings, 'FACEBOOK_APP_NAME', None)
self.callback_path = callback_path or getattr(settings, 'FACEBOOK_CALLBACK_PATH', None)
self.internal = internal or getattr(settings, 'FACEBOOK_INTERNAL', True)
self.oauth2 = oauth2 or getattr(settings, 'FACEBOOK_OAUTH2', False)
self.proxy = None
if getattr(settings, 'USE_HTTP_PROXY', False):
self.proxy = settings.HTTP_PROXY
Expand All @@ -228,7 +272,7 @@ def process_request(self, request):
callback_path = self.callback_path
if callable(callback_path):
callback_path = callback_path()
_thread_locals.facebook = request.facebook = Facebook(self.api_key, self.secret_key, app_name=self.app_name, callback_path=callback_path, internal=self.internal, proxy=self.proxy)
_thread_locals.facebook = request.facebook = Facebook(self.api_key, self.secret_key, app_name=self.app_name, callback_path=callback_path, internal=self.internal, proxy=self.proxy, oauth2=self.oauth2)
if not self.internal:
if 'fb_sig_session_key' in request.GET and ('fb_sig_user' in request.GET or 'fb_sig_canvas_user' in request.GET):
request.facebook.session_key = request.session['facebook_session_key'] = request.GET['fb_sig_session_key']
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from setuptools import setup, find_packages

setup(name='pyfacebook',
version='0.3',
version='2.0',
description='Python Client Library for the Facebook API',
author='Samuel Cormier-Iijima',
author_email='sciyoshi@gmail.com',
Expand Down

0 comments on commit 781173c

Please sign in to comment.