<commit_before>r"""
    facebook_utils
    ~~~~~~~~~~~~

    A collection of utilities for integrating user accoutns with Facebook.com
    
    right now this handles oauth , but will likely expand
    
    Purpose
    =======
    
    1. Facebook dropped development and support of their python sdk
    
    2. There are a handful of pyramid utilities that provide a complete drop-in 
    integration with Facebook.com; This is NOT one of them. Sometimes you want 
    to control the User Experience and have all your pages custom; if so, this
    is for you.
        
    
    Usage
    =====
    
    This was originally built/intended for use under the Pyramid environment
    
    calling FacebookPyramid() will create a new object that 
    subclasses FacebookHub() objects, using  default settings 
    from your .ini and pulling variables from 'request' as needed.
    
    facebook_utils.FacebookHub() can be used directly - however it will not 
    pull the appropriate settings from the .ini or request.
    
    
    Supports Two oAuth Flows
    =========================
    
    Flow 1 - Server Side
    --------------------
    1. configure an object with `oauth_code_redirect_uri`
    2. consumers click a button on your site, which redirects to 
    `oauth_code_redirect_uri` -- as provided by `oauth_code__url_dialog()`
    3. upon success, users are redirected from facebook to 
    `oauth_code_redirect_uri` along with a query param titled `code`
    4. you may then call `.oauth_code__get_access_token()` to get an access 
    token or call `oauth_code__get_access_token_and_profile()` to get the token 
    and profile data.
    5. profile data can be updated with `.graph__get_profile(access_token)`
    
    
    Flow 2 - Client Side
    --------------------
    1. configure an object with `oauth_token_redirect_uri`
    2. consumers click a button on your site, which redirects to 
    `oauth_token_redirect_uri` -- as provided by `oauth_token__url_dialog()`
    3. upon success, users are redirected from facebook to 
    `oauth_token__url_dialog` along with a query param titled `token` and a 
    hash value titled `#access_token`.  The `access_token` is not visible to
    the server, and must be transferred to your server via JavaScript or 
    not-at-all should you simply want to do all your integration in JavaScript.
    4. profile data can be obtained with `.graph__get_profile(access_token)`
    if you store the access token
    
    
    Notes
    =====
    Most methods will let you override the 'scope' and 'request_uri'.  This 
    shouldn't really be necessary and will probable be deprecated.
    
    
    
    
    
    Pyramid Examples
    ================
    define some variables in your .ini files:
    
    file: development.ini

        facebook.app.id= 123
        facebook.app.secret= 123
        facebook.app.scope= email,user_birthday,user_checkins,offline_access
        facebook.app.oauth_code_redirect_uri = http://127.0.0.1:5010/facebook-oauth-redirect
        
    
    integrate into your handlers:
        
        from facebook_utils import FacebookPyramid
    
        class WebAccount(base.Handler):
            def __new_fb_object(self):
                "Create a new Facebook Object"
                # note that we can override settings in the .ini files
                oauth_code_redirect_uri= "http://%(app_domain)s/account/facebook-authenticate-oauth?response_type=code" % { 'app_domain' : self.request.registry.settings['app_domain']}
                oauth_token_redirect_uri= "http://%(app_domain)s/account/facebook-authenticate-oauth-token?response_type=token" % { 'app_domain' : self.request.registry.settings['app_domain']}
                fb= FacebookPyramid( self.request , oauth_code_redirect_uri=oauth_code_redirect_uri )
                return fb
    
            def sign_up(self):
                "sign up page, which contains a "signup with facebook link"
                fb= self.__new_fb_object()
                return {"project":"MyApp" , 'facebook_pyramid':facebook }
    
            @action(renderer="web/account/facebook_authenticate_oauth.html")
            def facebook_authenticate_oauth(self):
                fb= self.__new_fb_object()
                ( access_token , profile )= fb.oauth_code__get_access_token_and_profile( self.request )
                if profile :
                    # congrats , they logged in
                    # register the user to your db 
                    raise HTTPFound(location='/account/home')
                else:
                    # boo, that didn't work
                    raise HTTPFound(location='/account/sign-up?error=facebook-oauth-failure')
                return {"project":"MyApp"}


    integrate into your template:
                <a class="fancy_button-1" id="signup-start_btn-facebook" href="${facebook_pyramid.oauth_code__url_dialog()}">
                    Connect with <strong>Facebook</strong>
                </a>
    

    :copyright: 2012 by <NAME>
    license: BSD
"""

import base64
import cgi
import datetime
import hashlib
import hmac
from time import time
import urllib
import urllib2
import urlparse

import simplejson as json


class ApiError( Exception ):
    """
    Raised if there is an error with authentication
    """
    code = None
    type = None
    message = None

    def __init__( self , code=None, type=None, message=None ):
        self.code = code
        self.type = type
        self.message = message
        
    def __str__( self ):
        return "ApiError: %s | %s | %s" % ( self.code , self.type , self.message )

class ApiAuthError(ApiError):
    """
    Raised if there is an error with authentication
    """


def reformat_error(json_string):
    rval = { 'message':None , 'type': None , 'code': None }
    for k in rval.keys():
        if k in json_string:
            rval[k] = json_string[k]
    return rval


def facebook_time(fb_time):
    """parses facebook's timestamp into a datetime object"""
    return datetime.datetime.strptime( fb_time, '%Y-%m-%dT%H:%M:%S+0000')



