Skip to content

Commit

Permalink
Merge pull request #1 from thieman/tnt-appsecret-proof
Browse files Browse the repository at this point in the history
Tnt appsecret proof
  • Loading branch information
thieman authored Mar 7, 2018
2 parents 27697ed + 762ef54 commit 6c4b20c
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ jobs:
python -m venv ~/venv;
fi;
. ~/venv/bin/activate
if [ $(python -c "import platform; print(platform.python_version_tuple()[0])") == "2" ]; then
pip install mock
fi;
pip install flake8 doc8 pygments
pip install .
- run:
Expand Down
5 changes: 5 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,17 @@ You can read more about `Facebook's Graph API here`_.
* ``proxies`` - A ``dict`` with proxy-settings that Requests should use.
`See Requests documentation`_.
* ``session`` - A `Requests Session object`_.
* ``app_secret`` - (optional) A ``string`` representing the secret key of your
app. If both ``app_secret`` and ``access_token`` are present this will be
used to compute an `appsecret_proof`_


.. _Read more about access tokens here: https://developers.facebook.com/docs/facebook-login/access-tokens
.. _See more here: http://docs.python-requests.org/en/latest/user/quickstart/#timeouts
.. _version of Facebook's Graph API to use: https://developers.facebook.com/docs/apps/changelog#versions
.. _See Requests documentation: http://www.python-requests.org/en/latest/user/advanced/#proxies
.. _Requests Session object: http://docs.python-requests.org/en/master/user/advanced/#session-objects
.. _appsecret_proof: https://developers.facebook.com/docs/graph-api/securing-requests

**Example**

Expand Down
18 changes: 17 additions & 1 deletion facebook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class GraphAPI(object):
"""

def __init__(self, access_token=None, timeout=None, version=None,
proxies=None, session=None):
proxies=None, session=None, app_secret=None):
# The default version is only used if the version kwarg does not exist.
default_version = VALID_API_VERSIONS[0]

Expand All @@ -89,6 +89,16 @@ def __init__(self, access_token=None, timeout=None, version=None,
self.proxies = proxies
self.session = session or requests.Session()

if app_secret and self.access_token:
# Generates an app secret hmac based on
# https://developers.facebook.com/docs/graph-api/securing-requests
self.app_secret_hmac = hmac.new(app_secret.encode('ascii'),
msg=access_token.encode('ascii'),
digestmod=hashlib.sha256
).hexdigest()
else:
self.app_secret_hmac = None

if version:
version_regex = re.compile("^\d\.\d{1,2}$")
match = version_regex.search(str(version))
Expand Down Expand Up @@ -258,6 +268,12 @@ def request(
elif "access_token" not in args:
args["access_token"] = self.access_token

if self.app_secret_hmac:
if post_args is not None:
post_args["appsecret_proof"] = self.app_secret_hmac
else:
args["appsecret_proof"] = self.app_secret_hmac

try:
response = self.session.request(
method or "GET",
Expand Down
78 changes: 78 additions & 0 deletions test/test_facebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
from urlparse import parse_qs, urlparse
from urllib import urlencode

try:
from unittest import mock
except ImportError:
import mock


class FacebookTestCase(unittest.TestCase):
"""
Expand Down Expand Up @@ -351,5 +356,78 @@ def test_get_user_permissions_nonexistant_user(self):
facebook.GraphAPI(token).get_permissions(1)


class TestAppSecretProof(FacebookTestCase):

proof = '4dad02ff1693df832f9c183fe400fc4f601360be06514acb4a73edb783eec345' # noqa

def test_appsecret_proof_set(self):
api = facebook.GraphAPI(access_token='abc123', app_secret='xyz789')
self.assertEqual(api.app_secret_hmac, self.proof)

def test_appsecret_proof_no_access_token(self):
api = facebook.GraphAPI(app_secret='xyz789')
self.assertEqual(api.app_secret_hmac, None)

def test_appsecret_proof_no_app_secret(self):
api = facebook.GraphAPI(access_token='abc123')
self.assertEqual(api.app_secret_hmac, None)

@mock.patch('requests.request')
def test_appsecret_proof_is_set_on_get_request(self, mock_request):
api = facebook.GraphAPI(access_token='abc123', app_secret='xyz789')
mock_response = mock.Mock()
mock_response.headers = {'content-type': 'json'}
mock_response.json.return_value = {}
mock_request.return_value = mock_response
api.session.request = mock_request
api.request('some-path')
mock_request.assert_called_once_with(
'GET',
'https://graph.facebook.com/some-path',
data=None,
files=None,
params={'access_token': 'abc123',
'appsecret_proof': self.proof},
proxies=None,
timeout=None)

@mock.patch('requests.request')
def test_appsecret_proof_is_set_on_post_request(self, mock_request):
api = facebook.GraphAPI(access_token='abc123', app_secret='xyz789')
mock_response = mock.Mock()
mock_response.headers = {'content-type': 'json'}
mock_response.json.return_value = {}
mock_request.return_value = mock_response
api.session.request = mock_request
api.request('some-path', method='POST')
mock_request.assert_called_once_with(
'POST',
'https://graph.facebook.com/some-path',
data=None,
files=None,
params={'access_token': 'abc123',
'appsecret_proof': self.proof},
proxies=None,
timeout=None)

@mock.patch('requests.request')
def test_missing_appsecret_proof_is_not_set_on_request(self, mock_request):
api = facebook.GraphAPI(access_token='abc123')
mock_response = mock.Mock()
mock_response.headers = {'content-type': 'json'}
mock_response.json.return_value = {}
mock_request.return_value = mock_response
api.session.request = mock_request
api.request('some-path')
mock_request.assert_called_once_with(
'GET',
'https://graph.facebook.com/some-path',
data=None,
files=None,
params={'access_token': 'abc123'},
proxies=None,
timeout=None)


if __name__ == '__main__':
unittest.main()

0 comments on commit 6c4b20c

Please sign in to comment.