├── __init__.py
├── django_facebook
├── __init__.py
├── templatetags
│ ├── __init__.py
│ └── facebook.py
├── templates
│ ├── tags
│ │ ├── facebook_init.html
│ │ └── facebook_load.html
│ └── facebook_debug.html
├── auth.py
├── decorators.py
└── middleware.py
├── .gitignore
├── setup.py
└── README.md
/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_facebook/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_facebook/templatetags/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.egg-info
2 | *.pyc
3 | .DS_Store
4 |
--------------------------------------------------------------------------------
/django_facebook/templates/tags/facebook_init.html:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/django_facebook/templates/tags/facebook_load.html:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | setup(
4 | name='django-facebook',
5 | version='0.1',
6 | description='Replace Django Authentication with Facebook',
7 | long_description=open('README.md').read(),
8 | author='Aidan Lister',
9 | author_email='aidan@php.net',
10 | url='http://github.com/aidanlister/django-facebook',
11 | license='MPL',
12 | packages=find_packages(),
13 | include_package_data=True,
14 | zip_safe=False,
15 | install_requires=[],
16 | classifiers=[
17 | 'Development Status :: 4 - Beta',
18 | 'Environment :: Web Environment',
19 | 'Framework :: Django',
20 | 'Intended Audience :: Developers',
21 | 'Operating System :: OS Independent',
22 | 'Programming Language :: Python',
23 | 'Topic :: Software Development :: Libraries :: Python Modules',
24 | ]
25 | )
26 |
--------------------------------------------------------------------------------
/django_facebook/templates/facebook_debug.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block facebook_code %}
4 | FB.getLoginStatus(function(response) {
5 | var update = $('#getLoginStatus')
6 | if (response.session) {
7 | update.text('response.session.uid = ' + response.session.uid)
8 | } else {
9 | update.text('response.session = null')
10 | }
11 | });
12 | {% endblock %}
13 |
14 |
15 | {% block content %}
16 |
17 | | request.user | {{ request.user }} |
18 | | request.facebook.uid | {{ request.facebook.uid }} |
19 | | request.facebook.user | {{ request.facebook.user }} |
20 | | request.COOKIES | {{ request.COOKIES }} |
21 | | FB.getLoginStatus | Loading ... |
22 |
23 |
24 |
25 |
26 | {% endblock %}
27 |
--------------------------------------------------------------------------------
/django_facebook/templatetags/facebook.py:
--------------------------------------------------------------------------------
1 | from django import template
2 | from django.conf import settings
3 | register = template.Library()
4 |
5 |
6 | @register.inclusion_tag('tags/facebook_load.html')
7 | def facebook_load():
8 | pass
9 |
10 | @register.tag
11 | def facebook_init(parser, token):
12 | nodelist = parser.parse(('endfacebook',))
13 | parser.delete_first_token()
14 | return FacebookNode(nodelist)
15 |
16 | class FacebookNode(template.Node):
17 | """ Allow code to be added inside the facebook asynchronous closure. """
18 | def __init__(self, nodelist):
19 | try:
20 | app_id = settings.FACEBOOK_APP_ID
21 | except AttributeError:
22 | raise template.TemplateSyntaxError, "%r tag requires FACEBOOK_APP_ID to be configured." \
23 | % token.contents.split()[0]
24 | self.app_id = app_id
25 | self.nodelist = nodelist
26 |
27 | def render(self, context):
28 | t = template.loader.get_template('tags/facebook_init.html')
29 | code = self.nodelist.render(context)
30 | custom_context = context
31 | custom_context['code'] = code
32 | custom_context['app_id'] = self.app_id
33 | return t.render(context)
34 |
35 | @register.simple_tag
36 | def facebook_perms():
37 | return ",".join(getattr(settings, 'FACEBOOK_PERMS', []))
38 |
--------------------------------------------------------------------------------
/django_facebook/auth.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.contrib.auth.models import User
3 | from django.contrib.auth.backends import ModelBackend
4 | import facebook
5 |
6 | class FacebookBackend(ModelBackend):
7 | """ Authenticate a facebook user. """
8 | def authenticate(self, fb_uid=None, fb_graphtoken=None):
9 | """ If we receive a facebook uid then the cookie has already been validated. """
10 | if fb_uid:
11 | user, created = User.objects.get_or_create(username=fb_uid)
12 | return user
13 | return None
14 |
15 |
16 | class FacebookProfileBackend(ModelBackend):
17 | """ Authenticate a facebook user and autopopulate facebook data into the users profile. """
18 | def authenticate(self, fb_uid=None, fb_graphtoken=None):
19 | """ If we receive a facebook uid then the cookie has already been validated. """
20 | if fb_uid and fb_graphtoken:
21 | user, created = User.objects.get_or_create(username=fb_uid)
22 | if created:
23 | # It would be nice to replace this with an asynchronous request
24 | graph = facebook.GraphAPI(fb_graphtoken)
25 | me = graph.get_object('me')
26 | if me:
27 | if me.get('first_name'): user.first_name = me['first_name']
28 | if me.get('last_name'): user.last_name = me['last_name']
29 | if me.get('email'): user.email = me['email']
30 | user.save()
31 | return user
32 | return None
33 |
--------------------------------------------------------------------------------
/django_facebook/decorators.py:
--------------------------------------------------------------------------------
1 | import facebook
2 | from functools import update_wrapper, wraps
3 | from django.contrib.auth import REDIRECT_FIELD_NAME
4 | from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
5 | from django.utils.decorators import available_attrs
6 | from django.utils.http import urlquote
7 | from django.conf import settings
8 |
9 |
10 | def canvas_only(function=None):
11 | """
12 | Decorator ensures that a page is only accessed from within a facebook application.
13 | """
14 | def _dec(view_func):
15 | def _view(request, *args, **kwargs):
16 | # Make sure we're receiving a signed_request from facebook
17 | if not request.POST.get('signed_request'):
18 | return HttpResponseBadRequest()
19 |
20 | # Parse the request and ensure it's valid
21 | try:
22 | signed_request = request.POST["signed_request"]
23 | data = facebook.parse_signed_request(signed_request, settings.FACEBOOK_SECRET_KEY)
24 | except ValueError:
25 | return HttpResponseBadRequest()
26 |
27 | # If the user has not authorised redirect them
28 | if not data.get('user_id'):
29 | scope = getattr(settings, 'FACEBOOK_PERMS', None)
30 | auth_url = facebook.auth_url(settings.FACEBOOK_APP_ID, settings.FACEBOOK_CANVAS_PAGE, scope)
31 | markup = '' % auth_url
32 | return HttpResponse(markup)
33 |
34 | # Success so return the view
35 | return view_func(request, *args, **kwargs)
36 | return _view
37 | return _dec(function)
38 |
39 |
40 | def facebook_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME):
41 | """
42 | Decorator for views that checks that the user is logged in, redirecting
43 | to the log-in page if necessary.
44 | """
45 | def _passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
46 | if not login_url:
47 | from django.conf import settings
48 | login_url = settings.LOGIN_URL
49 |
50 | def decorator(view_func):
51 | def _wrapped_view(request, *args, **kwargs):
52 | if test_func(request):
53 | return view_func(request, *args, **kwargs)
54 | path = urlquote(request.get_full_path())
55 | tup = login_url, redirect_field_name, path
56 | return HttpResponseRedirect('%s?%s=%s' % tup)
57 | return wraps(view_func, assigned=available_attrs(view_func))(_wrapped_view)
58 | return decorator
59 |
60 | actual_decorator = _passes_test(
61 | lambda r: r.facebook,
62 | redirect_field_name=redirect_field_name
63 | )
64 |
65 | if function:
66 | return actual_decorator(function)
67 | return actual_decorator
68 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Facebook integration for your Django website
2 | =============================================
3 |
4 | Installation:
5 | ------------
6 | Simply add ``django_facebook`` to your INSTALLED_APPS and configure
7 | the following settings:
8 |
9 | FACEBOOK_APP_ID = ''
10 | FACEBOOK_API_KEY = ''
11 | FACEBOOK_SECRET_KEY = ''
12 |
13 | # Optionally set default permissions to request, e.g: ['email', 'user_about_me']
14 | FACEBOOK_PERMS = []
15 |
16 | # And for local debugging, use one of the debug middlewares and set:
17 | FACEBOOK_DEBUG_TOKEN = ''
18 | FACEBOOK_DEBUG_UID = ''
19 | FACEBOOK_DEBUG_COOKIE = ''
20 | FACEBOOK_DEBUG_SIGNEDREQ = ''
21 |
22 |
23 | Templates:
24 | ---------
25 | A few helpers for using the Javascript SDK can be enabled by adding
26 | this to your base template in the ```` section:
27 |
28 | {% load facebook %}
29 | {% facebook_init %}
30 | {% block facebook_code %}{% endblock %}
31 | {% endfacebook %}
32 |
33 | And this should be added just before your ```` tag:
34 |
35 | {% facebook_load %}
36 |
37 | The ``facebook_load`` template tag inserts the code required to
38 | asynchronously load the facebook javascript SDK. The ``facebook_init``
39 | tag calls ``FB.init`` with your configured application settings. It is
40 | best to put your facebook related javascript into the ``facebook_code``
41 | region so that it can be called by the asynchronous handler.
42 |
43 | You may find the ``facebook_perms`` tag useful, which takes the setting
44 | in FACEBOOK_PERMS and prints the extended permissions out in a
45 | comma-separated list.
46 |
47 |
49 |
50 |
51 | A helpful debugging page to view the status of your facebook login can
52 | be enabled by adding this to your url configuration:
53 |
54 | (r'^facebook_debug/', direct_to_template, {'template':'facebook_debug.html'}),
55 |
56 |
57 | Once this is in place you are ready to start with the facebook javascript SDK!
58 |
59 | This module also provides all of the tools necessary for working with facebook
60 | on the backend:
61 |
62 |
63 | Middleware:
64 | ----------
65 | This provides seamless access to the Facebook Graph via request object.
66 |
67 | If a user accesses your site with:
68 | - a valid cookie (Javascript SDK), or
69 | - a valid ``signed_request`` parameter (Facebook Canvas App),
70 | then your views will have access to request.facebook.graph and you can
71 | begin querying the graph immediately. For example, to get the users friends:
72 |
73 | def friends(request):
74 | if request.facebook:
75 | friends = request.facebook.graph.get_connections('me', 'friends')
76 |
77 | To use the middleware, simply add this to your MIDDLEWARE_CLASSES:
78 | 'django_facebook.middleware.FacebookMiddleware'
79 |
80 |
81 | ``FacebookDebugCookieMiddleware`` allows you to set a cookie in your settings
82 | file and use this to simulate facebook logins offline.
83 |
84 | ``FacebookDebugTokenMiddleware`` allows you to set a uid and access_token to
85 | force facebook graph availability.
86 |
87 | ``FacebookDebugCanvasMiddleware`` allows you to set a signed_request to mimic
88 | a page being loaded as a canvas inside Facebook.
89 |
90 |
91 | Authentication:
92 | --------------
93 | This provides seamless integration with the Django user system.
94 |
95 | If a user accesses your site with a valid facebook cookie, a user
96 | account is automatically created or retrieved based on the facebook UID.
97 |
98 | To use the backend, add this to your AUTHENTICATION_BACKENDS:
99 | 'django_facebook.auth.FacebookBackend'
100 |
101 | To automatically populate your User and Profile models with facebook data, use:
102 | 'django_facebook.auth.FacebookProfileBackend'
103 |
104 | Don't forget to include the default backend if you want to use standard
105 | logins for users as well:
106 | 'django.contrib.auth.backends.ModelBackend'
107 |
108 |
109 | Decorators:
110 | ----------
111 | ``@facebook_required`` is a decorator which ensures the user is currently
112 | logged in with facebook and has access to the facebook graph. It is a replacement
113 | for ``@login_required`` if you are not using the facebook authentication backend.
114 |
115 | ``@canvas_required`` is a decorater to ensure the view is being loaded with
116 | a valid ``signed_request`` via Facebook Canvas. If signed_request is not found, the
117 | decorator will return a HTTP 400. If signed_request is found but the user has not
118 | authorised, the decorator will redirect the user to authorise.
119 |
--------------------------------------------------------------------------------
/django_facebook/middleware.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.contrib import auth
3 | import facebook
4 | import datetime
5 |
6 |
7 | class DjangoFacebook(object):
8 | """ Simple accessor object for the Facebook user. """
9 | def __init__(self, user):
10 | self.user = user
11 | self.uid = user['uid']
12 | self.graph = facebook.GraphAPI(user['access_token'])
13 |
14 |
15 | class FacebookDebugCanvasMiddleware(object):
16 | """ Emulates signed_request behaviour to test your applications embedding.
17 |
18 | This should be a raw string as is sent from facebook to the server in the POST
19 | data, obtained by LiveHeaders, Firebug or similar. This should initialised
20 | before FacebookMiddleware.
21 | """
22 | def process_request(self, request):
23 | cp = request.POST.copy()
24 | request.POST = cp
25 | request.POST['signed_request'] = settings.FACEBOOK_DEBUG_SIGNEDREQ
26 | return None
27 |
28 |
29 | class FacebookDebugCookieMiddleware(object):
30 | """ Sets an imaginary cookie to make it easy to work from a development environment.
31 |
32 | This should be a raw string as is sent from a browser to the server, obtained by
33 | LiveHeaders, Firebug or similar. The middleware takes care of naming the cookie
34 | correctly. This should initialised before FacebookMiddleware.
35 | """
36 | def process_request(self, request):
37 | cookie_name = "fbs_" + settings.FACEBOOK_APP_ID
38 | request.COOKIES[cookie_name] = settings.FACEBOOK_DEBUG_COOKIE
39 | return None
40 |
41 |
42 | class FacebookDebugTokenMiddleware(object):
43 | """ Forces a specific access token to be used.
44 |
45 | This should be used instead of FacebookMiddleware. Make sure you have
46 | FACEBOOK_DEBUG_UID and FACEBOOK_DEBUG_TOKEN set in your configuration.
47 | """
48 | def process_request(self, request):
49 | user = {
50 | 'uid':settings.FACEBOOK_DEBUG_UID,
51 | 'access_token':settings.FACEBOOK_DEBUG_TOKEN,
52 | }
53 | request.facebook = DjangoFacebook(user)
54 | return None
55 |
56 |
57 | class FacebookMiddleware(object):
58 | """ Transparently integrate Django accounts with Facebook.
59 |
60 | If the user presents with a valid facebook cookie, then we want them to be
61 | automatically logged in as that user. We rely on the authentication backend
62 | to create the user if it does not exist.
63 |
64 | We do not want to persist the facebook login, so we avoid calling auth.login()
65 | with the rationale that if they log out via fb:login-button we want them to
66 | be logged out of Django also.
67 |
68 | We also want to allow people to log in with other backends, which means we
69 | need to be careful before replacing request.user.
70 | """
71 |
72 | def get_fb_user_cookie(self, request):
73 | """ Attempt to find a facebook user using a cookie. """
74 | fb_user = facebook.get_user_from_cookie(request.COOKIES,
75 | settings.FACEBOOK_APP_ID, settings.FACEBOOK_SECRET_KEY)
76 | if fb_user:
77 | fb_user['method'] = 'cookie'
78 | return fb_user
79 |
80 | def get_fb_user_canvas(self, request):
81 | """ Attempt to find a user using a signed_request (canvas). """
82 | fb_user = None
83 | if request.POST.get('signed_request'):
84 | signed_request = request.POST["signed_request"]
85 | try:
86 | data = facebook.parse_signed_request(signed_request, settings.FACEBOOK_SECRET_KEY)
87 | except ValueError:
88 | pass
89 | if data and data.get('user_id'):
90 | fb_user = data['user']
91 | fb_user['method'] = 'canvas'
92 | fb_user['uid'] = data['user_id']
93 | fb_user['access_token'] = data['oauth_token']
94 | return fb_user
95 |
96 | def get_fb_user(self, request):
97 | """ Return a dict containing the facebook user details, if found.
98 |
99 | The dict must contain the auth method, uid, access_token and any
100 | other information that was made available by the authentication
101 | method.
102 | """
103 | fb_user = None
104 | methods = ['get_fb_user_cookie', 'get_fb_user_canvas']
105 | for method in methods:
106 | fb_user = getattr(self, method)(request)
107 | if (fb_user):
108 | break
109 | return fb_user
110 |
111 | def process_request(self, request):
112 | """ Add `facebook` into the request context and attempt to authenticate the user.
113 |
114 | If no user was found, request.facebook will be None. Otherwise it will contain
115 | a DjangoFacebook object containing:
116 | uid: The facebook users UID
117 | user: Any user information made available as part of the authentication process
118 | graph: A GraphAPI object connected to the current user.
119 |
120 | An attempt to authenticate the user is also made. The fb_uid and fb_graphtoken
121 | parameters are passed and are available for any AuthenticationBackends.
122 |
123 | The user however is not "logged in" via login() as facebook sessions are ephemeral
124 | and must be revalidated on every request.
125 | """
126 | fb_user = self.get_fb_user(request)
127 | request.facebook = DjangoFacebook(fb_user) if fb_user else None
128 |
129 | if fb_user and request.user.is_anonymous():
130 | user = auth.authenticate(fb_uid=fb_user['uid'], fb_graphtoken=fb_user['access_token'])
131 | if user:
132 | user.last_login = datetime.datetime.now()
133 | user.save()
134 | request.user = user
135 | return None
136 |
--------------------------------------------------------------------------------