class FacebookHub(object):


    app_id= None
    app_secret= None
    app_scope= None
    app_domain= None
    oauth_code_redirect_uri= None
    oauth_token_redirect_uri= None


    def __init__( self , app_id=None , app_secret=None , app_scope=None , app_domain=None , oauth_code_redirect_uri=None , oauth_token_redirect_uri=None ):
        """Initialize the FacebookHub object with some variables.  app_id and app_secret are required."""
        if app_id is None or app_secret is None:
            raise ValueError("Must initialize FacebookHub() with an app_id and an app_secret")
        self.app_id= app_id
        self.app_secret= app_secret
        self.app_scope= app_scope
        self.app_domain= app_domain
        self.oauth_code_redirect_uri = oauth_code_redirect_uri
        self.oauth_token_redirect_uri= oauth_token_redirect_uri


    def oauth_code__url_dialog( self, redirect_uri=None , scope=None ):
        """Generates the URL for an oAuth dialog to facebook for a "code" flow.  This flow will return the user to your website with a 'code' object in a query param. """
        if scope == None:
            scope= self.app_scope
        if redirect_uri == None:
            redirect_uri= self.oauth_code_redirect_uri
        return """https://www.facebook.com/dialog/oauth?client_id=%(app_id)s&scope=%(scope)s&redirect_uri=%(redirect_uri)s""" % { 'app_id':self.app_id, "redirect_uri":urllib.quote( redirect_uri ) , 'scope':scope }
        

    def oauth_code__url_access_token( self, submitted_code=None , redirect_uri=None , scope=None ):
        """Generates the URL to grab an access token from Facebook.  This is returned based on EXACTLY matching the app_id, app_secret, and 'code' with the redirect_uri. If you change the redirect uri - or any other component - it will break.
        https://graph.facebook.com/oauth/access_token?client_id=YOUR_APP_ID&redirect_uri=YOUR_URL&client_secret=YOUR_APP_SECRET&code=THE_CODE_FROM_URL_DIALOG_TOKEN 
        
        """
        if submitted_code is None:
            raise ValueError('must call with submitted_code')
        if redirect_uri == None:
            redirect_uri= self.oauth_code_redirect_uri
        if scope == None:
            scope= self.app_scope
        return """https://graph.facebook.com/oauth/access_token?client_id=%(app_id)s&redirect_uri=%(redirect_uri)s&client_secret=%(client_secret)s&code=%(code)s""" % { 'app_id':self.app_id , "redirect_uri":urllib.quote( redirect_uri ) , 'client_secret':self.app_secret, 'code':submitted_code }
        
    def api_proxy( self , url , post_data=None , expected_format='json.load' , is_delete=False ):
        try:
            if post_data :
                post_data_encoded = urllib.urlencode(post_data)
                if is_delete:
                    opener = urllib2.build_opener(urllib2.HTTPHandler)
                    request = urllib2.Request(url,post_data)
                    request.get_method = lambda: 'DELETE'
                    response = opener.open(request)
                else:
                    response = urllib2.urlopen( url , post_data_encoded )
            else:
                response = urllib2.urlopen(url)
            if expected_format == 'json.load' :
                return json.load(response)
            elif expected_format == 'cgi.parse_qs' :
                response = cgi.parse_qs(response.read())
            elif expected_format == 'urlparse.parse_qs' :
                response = urlparse.parse_qs(response.read())
            else:
                raise ValueError("Unexpected Format: %s" % expected_format)
            return response
        except urllib2.HTTPError , e :
            if e.code == 400:
                rval = ''
                try:
                    rval = json.loads(e.read())
                    if 'error' in rval : 
                        error = reformat_error( rval['error'] )
                        if error['type'] == 'OAuthException' :
                            raise ApiAuthError(**error)
                        raise ApiError(**error)
                    raise ApiError(message = 'I don\'t know how to handle this error (%s)' % rval , code=400 )
                except json.JSONDecodeError :
                    raise ApiError( message = 'Could not parse JSON from the error (%s)' % rval , code=400 )
                except: 
                    raise 
            raise ApiError( message = 'Could not communicate with the API' , code=e.code )
        except:
            raise
        
            

    def oauth_code__get_access_token( self , submitted_code=None , redirect_uri=None , scope=None ):
        """Gets the access token from Facebook that corresponds with a code.  This uses urllib2 to open the url , so should be considered as blocking code."""
        if submitted_code is None:
            raise ValueError('must call with submitted_code')
        if scope == None:
            scope= self.app_scope
        if redirect_uri == None:
            redirect_uri= self.oauth_code_redirect_uri
        url_access_token = self.oauth_code__url_access_token( submitted_code , redirect_uri=redirect_uri , scope=scope )
        access_token = None
        try:
            response = self.api_proxy( url_access_token , expected_format='cgi.parse_qs' )
            if 'access_token' not in response:
                raise ValueError('invalid response')
            access_token = response["access_token"][-1]
        except:
            raise
        return access_token
        
        
    def oauth_code__get_access_token_and_profile( self , submitted_code=None , redirect_uri=None , scope=None  ):
        """Gets the access token AND a profile from Facebook that corresponds with a code.  This method wraps a call to `oauth_code__get_access_token`, then wraps `graph__get_profile_for_access_token` which opens a json object at the url returned by `graph__url_me_for_access_token`.  This is a convenince method, since most people want to do that ( at least on the initial Facebook auth.  This wraps methods which use urllib2 to open urls, so should be considered as blocking code."""
        if submitted_code is None:
            raise ValueError('must submit a code')
        ( access_token , profile ) = ( None , None )
        try:
            access_token = self.oauth_code__get_access_token( submitted_code , redirect_uri=redirect_uri , scope=scope )
            profile = self.graph__get_profile_for_access_token(access_token=access_token)
        except:
            raise
        return ( access_token , profile )        


    def oauth_token__url_dialog( self, redirect_uri=None , scope=None ):
        """Generates the URL for an oAuth dialog to facebook.  This flow will return the user to your website with a 'token' object as a URI hashstring.  This hashstring can not be seen by the server, it must be handled via javascript """
        if scope == None:
            scope= self.app_scope
        if redirect_uri == None:
            redirect_uri= self.oauth_token_redirect_uri
        return """https://www.facebook.com/dialog/oauth?client_id=%(app_id)s&scope=%(scope)s&redirect_uri=%(redirect_uri)s&response_type=token""" % { 'app_id':self.app_id , "redirect_uri":urllib.quote( redirect_uri ) , 'scope':scope }
        

    def oauth__url_extend_access_token( self, access_token=None ):
        """Generates the URL to extend an access token from Facebook.  
        
        see https://developers.facebook.com/roadmap/offline-access-removal/

        https://graph.facebook.com/oauth/access_token?             
            client_id=APP_ID&
            client_secret=APP_SECRET&
            grant_type=fb_exchange_token&
            fb_exchange_token=EXISTING_ACCESS_TOKEN 
            
        oddly, this returns a url formatted string and not a json document.  go figure.
    
        """
        if access_token is None:
            raise ValueError('must call with access_token')
        return """https://graph.facebook.com/oauth/access_token?client_id=%(app_id)s&client_secret=%(client_secret)s&grant_type=fb_exchange_token&fb_exchange_token=%(access_token)s""" % { 'app_id':self.app_id , 'client_secret':self.app_secret, 'access_token':access_token }


    def graph__extend_access_token( self , access_token=None ):
        """ see oauth__url_extend_access_token  """
        if access_token is None or not access_token :
            raise ValueError('must submit access_token')
        result= None
        try:
            url = self.oauth__url_extend_access_token(access_token=access_token)
            response = self.api_proxy( url , expected_format='urlparse.parse_qs' )
        except:
            raise
        return result


    def graph__url_me( self , access_token ):
        raise ValueError('Deprecated; call graph__url_me_for_access_token instead')
        
    
    def graph__url_me_for_access_token( self , access_token=None ):
        if access_token is None:
            raise ValueError('must submit access_token')
        return "https://graph.facebook.com/me?" + urllib.urlencode(dict(access_token=access_token))


    def graph__url_user_for_access_token( self , access_token=None , user=None , action=None ):
        if access_token is None:
            raise ValueError('must submit access_token')
        if user is None:
            raise ValueError('must submit user')
        if action :
            return "https://graph.facebook.com/%s/%s?%s" % ( user , action , urllib.urlencode(dict(access_token=access_token)) )
        return "https://graph.facebook.com/%s?%s" % ( user , urllib.urlencode(dict(access_token=access_token)) )


    def graph__get_profile_for_access_token( self , access_token=None  , user=None , action=None ):
        """Grabs a profile for a user, corresponding to a profile , from Facebook.  This uses urllib2 to open the url , so should be considered as blocking code."""
        if access_token is None:
            raise ValueError('must submit access_token')
        profile= None
        try:
            url = None
            if not user :
                if action :
                    url = self.graph__url_user_for_access_token(access_token,action=action )
                else:
                    url = self.graph__url_me_for_access_token(access_token)
            else :
                url = self.graph__url_user_for_access_token(access_token,user=user,action=action ) 
            profile = self.api_proxy( url , expected_format='json.load' )
        except:
            raise
        return profile
    

    def graph__get_profile( self , access_token=None  ):
        raise ValueError('Deprecated; call graph__get_profile_for_access_token instead')


    def graph__action_create( self , access_token=None , fb_app_namespace=None , fb_action_type_name=None , object_type_name=None , object_instance_url=None):
        if not all(( access_token , fb_app_namespace , fb_action_type_name )):
            raise ValueError('must submit access_token , fb_app_namespace , fb_action_type_name' )
        if not all(( object_type_name , object_instance_url )):
            raise ValueError('must submit object_type_name , object_instance_url ' )
        url = "https://graph.facebook.com/me/%s:%s" % ( fb_app_namespace , fb_action_type_name )
        post_data = {
            'access_token' : access_token ,
            object_type_name : object_instance_url ,
        }
        try:
            payload = self.api_proxy( url , post_data , expected_format='json.load' )
            return payload
        except:
            raise


    def graph__action_list( self , access_token=None , fb_app_namespace=None , fb_action_type_name=None ):
        if not all(( access_token , fb_app_namespace , fb_action_type_name )):
            raise ValueError('must submit access_token , fb_app_namespace , fb_action_type_name' )
        url = "https://graph.facebook.com/me/%s:%s?access_token=%s" % ( fb_app_namespace , fb_action_type_name , access_token )
        try:
            payload = self.api_proxy( url , expected_format='json.load' )
            return payload
        except:
            raise

        
    def graph__action_delete( self , access_token=None , action_id=None ):
        if not all(( access_token , action_id )):
            raise ValueError('must submit action_id' )
        url = "https://graph.facebook.com/%s" % ( action_id )
        post_data = {
            'access_token' : access_token ,
        }
        post_data = urllib.urlencode(post_data)
        try:
            payload = self.api_proxy( url , post_data=post_data , expected_format='json.load' , is_delete=True )
            return payload
        except:
            raise

    def verify_signed_request( self , signed_request=None , timeout=None ):
        """ verifies the signedRequest from Facebook.  accepts a timeout value, to test against the 'issued_at' key within the payload
        
        this will always return a Tuple of ( BOOL , DICT )
        
        Bool :
            True = Signed Request is verified
            False = Signed Request is not verified

        Dict :
            if request is verified: the payload object as JSON
            if request is not verified: a 'python-error' key with the reason
            
        PLEASE NOTE:
            1. if the request is verfied, but the data is outside of the timeout, this will return FALSE as a bool; and the dict will the verified payload with an 'python-error' key.
            2. i chose 'python-error', because facebook is likely to change their spec. they do that. the chances of them adding 'error' are much greater than 'python-error'
        
        Reference documentation
        https://developers.facebook.com/docs/authentication/signed_request/
        "The signed_request parameter is the concatenation of a HMAC SHA-256 signature string, a period (.), and a base64url encoded JSON object."
        
        after starting this, i found someone already did the hard work.
        following is based on Sunil Arora's blog post - http://sunilarora.org/parsing-signedrequest-parameter-in-python-bas
        """
        if signed_request is None:
            raise ValueError('must submit signed_request')

        def base64_url_decode(inp):
            padding_factor = (4 - len(inp) % 4) % 4
            inp += "="*padding_factor 
            return base64.b64decode(unicode(inp).translate(dict(zip(map(ord, u'-_'), u'+/'))))
        
        (signature,payload)= signed_request.split('.')

        decoded_signature = base64_url_decode(signature)
        data = json.loads(base64_url_decode(payload))

        if data.get('algorithm').upper() != 'HMAC-SHA256':
            return ( False , { 'python-error':  'Unknown algorithm - %s' % data.get('algorithm').upper() } )

        expected_sig = hmac.new( self.app_secret , msg=payload , digestmod=hashlib.sha256 ).digest()
    
        if decoded_signature != expected_sig:
            return ( None , {'python-error': 'signature (%s) != expected_sig (%s)' % { decoded_signature , expected_sig }  } )
            
        if timeout:
            time_now= int(time())
            diff = time_now - data['issued_at']
            if ( diff > timeout ) :
                data['python-error']= "payload issued outside of timeout window"
                return ( False , data )

        return ( True , data )
            
            

        





