Skip to content
This repository was archived by the owner on Aug 3, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 94 additions & 2 deletions django_facebook/auth.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,104 @@
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.signals import user_logged_in
from django.contrib.auth import SESSION_KEY, BACKEND_SESSION_KEY

import facebook

from django_facebook.utils import get_fb_cookie_data


def get_facebook_user(request):
"""
Tries to get the facebook user from either de fsbr_ cookie or a canvas
request.

Return a dict containing the facebook user details, if found.

The dict contains at least the auth method and uid, it may also contain
the access_token and any other info made available by the authentication
method.
"""

def get_fb_user_cookie(self, request):
"""Get the user_id from the fbsr cookie."""
data = get_fb_cookie_data(request)
if not data.get('user_id'):
return None

fb_user = dict(uid=data['user_id'],
access_token='',
method='cookie')
return fb_user

def get_fb_user_canvas(self, request):
"""Attempt to find a user using a signed_request (canvas)."""
fb_user = None
if request.POST.get('signed_request'):
signed_request = request.POST["signed_request"]
data = facebook.parse_signed_request(signed_request,
settings.FACEBOOK_SECRET_KEY)
if data and data.get('user_id'):
fb_user = data['user']
fb_user['method'] = 'canvas'
fb_user['uid'] = data['user_id']
fb_user['access_token'] = data['oauth_token']
return fb_user


fb_user = {}
functions = [get_fb_user_cookie, get_fb_user_canvas]
for func in functions:
fb_user = func(request)
if fb_user:
break

return fb_user or None


def login(request, user):
"""
Persist the facebook user_id and the backend in the request. This way a
user doesn't have to reauthenticate on every request.
"""
if user is None:
user = request.user
if SESSION_KEY in request.session:
if request.session[SESSION_KEY] != user.username:
# To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.
request.session.flush()
else:
request.session.cycle_key()
request.session[SESSION_KEY] = user.username
request.session[BACKEND_SESSION_KEY] = user.backend
if hasattr(request, 'user'):
request.user = user
user_logged_in.send(sender=user.__class__, request=request, user=user)


class FacebookModelBackend(ModelBackend):

def authenticate(self, request=None):
if request:
user_data = get_facebook_user(request)
if user_data:
user = self.get_user(user_data['user_id'])
return user
return None

def get_user(self, user_id):
user, created = User.objects.get_or_create(
username=user_id)
# TODO profile callback on created
return user


class FacebookBackend(ModelBackend):
"""Authenticate a facebook user."""

def authenticate(self, fb_uid=None, fb_graphtoken=None):
"""
If we receive a facebook uid then the cookie has already been
Expand All @@ -22,13 +115,12 @@ class FacebookProfileBackend(ModelBackend):
"""
Authenticate a facebook user and autopopulate facebook data into the
user's profile.

"""

def authenticate(self, fb_uid=None, fb_graphtoken=None):
"""
If we receive a facebook uid then the cookie has already been
validated.

"""
if fb_uid and fb_graphtoken:
user, created = User.objects.get_or_create(username=fb_uid)
Expand Down
206 changes: 107 additions & 99 deletions django_facebook/middleware.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,121 @@
from django.conf import settings
from django.contrib import auth
from django.core.exceptions import ImproperlyConfigured
from django.contrib.auth import authenticate, logout
from django.contrib.auth import BACKEND_SESSION_KEY

import facebook
import datetime

from django_facebook.auth import login
from django_facebook.utils import get_access_token, get_fb_cookie_data


class DjangoFacebook(object):
class FacebookAccessor(object):
""" Simple accessor object for the Facebook user. """
def __init__(self, user):
self.user = user
self.uid = user['uid']
self.graph = facebook.GraphAPI(user['access_token'])

def __init__(self, request):
self.uid = request.user.username
access_token = get_access_token(request)
self.graph = facebook.GraphAPI(access_token)


class FacebookLoginMiddleware(object):
"""
Transparently integrate Django accounts with Facebook. You need to add
``auth.FacebookModelBackend`` to ``AUTHENTICATION_BACKENDS`` for this to
work.

If the user presents with a valid facebook cookie, or there is a
signed_request in request.POST (see ``auth.get_facebook_user``), then we
want them to be automatically logged in as that user. We rely on the
authentication backend to create the user if it does not exist.

If you do not want to persist the facebook login, also enable
FacebookLogOutMiddleware so that if they log out via fb:login-button they
are also logged out of Django.

We also want to allow people to log in with other backends, so we only log
someone in if their not already logged in.
"""

def process_request(self, request):
# AuthenticationMiddleware is required so that request.user exists.
if not hasattr(request, 'user'):
raise ImproperlyConfigured(
"The FacebookCookieLoginMiddleware requires the"
" authentication middleware to be installed. Edit your"
" MIDDLEWARE_CLASSES setting to insert"
" 'django.contrib.auth.middleware.AuthenticationMiddleware'"
" before the FacebookCookieLoginMiddleware class.")
if request.user.is_anonymous():
user = authenticate(request=request)
if user:
login(request, user)


class FacebookLogOutMiddleware(object):
"""
When a user logs out of facebook (on our page!), we won't get notified,
but the fbsr_ cookie wil be cleared. So this middleware checks if that
cookie is still present, and if not, logs the user out.

