# FacebookConnectMiddleware.py
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.conf import settings

import md5
import urllib
import time
import simplejson
from datetime import datetime

# These values could be placed in Django's project settings
# More info here: http://nyquistrate.com/django/facebook-connect/
API_KEY = 'YOUR KEY FROM FACEBOOK'
API_SECRET = 'YOUR SECRET FROM FACEBOOK'

REST_SERVER = 'http://api.facebook.com/restserver.php'

# You can get your User ID here: http://developers.facebook.com/tools.php?api
MY_FACEBOOK_UID = 'YOUR FACEBOOK User ID'

NOT_FRIEND_ERROR = 'You must be my Facebook friend to log in.'
PROBLEM_ERROR = 'There was a problem. Try again later.'
ACCOUNT_DISABLED_ERROR = 'Your account is not active.'
ACCOUNT_PROBLEM_ERROR = 'There is a problem with your account.'

class FacebookConnectMiddleware(object):
    
    delete_fb_cookies = False
    facebook_user_is_authenticated = False
    
    def process_request(self, request):
        try:
             # Set the facebook message to empty. This message can be used to dispaly info from the middleware on a Web page.
            request.facebook_message = None

            # Don't bother trying FB Connect login if the user is already logged in
            if not request.user.is_authenticated():
            
                # FB Connect will set a cookie with a key == FB App API Key if the user has been authenticated
                if API_KEY in request.COOKIES:

                    signature_hash = self.get_facebook_signature(request.COOKIES, True)
                
                    # The hash of the values in the cookie to make sure they're not forged
                    if(signature_hash == request.COOKIES[API_KEY]):
                
                        # If session hasn't expired
                        if(datetime.fromtimestamp(float(request.COOKIES[API_KEY+'_expires'])) > datetime.now()):

                            # Make a request to FB REST(like) API to see if current user is my friend
                            are_friends_params = {
                                'method':'Friends.areFriends',
                                'api_key': API_KEY,
                                'session_key': request.COOKIES[API_KEY + '_session_key'],
                                'call_id': time.time(),
                                'v': '1.0',
                                'uids1': MY_FACEBOOK_UID,
                                'uids2': request.COOKIES[API_KEY + '_user'],
                                'format': 'json',
                            }
                
                            are_friends_hash = self.get_facebook_signature(are_friends_params)
    
                            are_friends_params['sig'] = are_friends_hash
                
                            are_friends_params = urllib.urlencode(are_friends_params)
                
                            are_friends_response  = simplejson.load(urllib.urlopen(REST_SERVER, are_friends_params))
                        
                            # If we are friends
                            if(are_friends_response[0]['are_friends'] is True):
                    
                                try:
                                    # Try to get Django account corresponding to friend
                                    # Authenticate then login (or display disabled error message)
                                    django_user = User.objects.get(username=request.COOKIES[API_KEY + '_user'])
                                    user = authenticate(username=request.COOKIES[API_KEY + '_user'], 
                                                        password=md5.new(request.COOKIES[API_KEY + '_user'] + settings.SECRET_KEY).hexdigest())
                                    if user is not None:
                                        if user.is_active:
                                            login(request, user)
                                            self.facebook_user_is_authenticated = True
                                        else:
                                            request.facebook_message = ACCOUNT_DISABLED_ERROR
                                            self.delete_fb_cookies = True
                                    else:
                                       request.facebook_message = ACCOUNT_PROBLEM_ERROR
                                       self.delete_fb_cookies = True
                                except User.DoesNotExist:
                                    # There is no Django account for this Facebook user.
                                    # Create one, then log the user in.
                    
                                    # Make request to FB API to get user's first and last name
                                    user_info_params = {
                                        'method': 'Users.getInfo',
                                        'api_key': API_KEY,
                                        'call_id': time.time(),
                                        'v': '1.0',
                                        'uids': request.COOKIES[API_KEY + '_user'],
                                        'fields': 'first_name,last_name',
                                        'format': 'json',
                                    }

                                    user_info_hash = self.get_facebook_signature(user_info_params)

                                    user_info_params['sig'] = user_info_hash
                    
                                    user_info_params = urllib.urlencode(user_info_params)

                                    user_info_response  = simplejson.load(urllib.urlopen(REST_SERVER, user_info_params))
                    
                    
                                    # Create user
                                    user = User.objects.create_user(request.COOKIES[API_KEY + '_user'], '', 
                                                                    md5.new(request.COOKIES[API_KEY + '_user'] + 
                                                                    settings.SECRET_KEY).hexdigest())
                                    user.first_name = user_info_response[0]['first_name']
                                    user.last_name = user_info_response[0]['last_name']
                                    user.save()
                    
                                    # Authenticate and log in (or display disabled error message)
                                    user = authenticate(username=request.COOKIES[API_KEY + '_user'], 
                                                        password=md5.new(request.COOKIES[API_KEY + '_user'] + settings.SECRET_KEY).hexdigest())
                                    if user is not None:
                                        if user.is_active:
                                            login(request, user)
                                            self.facebook_user_is_authenticated = True
                                        else:
                                            request.facebook_message = ACCOUNT_DISABLED_ERROR
                                            self.delete_fb_cookies = True
                                    else:
                                       request.facebook_message = ACCOUNT_PROBLEM_ERROR
                                       self.delete_fb_cookies = True
                            # Not my FB friend
                            else:
                                request.facebook_message = NOT_FRIEND_ERROR
                                self.delete_fb_cookies = True
                            
                        # Cookie session expired
                        else:
                            logout(request)
                            self.delete_fb_cookies = True
                        
                   # Cookie values don't match hash
                    else:
                        logout(request)
                        self.delete_fb_cookies = True
                    
            # Logged in
            else:
                # If FB Connect user
                if API_KEY in request.COOKIES:
                    # IP hash cookie set
                    if 'fb_ip' in request.COOKIES:
                        
                        try:
                            real_ip = request.META['HTTP_X_FORWARDED_FOR']
                        except KeyError:
                            real_ip = request.META['REMOTE_ADDR']
                        
                        # If IP hash cookie is NOT correct
                        if request.COOKIES['fb_ip'] != md5.new(real_ip + API_SECRET + settings.SECRET_KEY).hexdigest():
                             logout(request)
                             self.delete_fb_cookies = True
                    # FB Connect user without hash cookie set
                    else:
                        logout(request)
                        self.delete_fb_cookies = True
                        
        # Something else happened. Make sure user doesn't have site access until problem is fixed.
        except:
            request.facebook_message = PROBLEM_ERROR
            logout(request)
            self.delete_fb_cookies = True
        
    def process_response(self, request, response):        
        
        # Delete FB Connect cookies
        # FB Connect JavaScript may add them back, but this will ensure they're deleted if they should be
        if self.delete_fb_cookies is True:
            response.delete_cookie(API_KEY + '_user')
            response.delete_cookie(API_KEY + '_session_key')
            response.delete_cookie(API_KEY + '_expires')
            response.delete_cookie(API_KEY + '_ss')
            response.delete_cookie(API_KEY)
            response.delete_cookie('fbsetting_' + API_KEY)
    
        self.delete_fb_cookies = False
        
        if self.facebook_user_is_authenticated is True:
            try:
                real_ip = request.META['HTTP_X_FORWARDED_FOR']
            except KeyError:
                real_ip = request.META['REMOTE_ADDR']
            response.set_cookie('fb_ip', md5.new(real_ip + API_SECRET + settings.SECRET_KEY).hexdigest())
        
        # process_response() must always return a HttpResponse
        return response
                                
    # Generates signatures for FB requests/cookies
    def get_facebook_signature(self, values_dict, is_cookie_check=False):
        signature_keys = []
        for key in sorted(values_dict.keys()):
            if (is_cookie_check and key.startswith(API_KEY + '_')):
                signature_keys.append(key)
            elif (is_cookie_check is False):
                signature_keys.append(key)

        if (is_cookie_check):
            signature_string = ''.join(['%s=%s' % (x.replace(API_KEY + '_',''), values_dict[x]) for x in signature_keys])
        else:
            signature_string = ''.join(['%s=%s' % (x, values_dict[x]) for x in signature_keys])
        signature_string = signature_string + API_SECRET

        return md5.new(signature_string).hexdigest()