class FacebookPyramid(FacebookHub):

    def __init__( self, request , app_id=None , app_secret=None , app_scope=None , app_domain=None , oauth_code_redirect_uri=None , oauth_token_redirect_uri=None ):
        """Creates a new FacebookHub object, sets it up with Pyramid Config vars, and then proxies other functions into it"""
        self.request= request
        if app_id is None and 'facebook.app.id' in request.registry.settings :
            app_id = request.registry.settings['facebook.app.id']
        if app_secret is None and 'facebook.app.secret' in request.registry.settings :
            app_secret = request.registry.settings['facebook.app.secret']
        if app_scope is None and 'facebook.app.scope' in request.registry.settings :
            app_scope = request.registry.settings['facebook.app.scope']
        if app_domain is None:
            app_domain = request.registry.settings['app_domain']
        if oauth_code_redirect_uri is None and 'facebook.app.oauth_code_redirect_uri' in request.registry.settings :
            oauth_code_redirect_uri = request.registry.settings['facebook.app.oauth_code_redirect_uri']
        if oauth_token_redirect_uri is None and 'facebook.app.oauth_token_redirect_uri' in request.registry.settings :
            oauth_token_redirect_uri = request.registry.settings['facebook.app.oauth_token_redirect_uri']
        FacebookHub.__init__( self , app_id=app_id , app_secret=app_secret , app_scope=app_scope , app_domain=app_domain , oauth_code_redirect_uri=oauth_code_redirect_uri , oauth_token_redirect_uri=oauth_token_redirect_uri )
        
    def oauth_code__url_access_token( self, submitted_code=None , redirect_uri=None , scope=None ):
        if submitted_code is None:
            submitted_code = self.request.params.get('code')
        return FacebookHub.oauth_code__url_access_token( self , submitted_code=submitted_code , redirect_uri=redirect_uri , scope=scope )

    def oauth_code__get_access_token( self , submitted_code=None , redirect_uri=None , scope=None ):
        if submitted_code is None:
            submitted_code = self.request.params.get('code')
        return FacebookHub.oauth_code__get_access_token( self , submitted_code=submitted_code , redirect_uri=redirect_uri , scope=scope )

    def oauth_code__get_access_token_and_profile( self , submitted_code=None , redirect_uri=None , scope=None ):
        if submitted_code is None:
            submitted_code = self.request.params.get('code')
        return FacebookHub.oauth_code__get_access_token_and_profile( self , submitted_code=submitted_code , redirect_uri=redirect_uri , scope=scope )