This works only if the user is logged in with the
``auth.FacebookModelBackend``.
"""

def process_request(self, request):
# AuthenticationMiddleware is required so that request.user exists.
if not hasattr(request, 'user'):
raise ImproperlyConfigured(
"The FacebookLogOutMiddleware requires the"
" authentication middleware to be installed. Edit your"
" MIDDLEWARE_CLASSES setting to insert"
" 'django.contrib.auth.middleware.AuthenticationMiddleware'"
" before the FacebookLogOutMiddleware class.")
if request.user.is_authenticated() and request.session.get(
BACKEND_SESSION_KEY) == \
'django_facebook.auth.FacebookModelBackend':
cookie_data = get_fb_cookie_data(request)
if not cookie_data.get('user_id') == request.user.username:
logout(request)


class FacebookHelperMiddleware(object):
"""
This middleware sets the ``facebook`` attribute on the request, which
contains the user id and a graph accessor.
"""

def process_request(self, request):
if request.user.is_authenticated():
if request.session.get(BACKEND_SESSION_KEY) == \
'django_facebook.auth.FacebookModelBackend':
request.facebook = FacebookAccessor(request)


class FacebookMiddleware(object):
"""
This middleware implements the basic behaviour:

- Log someone in if we can authenticate them (see ``auth``)
- Log someone out if we can't authenticate them anymore
- Add a ``facebook`` attribute to the request with a graph accessor.
"""
def process_request(self, request):
FacebookLoginMiddleware().process_request(request)
FacebookLogOutMiddleware().process_request(request)
FacebookHelperMiddleware().process_request(request)


class FacebookDebugCanvasMiddleware(object):
"""
Emulates signed_request behaviour to test your applications embedding.

This should be a raw string as is sent from facebook to the server in the
POST data, obtained by LiveHeaders, Firebug or similar. This should
POST data, obtained by LiveHeaders, Firebug or similar. This should be
initialised before FacebookMiddleware.

"""

def process_request(self, request):
cp = request.POST.copy()
request.POST = cp
Expand All @@ -36,9 +131,10 @@ class FacebookDebugCookieMiddleware(object):
This should be a raw string as is sent from a browser to the server,
obtained by LiveHeaders, Firebug or similar. The middleware takes care of
naming the cookie correctly. This should initialised before
FacebookMiddleware.
FacebookCookieLoginMiddleware.

"""

def process_request(self, request):
cookie_name = "fbs_" + settings.FACEBOOK_APP_ID
request.COOKIES[cookie_name] = settings.FACEBOOK_DEBUG_COOKIE
Expand All @@ -53,99 +149,11 @@ class FacebookDebugTokenMiddleware(object):
FACEBOOK_DEBUG_UID and FACEBOOK_DEBUG_TOKEN set in your configuration.

"""

def process_request(self, request):
user = {
'uid': settings.FACEBOOK_DEBUG_UID,
'access_token': settings.FACEBOOK_DEBUG_TOKEN,
}
request.facebook = DjangoFacebook(user)
return None


class FacebookMiddleware(object):
"""
Transparently integrate Django accounts with Facebook.

If the user presents with a valid facebook cookie, then we want them to be
automatically logged in as that user. We rely on the authentication backend
to create the user if it does not exist.

We do not want to persist the facebook login, so we avoid calling
auth.login() with the rationale that if they log out via fb:login-button
we want them to be logged out of Django also.

We also want to allow people to log in with other backends, which means we
need to be careful before replacing request.user.

"""
def get_fb_user_cookie(self, request):
""" Attempt to find a facebook user using a cookie. """
fb_user = facebook.get_user_from_cookie(request.COOKIES,
settings.FACEBOOK_APP_ID, settings.FACEBOOK_SECRET_KEY)
if fb_user:
fb_user['method'] = 'cookie'
return fb_user

def get_fb_user_canvas(self, request):
""" Attempt to find a user using a signed_request (canvas). """
fb_user = None
if request.POST.get('signed_request'):
signed_request = request.POST["signed_request"]
data = facebook.parse_signed_request(signed_request,
settings.FACEBOOK_SECRET_KEY)
if data and data.get('user_id'):
fb_user = data['user']
fb_user['method'] = 'canvas'
fb_user['uid'] = data['user_id']
fb_user['access_token'] = data['oauth_token']
return fb_user

def get_fb_user(self, request):
"""
Return a dict containing the facebook user details, if found.

The dict must contain the auth method, uid, access_token and any
other information that was made available by the authentication
method.

"""
fb_user = None
methods = ['get_fb_user_cookie', 'get_fb_user_canvas']
for method in methods:
fb_user = getattr(self, method)(request)
if (fb_user):
break
return fb_user

def process_request(self, request):
"""
Add `facebook` into the request context and attempt to authenticate
the user.

If no user was found, request.facebook will be None. Otherwise it will
contain a DjangoFacebook object containing:

uid: The facebook users UID
user: Any user information made available as part of the
authentication process
graph: A GraphAPI object connected to the current user.

An attempt to authenticate the user is also made. The fb_uid and
fb_graphtoken parameters are passed and are available for any
AuthenticationBackends.

The user however is not "logged in" via login() as facebook sessions
are ephemeral and must be revalidated on every request.

"""
fb_user = self.get_fb_user(request)
request.facebook = DjangoFacebook(fb_user) if fb_user else None

if fb_user and request.user.is_anonymous():
user = auth.authenticate(fb_uid=fb_user['uid'],
fb_graphtoken=fb_user['access_token'])
if user:
user.last_login = datetime.datetime.now()
user.save()
request.user = user
request.facebook = FacebookAccessor(request)
return None
Loading