def parse_signed_data(cls, signed_request, secret=facebook_settings.FACEBOOK_APP_SECRET): """ Thanks to http://stackoverflow.com/questions/3302946/how-to-base64-url-decode-in-python and http://sunilarora.org/parsing-signedrequest-parameter-in-python-bas """ from open_facebook.utils import base64_url_decode_php_style l = signed_request.split(".", 2) encoded_sig = l[0] payload = l[1] from open_facebook.utils import json sig = base64_url_decode_php_style(encoded_sig) import hmac import hashlib data = json.loads(base64_url_decode_php_style(payload)) if data.get("algorithm").upper() != "HMAC-SHA256": logger.error("Unknown algorithm") return None else: expected_sig = hmac.new(secret, msg=payload, digestmod=hashlib.sha256).digest() if sig != expected_sig: return None else: logger.debug("valid signed request received..") return data
def parse_signed_data(cls, signed_request, secret=facebook_settings.FACEBOOK_APP_SECRET): ''' Thanks to http://stackoverflow.com/questions/3302946/how-to-base64-url-decode-in-python and http://sunilarora.org/parsing-signedrequest-parameter-in-python-bas ''' from open_facebook.utils import base64_url_decode_php_style l = signed_request.split('.', 2) encoded_sig = l[0] payload = l[1] from open_facebook.utils import json sig = base64_url_decode_php_style(encoded_sig) import hmac import hashlib data = json.loads(base64_url_decode_php_style(payload)) algo = data.get('algorithm').upper() if algo != 'HMAC-SHA256': send_warning('Unknown algorithm we only support HMAC-SHA256 user asked for %s', algo) logger.error('Unknown algorithm') return None else: expected_sig = hmac.new(secret, msg=payload, digestmod=hashlib.sha256).digest() if sig != expected_sig: send_warning('Signature %s didnt match the expected signature %s', sig, expected_sig) return None else: logger.debug('valid signed request received..') return data
def parse_signed_data(cls, signed_request, secret=facebook_settings.FACEBOOK_APP_SECRET): ''' Thanks to http://stackoverflow.com/questions/3302946/how-to-base64-url-decode-in-python and http://sunilarora.org/parsing-signedrequest-parameter-in-python-bas ''' from open_facebook.utils import base64_url_decode_php_style l = signed_request.split('.', 2) encoded_sig = l[0] payload = l[1] from open_facebook.utils import json sig = base64_url_decode_php_style(encoded_sig) import hmac import hashlib data = json.loads(base64_url_decode_php_style(payload)) algo = data.get('algorithm').upper() if algo != 'HMAC-SHA256': send_warning('Unknown algorithm we only support HMAC-SHA256 ' \ 'user asked for %s', algo) logger.error('Unknown algorithm') return None else: expected_sig = hmac.new(secret, msg=payload, digestmod=hashlib.sha256).digest() if sig != expected_sig: send_warning('Signature %s didnt match the expected ' \ 'signature %s', sig, expected_sig) return None else: logger.debug('valid signed request received..') return data
def test_oauth_errors(self): expires_response = '''{ "error": { "type": "OAuthException", "message": "Session has expired at unix time SOME_TIME. The current unix time is SOME_TIME." } } ''' changed_password_response = ''' { "error": { "type": "OAuthException", "message": "The session has been invalidated because the user has changed the password." } } ''' deauthorized_response = ''' { "error": { "type": "OAuthException", "message": "Error validating access token: USER_ID has not authorized application APP_ID" } } ''' loggedout_response = ''' { "error": { "type": "OAuthException", "message": "Error validating access token: The session is invalid because the user logged out." } } ''' responses = [ expires_response, changed_password_response, deauthorized_response, loggedout_response ] response_objects = [] for response_string in responses: response = json.loads(response_string) response_objects.append(response) from open_facebook import exceptions as open_facebook_exceptions for response in response_objects: oauth = False try: FacebookConnection.raise_error(response['error']['type'], response['error']['message']) except open_facebook_exceptions.OAuthException as e: oauth = True assert oauth, 'response %s didnt raise oauth error' % response
def test_oauth_errors(self): expires_response = '''{ "error": { "type": "OAuthException", "message": "Session has expired at unix time SOME_TIME. The current unix time is SOME_TIME." } } ''' changed_password_response = ''' { "error": { "type": "OAuthException", "message": "The session has been invalidated because the user has changed the password." } } ''' deauthorized_response = ''' { "error": { "type": "OAuthException", "message": "Error validating access token: USER_ID has not authorized application APP_ID" } } ''' loggedout_response = ''' { "error": { "type": "OAuthException", "message": "Error validating access token: The session is invalid because the user logged out." } } ''' responses = [expires_response, changed_password_response, deauthorized_response, loggedout_response] response_objects = [] for response_string in responses: response = json.loads(response_string) response_objects.append(response) from open_facebook import exceptions as open_facebook_exceptions for response in response_objects: oauth = False try: FacebookConnection.raise_error(response['error']['type'], response['error']['message']) except open_facebook_exceptions.OAuthException, e: oauth = True assert oauth, 'response %s didnt raise oauth error' % response
def test_non_oauth_errors(self): object_open_graph_error = """ {"error": {"message": "(#3502) Object at URL http://www.fashiolista.com/my_style/list/441276/?og=active&utm_campaign=facebook_action_comment&utm_medium=facebook&utm_source=facebook has og:type of 'website'. The property 'list' requires an object of og:type 'fashiolista:list'. ", "code": 3502, "type": "OAuthException" } } """ response = json.loads(object_open_graph_error) def test(): FacebookConnection.raise_error( response["error"]["type"], response["error"]["message"], response["error"].get("code") ) self.assertRaises(OpenGraphException, test)
def test_non_oauth_errors(self): object_open_graph_error = ''' {"error": {"message": "(#3502) Object at URL http://www.fashiolista.com/my_style/list/441276/?og=active&utm_campaign=facebook_action_comment&utm_medium=facebook&utm_source=facebook has og:type of 'website'. The property 'list' requires an object of og:type 'fashiolista:list'. ", "code": 3502, "type": "OAuthException" } } ''' response = json.loads(object_open_graph_error) def test(): FacebookConnection.raise_error(response['error']['type'], response['error']['message'], response['error'].get('code')) self.assertRaises(OpenGraphException, test)
def parse_signed_data(cls, signed_request, secret=None): """Parse a ``signed_request`` from Facebook. Thanks to http://stackoverflow.com/questions/3302946/how-to-base64-url-decode-in-python and http://sunilarora.org/parsing-signedrequest-parameter-in-python-bas :param signed_request: The signed request to be verified. This is a string containing two dot-separated parts: signature and data. Each part is (PHP-style) base64-encoded. Signature is a HMAC-SHA256 signature of the data, using APP_SECRET as the key. Data is a JSON-encoded object. :param secret: The key to be used to verify signature. Defaults to ``FACEBOOK_APP_SECRET`` from settings. :returns: The decoded data object if signature is valid, else ``None``. """ from open_facebook.utils import base64_url_decode_php_style import hmac import hashlib if not signed_request: return if secret is None: secret = facebook_settings.FACEBOOK_APP_SECRET enc_signature, enc_payload = signed_request.split('.', 1) signature, payload = map(base64_url_decode_php_style, (enc_signature, enc_payload)) data = json.loads(payload) algo = data.get('algorithm').upper() try: if cls.verify_signature(enc_payload, secret, signature, algo): logger.debug("Received a valid signed request") return data else: logger.error("Invalid signed_data signature!") return except: logger.exception("Something went wrong while verifying the signed_request.")
response_file = e else: raise response = response_file.read().decode("utf8") break except (urllib2.HTTPError, urllib2.URLError), e: logger.warn("facebook encountered a timeout or error %s", unicode(e)) attempts -= 1 if not attempts: raise finally: if response_file: response_file.close() try: parsed_response = json.loads(response) logger.info("facebook send response %s" % parsed_response) except Exception, e: # using exception because we need to support multiple json libs :S parsed_response = QueryDict(response, True) logger.info("facebook send response %s" % parsed_response) if parsed_response and isinstance(parsed_response, dict): # of course we have two different syntaxes if parsed_response.get("error"): cls.raise_error(parsed_response["error"]["type"], parsed_response["error"]["message"]) elif parsed_response.get("error_code"): cls.raise_error(parsed_response["error_code"], parsed_response["error_msg"]) return parsed_response
def get_share_dict(self): share_dict_string = self.share_dict share_dict = json.loads(share_dict_string) return share_dict
response = response_file.read().decode('utf8') break except (urllib2.HTTPError, urllib2.URLError), e: logger.warn('facebook encountered a timeout or error %s', unicode(e)) attempts -= 1 if not attempts: raise finally: if response_file: response_file.close() if django_statsd: django_statsd.stop('facebook.%s' % path) try: parsed_response = json.loads(response) logger.info('facebook send response %s' % parsed_response) except Exception, e: # using exception because we need to support multiple json libs :S parsed_response = QueryDict(response, True) logger.info('facebook send response %s' % parsed_response) if parsed_response and isinstance(parsed_response, dict): # of course we have two different syntaxes if parsed_response.get('error'): cls.raise_error(parsed_response['error']['type'], parsed_response['error']['message']) elif parsed_response.get('error_code'): cls.raise_error(parsed_response['error_code'], parsed_response['error_msg'])
def _request(cls, url, post_data=None, timeout=REQUEST_TIMEOUT, attempts=REQUEST_ATTEMPTS): # change fb__explicitly_shared to fb:explicitly_shared if post_data: post_data = dict( (k.replace('__', ':'), v) for k, v in post_data.items()) logger.info('requesting url %s with post data %s', url, post_data) post_request = (post_data is not None or 'method=post' in url) if post_request and facebook_settings.FACEBOOK_READ_ONLY: logger.info('running in readonly mode') response = dict(id=123456789, setting_read_only=True) return response # nicely identify ourselves before sending the request opener = build_opener() opener.addheaders = [('User-agent', 'Open Facebook Python')] # get the statsd path to track response times with path = urlparse(url).path statsd_path = path.replace('.', '_') # give it a few shots, connection is buggy at times timeout_mp = 0 while attempts: # gradually increase the timeout upon failure timeout_mp += 1 extended_timeout = timeout * timeout_mp response_file = None encoded_params = encode_params(post_data) if post_data else None post_string = (urlencode(encoded_params) if post_data else None) try: start_statsd('facebook.%s' % statsd_path) try: response_file = opener.open( url, post_string, timeout=extended_timeout) response = response_file.read().decode('utf8') except (HTTPError,) as e: response_file = e response = response_file.read().decode('utf8') # Facebook sents error codes for many of their flows # we still want the json to allow for proper handling msg_format = 'FB request, error type %s, code %s' logger.warn(msg_format, type(e), getattr(e, 'code', None)) # detect if its a server or application error server_error = cls.is_server_error(e, response) if server_error: # trigger a retry raise URLError( 'Facebook is down %s' % response) break except (HTTPError, URLError, ssl.SSLError) as e: # These are often temporary errors, so we will retry before # failing error_format = 'Facebook encountered a timeout (%ss) or error %s' logger.warn(error_format, extended_timeout, unicode(e)) attempts -= 1 if not attempts: # if we have no more attempts actually raise the error error_instance = facebook_exceptions.convert_unreachable_exception( e) error_msg = 'Facebook request failed after several retries, raising error %s' logger.warn(error_msg, error_instance) raise error_instance finally: if response_file: response_file.close() stop_statsd('facebook.%s' % statsd_path) # Faceboook response is either # Valid json # A string which is a querydict (a=b&c=d...etc) # A html page stating FB is having trouble (but that shouldnt reach # this part of the code) try: parsed_response = json.loads(response) logger.info('facebook send response %s' % parsed_response) except Exception as e: # using exception because we need to support multiple json libs :S parsed_response = QueryDict(response, True) logger.info('facebook send response %s' % parsed_response) if parsed_response and isinstance(parsed_response, dict): # of course we have two different syntaxes if parsed_response.get('error'): cls.raise_error(parsed_response['error']['type'], parsed_response['error']['message'], parsed_response['error'].get('code')) elif parsed_response.get('error_code'): cls.raise_error(parsed_response['error_code'], parsed_response['error_msg']) return parsed_response
def _request(cls, url, post_data=None, timeout=REQUEST_TIMEOUT, attempts=REQUEST_ATTEMPTS): # change fb__explicitly_shared to fb:explicitly_shared if post_data: post_data = dict( (k.replace('__', ':'), v) for k, v in post_data.items()) logger.info('requesting url %s with post data %s', url, post_data) post_request = (post_data is not None or 'method=post' in url) if post_request and facebook_settings.FACEBOOK_READ_ONLY: logger.info('running in readonly mode') response = dict(id=123456789, setting_read_only=True) return response # nicely identify ourselves before sending the request opener = build_opener() opener.addheaders = [('User-agent', 'Open Facebook Python')] # get the statsd path to track response times with path = urlparse(url).path statsd_path = path.replace('.', '_') # give it a few shots, connection is buggy at times timeout_mp = 0 while attempts: # gradually increase the timeout upon failure timeout_mp += 1 extended_timeout = timeout * timeout_mp response_file = None encoded_params = encode_params(post_data) if post_data else None post_string = (urlencode(encoded_params) if post_data else None) try: start_statsd('facebook.%s' % statsd_path) try: response_file = opener.open(url, post_string, timeout=extended_timeout) response = response_file.read().decode('utf8') except (HTTPError, ) as e: response_file = e response = response_file.read().decode('utf8') # Facebook sents error codes for many of their flows # we still want the json to allow for proper handling msg_format = 'FB request, error type %s, code %s' logger.warn(msg_format, type(e), getattr(e, 'code', None)) # detect if its a server or application error server_error = cls.is_server_error(e, response) if server_error: # trigger a retry raise URLError('Facebook is down %s' % response) break except (HTTPError, URLError, ssl.SSLError) as e: # These are often temporary errors, so we will retry before # failing error_format = 'Facebook encountered a timeout (%ss) or error %s' logger.warn(error_format, extended_timeout, str(e)) attempts -= 1 if not attempts: # if we have no more attempts actually raise the error error_instance = facebook_exceptions.convert_unreachable_exception( e) error_msg = 'Facebook request failed after several retries, raising error %s' logger.warn(error_msg, error_instance) raise error_instance finally: if response_file: response_file.close() stop_statsd('facebook.%s' % statsd_path) # Faceboook response is either # Valid json # A string which is a querydict (a=b&c=d...etc) # A html page stating FB is having trouble (but that shouldnt reach # this part of the code) try: parsed_response = json.loads(response) logger.info('facebook send response %s' % parsed_response) except Exception as e: # using exception because we need to support multiple json libs :S parsed_response = QueryDict(response, True) logger.info('facebook send response %s' % parsed_response) if parsed_response and isinstance(parsed_response, dict): # of course we have two different syntaxes if parsed_response.get('error'): cls.raise_error(parsed_response['error']['type'], parsed_response['error']['message'], parsed_response['error'].get('code')) elif parsed_response.get('error_code'): cls.raise_error(parsed_response['error_code'], parsed_response['error_msg']) return parsed_response