<commit_msg>fixed some error codes<commit_after>r"""
    facebook_utils
    ~~~~~~~~~~~~

    A collection of utilities for integrating user accoutns with Facebook.com
    
    right now this handles oauth , but will likely expand
    
    Purpose
    =======
    
    1. Facebook dropped development and support of their python sdk
    
    2. There are a handful of pyramid utilities that provide a complete drop-in 
    integration with Facebook.com; This is NOT one of them. Sometimes you want 
    to control the User Experience and have all your pages custom; if so, this
    is for you.
        
    
    Usage
    =====
    
    This was originally built/intended for use under the Pyramid environment
    
    calling FacebookPyramid() will create a new object that 
    subclasses FacebookHub() objects, using  default settings 
    from your .ini and pulling variables from 'request' as needed.
    
    facebook_utils.FacebookHub() can be used directly - however it will not 
    pull the appropriate settings from the .ini or request.
    
    
    Supports Two oAuth Flows
    =========================
    
    Flow 1 - Server Side
    --------------------
    1. configure an object with `oauth_code_redirect_uri`
    2. consumers click a button on your site, which redirects to 
    `oauth_code_redirect_uri` -- as provided by `oauth_code__url_dialog()`
    3. upon success, users are redirected from facebook to 
    `oauth_code_redirect_uri` along with a query param titled `code`
    4. you may then call `.oauth_code__get_access_token()` to get an access 
    token or call `oauth_code__get_access_token_and_profile()` to get the token 
    and profile data.
    5. profile data can be updated with `.graph__get_profile(access_token)`
    
    
    Flow 2 - Client Side
    --------------------
    1. configure an object with `oauth_token_redirect_uri`
    2. consumers click a button on your site, which redirects to 
    `oauth_token_redirect_uri` -- as provided by `oauth_token__url_dialog()`
    3. upon success, users are redirected from facebook to 
    `oauth_token__url_dialog` along with a query param titled `token` and a 
    hash value titled `#access_token`.  The `access_token` is not visible to
    the server, and must be transferred to your server via JavaScript or 
    not-at-all should you simply want to do all your integration in JavaScript.
    4. profile data can be obtained with `.graph__get_profile(access_token)`
    if you store the access token
    
    
    Notes
    =====
    Most methods will let you override the 'scope' and 'request_uri'.  This 
    shouldn't really be necessary and will probable be deprecated.
    
    
    
    
    
    Pyramid Examples
    ================
    define some variables in your .ini files:
    
    file: development.ini

        facebook.app.id= 123
        facebook.app.secret= 123
        facebook.app.scope= email,user_birthday,user_checkins,offline_access
        facebook.app.oauth_code_redirect_uri = http://127.0.0.1:5010/facebook-oauth-redirect
        
    
    integrate into your handlers:
        
        from facebook_utils import FacebookPyramid
    
        class WebAccount(base.Handler):
            def __new_fb_object(self):
                "Create a new Facebook Object"
                # note that we can override settings in the .ini files
                oauth_code_redirect_uri= "http://%(app_domain)s/account/facebook-authenticate-oauth?response_type=code" % { 'app_domain' : self.request.registry.settings['app_domain']}
                oauth_token_redirect_uri= "http://%(app_domain)s/account/facebook-authenticate-oauth-token?response_type=token" % { 'app_domain' : self.request.registry.settings['app_domain']}
                fb= FacebookPyramid( self.request , oauth_code_redirect_uri=oauth_code_redirect_uri )
                return fb
    
            def sign_up(self):
                "sign up page, which contains a "signup with facebook link"
                fb= self.__new_fb_object()
                return {"project":"MyApp" , 'facebook_pyramid':facebook }
    
            @action(renderer="web/account/facebook_authenticate_oauth.html")
            def facebook_authenticate_oauth(self):
                fb= self.__new_fb_object()
                ( access_token , profile )= fb.oauth_code__get_access_token_and_profile( self.request )
                if profile :
                    # congrats , they logged in
                    # register the user to your db 
                    raise HTTPFound(location='/account/home')
                else:
                    # boo, that didn't work
                    raise HTTPFound(location='/account/sign-up?error=facebook-oauth-failure')
                return {"project":"MyApp"}


    integrate into your template:
                <a class="fancy_button-1" id="signup-start_btn-facebook" href="${facebook_pyramid.oauth_code__url_dialog()}">
                    Connect with <strong>Facebook</strong>
                </a>
    

    :copyright: 2012 by <NAME>
    license: BSD
"""

import base64
import cgi
import datetime
import hashlib
import hmac
from time import time
import urllib
import urllib2
import urlparse

import simplejson as json


class ApiError( Exception ):
    """
    Raised if there is an error with authentication
    """
    code = None
    type = None
    message = None

    def __init__( self , code=None, type=None, message=None ):
        self.code = code
        self.type = type
        self.message = message
        
    def __str__( self ):
        return "ApiError: %s | %s | %s" % ( self.code , self.type , self.message )

class ApiAuthError(ApiError):
    """
    Raised if there is an error with authentication
    """
    pass


def reformat_error(json_string):
    rval = { 'message':None , 'type': None , 'code': None }
    for k in rval.keys():
        if k in json_string:
            rval[k] = json_string[k]
    return rval


def facebook_time(fb_time):
    """parses facebook's timestamp into a datetime object"""
    return datetime.datetime.strptime( fb_time, '%Y-%m-%dT%H:%M:%S+0000')



class FacebookHub(object):


    app_id= None
    app_secret= None
    app_scope= None
    app_domain= None
    oauth_code_redirect_uri= None
    oauth_token_redirect_uri= None


    def __init__( self , app_id=None , app_secret=None , app_scope=None , app_domain=None , oauth_code_redirect_uri=None , oauth_token_redirect_uri=None ):
        """Initialize the FacebookHub object with some variables.  app_id and app_secret are required."""
        if app_id is None or app_secret is None:
            raise ValueError("Must initialize FacebookHub() with an app_id and an app_secret")
        self.app_id= app_id
        self.app_secret= app_secret
        self.app_scope= app_scope
        self.app_domain= app_domain
        self.oauth_code_redirect_uri = oauth_code_redirect_uri
        self.oauth_token_redirect_uri= oauth_token_redirect_uri


    def oauth_code__url_dialog( self, redirect_uri=None , scope=None ):
        """Generates the URL for an oAuth dialog to facebook for a "code" flow.  This flow will return the user to your website with a 'code' object in a query param. """
        if scope == None:
            scope= self.app_scope
        if redirect_uri == None:
            redirect_uri= self.oauth_code_redirect_uri
        return """https://www.facebook.com/dialog/oauth?client_id=%(app_id)s&scope=%(scope)s&redirect_uri=%(redirect_uri)s""" % { 'app_id':self.app_id, "redirect_uri":urllib.quote( redirect_uri ) , 'scope':scope }
        

    def oauth_code__url_access_token( self, submitted_code=None , redirect_uri=None , scope=None ):
        """Generates the URL to grab an access token from Facebook.  This is returned based on EXACTLY matching the app_id, app_secret, and 'code' with the redirect_uri. If you change the redirect uri - or any other component - it will break.
        https://graph.facebook.com/oauth/access_token?client_id=YOUR_APP_ID&redirect_uri=YOUR_URL&client_secret=YOUR_APP_SECRET&code=THE_CODE_FROM_URL_DIALOG_TOKEN 
        
        """
        if submitted_code is None:
            raise ValueError('must call with submitted_code')
        if redirect_uri == None:
            redirect_uri= self.oauth_code_redirect_uri
        if scope == None:
            scope= self.app_scope
        return """https://graph.facebook.com/oauth/access_token?client_id=%(app_id)s&redirect_uri=%(redirect_uri)s&client_secret=%(client_secret)s&code=%(code)s""" % { 'app_id':self.app_id , "redirect_uri":urllib.quote( redirect_uri ) , 'client_secret':self.app_secret, 'code':submitted_code }
        
    def api_proxy( self , url , post_data=None , expected_format='json.load' , is_delete=False ):
        try:
            if post_data :
                post_data_encoded = urllib.urlencode(post_data)
                if is_delete:
                    opener = urllib2.build_opener(urllib2.HTTPHandler)
                    request = urllib2.Request(url,post_data)
                    request.get_method = lambda: 'DELETE'
                    response = opener.open(request)
                else:
                    response = urllib2.urlopen( url , post_data_encoded )
            else:
                response = urllib2.urlopen(url)
            if expected_format == 'json.load' :
                return json.load(response)
            elif expected_format == 'cgi.parse_qs' :
                response = cgi.parse_qs(response.read())
            elif expected_format == 'urlparse.parse_qs' :
                response = urlparse.parse_qs(response.read())
            else:
                raise ValueError("Unexpected Format: %s" % expected_format)
            return response
        except urllib2.HTTPError , e :
            if e.code == 400:
                rval = ''
                try:
                    rval = json.loads(e.read())
                    if 'error' in rval : 
                        error = reformat_error( rval['error'] )
                        if error['type'] == 'OAuthException' :
                            raise ApiAuthError(**error)
                        raise ApiError(**error)
                    raise ApiError(message = 'I don\'t know how to handle this error (%s)' % rval , code=400 )
                except json.JSONDecodeError :
                    raise ApiError( message = 'Could not parse JSON from the error (%s)' % rval , code=400 )
                except: 
                    raise 
            raise ApiError( message = 'Could not communicate with the API' , code=e.code )
        except:
            raise
        
            

    def oauth_code__get_access_token( self , submitted_code=None , redirect_uri=None , scope=None ):
        """Gets the access token from Facebook that corresponds with a code.  This uses urllib2 to open the url , so should be considered as blocking code."""
        if submitted_code is None:
            raise ValueError('must call with submitted_code')
        if scope == None:
            scope= self.app_scope
        if redirect_uri == None:
            redirect_uri= self.oauth_code_redirect_uri
        url_access_token = self.oauth_code__url_access_token( submitted_code , redirect_uri=redirect_uri , scope=scope )
        access_token = None
        try:
            response = self.api_proxy( url_access_token , expected_format='cgi.parse_qs' )
            if 'access_token' not in response:
                raise ApiError(message='invalid response')
            access_token = response["access_token"][-1]
        except:
            raise
        return access_token
        
        
    def oauth_code__get_access_token_and_profile( self , submitted_code=None , redirect_uri=None , scope=None  ):
        """Gets the access token AND a profile from Facebook that corresponds with a code.  This method wraps a call to `oauth_code__get_access_token`, then wraps `graph__get_profile_for_access_token` which opens a json object at the url returned by `graph__url_me_for_access_token`.  This is a convenince method, since most people want to do that ( at least on the initial Facebook auth.  This wraps methods which use urllib2 to open urls, so should be considered as blocking code."""
        if submitted_code is None:
            raise ValueError('must submit a code')
        ( access_token , profile ) = ( None , None )
        try:
            access_token = self.oauth_code__get_access_token( submitted_code , redirect_uri=redirect_uri , scope=scope )
            profile = self.graph__get_profile_for_access_token(access_token=access_token)
        except:
            raise
        return ( access_token , profile )        


    def oauth_token__url_dialog( self, redirect_uri=None , scope=None ):
        """Generates the URL for an oAuth dialog to facebook.  This flow will return the user to your website with a 'token' object as a URI hashstring.  This hashstring can not be seen by the server, it must be handled via javascript """
        if scope == None:
            scope= self.app_scope
        if redirect_uri == None:
            redirect_uri= self.oauth_token_redirect_uri
        return """https://www.facebook.com/dialog/oauth?client_id=%(app_id)s&scope=%(scope)s&redirect_uri=%(redirect_uri)s&response_type=token""" % { 'app_id':self.app_id , "redirect_uri":urllib.quote( redirect_uri ) , 'scope':scope }
        

    def oauth__url_extend_access_token( self, access_token=None ):
        """Generates the URL to extend an access token from Facebook.  
        
        see https://developers.facebook.com/roadmap/offline-access-removal/

        https://graph.facebook.com/oauth/access_token?             
            client_id=APP_ID&
            client_secret=APP_SECRET&
            grant_type=fb_exchange_token&
            fb_exchange_token=EXISTING_ACCESS_TOKEN 
            
        oddly, this returns a url formatted string and not a json document.  go figure.
    
        """
        if access_token is None:
            raise ValueError('must call with access_token')
        return """https://graph.facebook.com/oauth/access_token?client_id=%(app_id)s&client_secret=%(client_secret)s&grant_type=fb_exchange_token&fb_exchange_token=%(access_token)s""" % { 'app_id':self.app_id , 'client_secret':self.app_secret, 'access_token':access_token }


    def graph__extend_access_token( self , access_token=None ):
        """ see oauth__url_extend_access_token  """
        if access_token is None or not access_token :
            raise ValueError('must submit access_token')
        result= None
        try:
            url = self.oauth__url_extend_access_token(access_token=access_token)
            response = self.api_proxy( url , expected_format='urlparse.parse_qs' )
        except:
            raise
        return result


    def graph__url_me( self , access_token ):
        raise ValueError('Deprecated; call graph__url_me_for_access_token instead')
        
    
    def graph__url_me_for_access_token( self , access_token=None ):
        if access_token is None:
            raise ValueError('must submit access_token')
        return "https://graph.facebook.com/me?" + urllib.urlencode(dict(access_token=access_token))


    def graph__url_user_for_access_token( self , access_token=None , user=None , action=None ):
        if access_token is None:
            raise ValueError('must submit access_token')
        if user is None:
            raise ValueError('must submit user')
        if action :
            return "https://graph.facebook.com/%s/%s?%s" % ( user , action , urllib.urlencode(dict(access_token=access_token)) )
        return "https://graph.facebook.com/%s?%s" % ( user , urllib.urlencode(dict(access_token=access_token)) )


    def graph__get_profile_for_access_token( self , access_token=None  , user=None , action=None ):
        """Grabs a profile for a user, corresponding to a profile , from Facebook.  This uses urllib2 to open the url , so should be considered as blocking code."""
        if access_token is None:
            raise ValueError('must submit access_token')
        profile= None
        try:
            url = None
            if not user :
                if action :
                    url = self.graph__url_user_for_access_token(access_token,action=action )
                else:
                    url = self.graph__url_me_for_access_token(access_token)
            else :
                url = self.graph__url_user_for_access_token(access_token,user=user,action=action ) 
            profile = self.api_proxy( url , expected_format='json.load' )
        except:
            raise
        return profile
    

    def graph__get_profile( self , access_token=None  ):
        raise ValueError('Deprecated; call graph__get_profile_for_access_token instead')


    def graph__action_create( self , access_token=None , fb_app_namespace=None , fb_action_type_name=None , object_type_name=None , object_instance_url=None):
        if not all(( access_token , fb_app_namespace , fb_action_type_name )):
            raise ValueError('must submit access_token , fb_app_namespace , fb_action_type_name' )
        if not all(( object_type_name , object_instance_url )):
            raise ValueError('must submit object_type_name , object_instance_url ' )
        url = "https://graph.facebook.com/me/%s:%s" % ( fb_app_namespace , fb_action_type_name )
        post_data = {
            'access_token' : access_token ,
            object_type_name : object_instance_url ,
        }
        try:
            payload = self.api_proxy( url , post_data , expected_format='json.load' )
            return payload
        except:
            raise


    def graph__action_list( self , access_token=None , fb_app_namespace=None , fb_action_type_name=None ):
        if not all(( access_token , fb_app_namespace , fb_action_type_name )):
            raise ValueError('must submit access_token , fb_app_namespace , fb_action_type_name' )
        url = "https://graph.facebook.com/me/%s:%s?access_token=%s" % ( fb_app_namespace , fb_action_type_name , access_token )
        try:
            payload = self.api_proxy( url , expected_format='json.load' )
            return payload
        except:
            raise

        
    def graph__action_delete( self , access_token=None , action_id=None ):
        if not all(( access_token , action_id )):
            raise ValueError('must submit action_id' )
        url = "https://graph.facebook.com/%s" % ( action_id )
        post_data = {
            'access_token' : access_token ,
        }
        post_data = urllib.urlencode(post_data)
        try:
            payload = self.api_proxy( url , post_data=post_data , expected_format='json.load' , is_delete=True )
            return payload
        except:
            raise

    def verify_signed_request( self , signed_request=None , timeout=None ):
        """ verifies the signedRequest from Facebook.  accepts a timeout value, to test against the 'issued_at' key within the payload
        
        this will always return a Tuple of ( BOOL , DICT )
        
        Bool :
            True = Signed Request is verified
            False = Signed Request is not verified

        Dict :
            if request is verified: the payload object as JSON
            if request is not verified: a 'python-error' key with the reason
            
        PLEASE NOTE:
            1. if the request is verfied, but the data is outside of the timeout, this will return FALSE as a bool; and the dict will the verified payload with an 'python-error' key.
            2. i chose 'python-error', because facebook is likely to change their spec. they do that. the chances of them adding 'error' are much greater than 'python-error'
        
        Reference documentation
        https://developers.facebook.com/docs/authentication/signed_request/
        "The signed_request parameter is the concatenation of a HMAC SHA-256 signature string, a period (.), and a base64url encoded JSON object."
        
        after starting this, i found someone already did the hard work.
        following is based on <NAME>'s blog post - http://sunilarora.org/parsing-signedrequest-parameter-in-python-bas
        """
        if signed_request is None:
            raise ValueError('must submit signed_request')

        def base64_url_decode(inp):
            padding_factor = (4 - len(inp) % 4) % 4
            inp += "="*padding_factor 
            return base64.b64decode(unicode(inp).translate(dict(zip(map(ord, u'-_'), u'+/'))))
        
        (signature,payload)= signed_request.split('.')

        decoded_signature = base64_url_decode(signature)
        data = json.loads(base64_url_decode(payload))

        if data.get('algorithm').upper() != 'HMAC-SHA256':
            return ( False , { 'python-error':  'Unknown algorithm - %s' % data.get('algorithm').upper() } )

        expected_sig = hmac.new( self.app_secret , msg=payload , digestmod=hashlib.sha256 ).digest()
    
        if decoded_signature != expected_sig:
            return ( None , {'python-error': 'signature (%s) != expected_sig (%s)' % { decoded_signature , expected_sig }  } )
            
        if timeout:
            time_now= int(time())
            diff = time_now - data['issued_at']
            if ( diff > timeout ) :
                data['python-error']= "payload issued outside of timeout window"
                return ( False , data )

        return ( True , data )
            
            

        





class FacebookPyramid(FacebookHub):

    def __init__( self, request , app_id=None , app_secret=None , app_scope=None , app_domain=None , oauth_code_redirect_uri=None , oauth_token_redirect_uri=None ):
        """Creates a new FacebookHub object, sets it up with Pyramid Config vars, and then proxies other functions into it"""
        self.request= request
        if app_id is None and 'facebook.app.id' in request.registry.settings :
            app_id = request.registry.settings['facebook.app.id']
        if app_secret is None and 'facebook.app.secret' in request.registry.settings :
            app_secret = request.registry.settings['facebook.app.secret']
        if app_scope is None and 'facebook.app.scope' in request.registry.settings :
            app_scope = request.registry.settings['facebook.app.scope']
        if app_domain is None:
            app_domain = request.registry.settings['app_domain']
        if oauth_code_redirect_uri is None and 'facebook.app.oauth_code_redirect_uri' in request.registry.settings :
            oauth_code_redirect_uri = request.registry.settings['facebook.app.oauth_code_redirect_uri']
        if oauth_token_redirect_uri is None and 'facebook.app.oauth_token_redirect_uri' in request.registry.settings :
            oauth_token_redirect_uri = request.registry.settings['facebook.app.oauth_token_redirect_uri']
        FacebookHub.__init__( self , app_id=app_id , app_secret=app_secret , app_scope=app_scope , app_domain=app_domain , oauth_code_redirect_uri=oauth_code_redirect_uri , oauth_token_redirect_uri=oauth_token_redirect_uri )
        
    def oauth_code__url_access_token( self, submitted_code=None , redirect_uri=None , scope=None ):
        if submitted_code is None:
            submitted_code = self.request.params.get('code')
        return FacebookHub.oauth_code__url_access_token( self , submitted_code=submitted_code , redirect_uri=redirect_uri , scope=scope )

    def oauth_code__get_access_token( self , submitted_code=None , redirect_uri=None , scope=None ):
        if submitted_code is None:
            submitted_code = self.request.params.get('code')
        return FacebookHub.oauth_code__get_access_token( self , submitted_code=submitted_code , redirect_uri=redirect_uri , scope=scope )

    def oauth_code__get_access_token_and_profile( self , submitted_code=None , redirect_uri=None , scope=None ):
        if submitted_code is None:
            submitted_code = self.request.params.get('code')
        return FacebookHub.oauth_code__get_access_token_and_profile( self , submitted_code=submitted_code , redirect_uri=redirect_uri , scope=scope )

{'fraction_non_alphanumeric': 0.0875106202209006, 'fraction_numerical': 0.0035642497461508176, 'mean_word_length': 2.5473390179359012, 'pattern_counts': {'":': 10, '<': 14, '<?xml version=': 0, '>': 16, 'https://': 28, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 54, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1057208', 'n_tokens_mistral': 13868, 'n_tokens_neox': 13402, 'n_words': 3643}