From 5bbfa0bc7f28f39abbe2fa2873e98653a040a8fd Mon Sep 17 00:00:00 2001 From: Roy Stewart Date: Thu, 5 Oct 2017 12:23:05 +0000 Subject: [PATCH 01/16] Added Long Lived User Tokens Added support for long lived user access tokens. This includes the api call to exchange a short lived token for a long lived token and the api call to get a code to be redeemed for a long lived token. --- facebook/__init__.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/facebook/__init__.py b/facebook/__init__.py index 7f45bdee..d0002e2e 100755 --- a/facebook/__init__.py +++ b/facebook/__init__.py @@ -311,6 +311,30 @@ def get_app_access_token(self, app_id, app_secret, offline=False): return self.request("{0}/oauth/access_token".format(self.version), args=args)["access_token"] + def get_long_lived_token(self, app_id, app_secret): + """Exchanges a short lived user access token for a long lived one. + + This method uses a pre-existing short lived user access token and + exchanges it for a long lived user access token.""" + args = {'grant_type': 'fb_exchange_token', + 'client_id': app_id, + 'client_secret': app_secret, + 'fb_exchange_token': self.access_token} + + return self.request("{0}/oauth/access_token".format(self.version), args=args) + + def get_long_lived_token_code(self, app_id, app_secret, redirect_uri): + """Gets a code to be exchanged for a long lived token. + + Uses a pre-existing server side long lived token to return a code + that can be redeemed for a client side long lived token.""" + args = {'access_token': self.access_token, + 'client_id': app_id, + 'client_secret': app_secret, + 'redirect_uri': redirect_uri} + + return self.request("{0}oauth/client_code".format(self.version), args=args) + def get_access_token_from_code( self, code, redirect_uri, app_id, app_secret): """Get an access token from the "code" returned from an OAuth dialog. From 63a86edff952111bf9d69603706ef5a0ad909b74 Mon Sep 17 00:00:00 2001 From: Roy Stewart Date: Thu, 5 Oct 2017 15:20:26 +0000 Subject: [PATCH 02/16] Fix PEP8 style --- facebook/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/facebook/__init__.py b/facebook/__init__.py index d0002e2e..5fa8b2ee 100755 --- a/facebook/__init__.py +++ b/facebook/__init__.py @@ -321,7 +321,8 @@ def get_long_lived_token(self, app_id, app_secret): 'client_secret': app_secret, 'fb_exchange_token': self.access_token} - return self.request("{0}/oauth/access_token".format(self.version), args=args) + return self.request("{0}/oauth/access_token".format(self.version), + args=args) def get_long_lived_token_code(self, app_id, app_secret, redirect_uri): """Gets a code to be exchanged for a long lived token. @@ -333,7 +334,8 @@ def get_long_lived_token_code(self, app_id, app_secret, redirect_uri): 'client_secret': app_secret, 'redirect_uri': redirect_uri} - return self.request("{0}oauth/client_code".format(self.version), args=args) + return self.request("{0}oauth/client_code".format(self.version), + args=args)["code"] def get_access_token_from_code( self, code, redirect_uri, app_id, app_secret): From 9c3b69dfbdc46a8c0e8d28771f580b9f1dffb6dc Mon Sep 17 00:00:00 2001 From: Roy Stewart Date: Thu, 5 Oct 2017 15:31:33 +0000 Subject: [PATCH 03/16] Fix PEP8 style --- facebook/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/facebook/__init__.py b/facebook/__init__.py index 5fa8b2ee..843a78b8 100755 --- a/facebook/__init__.py +++ b/facebook/__init__.py @@ -312,10 +312,11 @@ def get_app_access_token(self, app_id, app_secret, offline=False): args=args)["access_token"] def get_long_lived_token(self, app_id, app_secret): - """Exchanges a short lived user access token for a long lived one. - + """ + Gets a long lived access token. This method uses a pre-existing short lived user access token and - exchanges it for a long lived user access token.""" + exchanges it for a long lived user access token. + """ args = {'grant_type': 'fb_exchange_token', 'client_id': app_id, 'client_secret': app_secret, @@ -325,10 +326,11 @@ def get_long_lived_token(self, app_id, app_secret): args=args) def get_long_lived_token_code(self, app_id, app_secret, redirect_uri): - """Gets a code to be exchanged for a long lived token. - + """ + Gets a code to be exchanged for a long lived token. Uses a pre-existing server side long lived token to return a code - that can be redeemed for a client side long lived token.""" + that can be redeemed for a client side long lived token. + """ args = {'access_token': self.access_token, 'client_id': app_id, 'client_secret': app_secret, From a308d633d333bec9f835df9d26f2c6231c27a4d7 Mon Sep 17 00:00:00 2001 From: Roy Stewart Date: Fri, 13 Oct 2017 03:02:00 +0000 Subject: [PATCH 04/16] Add get_code_from_token test and documentation --- docs/api.rst | 25 +++++++++++++++++++++++++ facebook/__init__.py | 26 +++++++++----------------- test/test_facebook.py | 16 ++++++++++++++++ 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 30dc6595..2f62e465 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -330,6 +330,31 @@ Generates Facebook login URL to request access token and permissions. fb_login_url = graph.auth_url(app_id, canvas_url, perms) print(fb_login_url) +get_code_from_token +^^^^^^^^^^^^^^^^^^^ +https://developers.facebook.com/docs/facebook-login/access-tokens/expiration-and-extension/ + +Exchanges an existing long livd user access token on the server side for a +code that can be redeemed for a long lived user token on the client side. + +**Parameters** + +* ``app_id`` - ``integer`` Facebook application id that is requesting a code. +* ``app_secret`` - ``string`` Facebook application secret that is attempting + to get a code from the access token. +* ``redirect_uri`` - ``string`` Return URL after successful authentication, + usually parses returned Facebook response for authorisation request. + +**Example** + +.. code-block:: python + + app_id = 1231241241 + app_secret = '123abc456def' + redirect_uri = 'https://domain.com/that-handles-auth-response/' + code = graph.get_code_from_token(app_id, app_secret, redirect_uri) + print(code) + get_permissions ^^^^^^^^^^^^^^^ diff --git a/facebook/__init__.py b/facebook/__init__.py index 843a78b8..daf01e28 100755 --- a/facebook/__init__.py +++ b/facebook/__init__.py @@ -311,21 +311,7 @@ def get_app_access_token(self, app_id, app_secret, offline=False): return self.request("{0}/oauth/access_token".format(self.version), args=args)["access_token"] - def get_long_lived_token(self, app_id, app_secret): - """ - Gets a long lived access token. - This method uses a pre-existing short lived user access token and - exchanges it for a long lived user access token. - """ - args = {'grant_type': 'fb_exchange_token', - 'client_id': app_id, - 'client_secret': app_secret, - 'fb_exchange_token': self.access_token} - - return self.request("{0}/oauth/access_token".format(self.version), - args=args) - - def get_long_lived_token_code(self, app_id, app_secret, redirect_uri): + def get_code_from_token(self, app_id, app_secret, redirect_uri): """ Gets a code to be exchanged for a long lived token. Uses a pre-existing server side long lived token to return a code @@ -336,8 +322,14 @@ def get_long_lived_token_code(self, app_id, app_secret, redirect_uri): 'client_secret': app_secret, 'redirect_uri': redirect_uri} - return self.request("{0}oauth/client_code".format(self.version), - args=args)["code"] + try: + code = self.request("{0}/oauth/client_code".format(self.version), + args=args)["code"] + return code + except GraphAPIError: + self.access_token = self.extend_access_token(app_id, app_secret) + code = self.request("{0}/oauth/client_code".format(self.version), + args=args)["code"] def get_access_token_from_code( self, code, redirect_uri, app_id, app_secret): diff --git a/test/test_facebook.py b/test/test_facebook.py index ed96f461..55995a57 100644 --- a/test/test_facebook.py +++ b/test/test_facebook.py @@ -190,6 +190,22 @@ def test_extend_access_token(self): self.assertEqual( e.message, "fb_exchange_token parameter not specified") + def test_get_code_from_token(self): + """ + Test that get_code_from_token returns a valid code which can be + exchanged for an access token. + """ + redirect_uri = 'https://localhost/facebook/callback/' + test_token = facebook.GraphAPI().request( + '{0}/accounts/test-users'.format( + app_id))[0]['access_token'] + self.assertTrue(facebook.GraphAPI(test_token).get_code_from_token( + self.app_id, self.secret, redirect_uri), + 'Code not returned by get_code_from_token method') + self.assertTrue(facebook.GraphApi().get_access_token_from_code( + self.app_id, self.secret), 'Invalid code returned by' + 'get_code_from_token method') + def test_bogus_access_token(self): graph = facebook.GraphAPI(access_token='wrong_token') self.assertRaises(facebook.GraphAPIError, graph.get_object, 'me') From 98420fa218a861426e83a555bea9bc27ff5ed939 Mon Sep 17 00:00:00 2001 From: Roy Stewart Date: Fri, 13 Oct 2017 03:16:20 +0000 Subject: [PATCH 05/16] Correct error in test method. --- test/test_facebook.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_facebook.py b/test/test_facebook.py index 55995a57..2d99b946 100644 --- a/test/test_facebook.py +++ b/test/test_facebook.py @@ -197,8 +197,8 @@ def test_get_code_from_token(self): """ redirect_uri = 'https://localhost/facebook/callback/' test_token = facebook.GraphAPI().request( - '{0}/accounts/test-users'.format( - app_id))[0]['access_token'] + '{0}/{1}/accounts/test-users'.format( + self.version, self.app_id))[0]['access_token'] self.assertTrue(facebook.GraphAPI(test_token).get_code_from_token( self.app_id, self.secret, redirect_uri), 'Code not returned by get_code_from_token method') From 05edd080951a4c90e6b7aa17a9ba7fe88e2d38df Mon Sep 17 00:00:00 2001 From: Roy Stewart Date: Fri, 13 Oct 2017 03:27:36 +0000 Subject: [PATCH 06/16] Correct error in test method. --- test/test_facebook.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_facebook.py b/test/test_facebook.py index 2d99b946..794d7229 100644 --- a/test/test_facebook.py +++ b/test/test_facebook.py @@ -198,7 +198,8 @@ def test_get_code_from_token(self): redirect_uri = 'https://localhost/facebook/callback/' test_token = facebook.GraphAPI().request( '{0}/{1}/accounts/test-users'.format( - self.version, self.app_id))[0]['access_token'] + GraphAPI().version, + self.app_id))[0]['access_token'] self.assertTrue(facebook.GraphAPI(test_token).get_code_from_token( self.app_id, self.secret, redirect_uri), 'Code not returned by get_code_from_token method') From c37c485f12a6883ef7c66a3bdb4edb35233a55fb Mon Sep 17 00:00:00 2001 From: Roy Stewart Date: Fri, 13 Oct 2017 03:42:04 +0000 Subject: [PATCH 07/16] Correct error in test method. --- test/test_facebook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_facebook.py b/test/test_facebook.py index 794d7229..e0ce1173 100644 --- a/test/test_facebook.py +++ b/test/test_facebook.py @@ -198,7 +198,7 @@ def test_get_code_from_token(self): redirect_uri = 'https://localhost/facebook/callback/' test_token = facebook.GraphAPI().request( '{0}/{1}/accounts/test-users'.format( - GraphAPI().version, + facebook.GraphAPI().version, self.app_id))[0]['access_token'] self.assertTrue(facebook.GraphAPI(test_token).get_code_from_token( self.app_id, self.secret, redirect_uri), From ff920b6369eed1777a1991b6f3913d64f65fa978 Mon Sep 17 00:00:00 2001 From: Roy Stewart Date: Fri, 13 Oct 2017 03:53:11 +0000 Subject: [PATCH 08/16] Correct error in test method. --- test/test_facebook.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_facebook.py b/test/test_facebook.py index e0ce1173..e2df074c 100644 --- a/test/test_facebook.py +++ b/test/test_facebook.py @@ -196,6 +196,7 @@ def test_get_code_from_token(self): exchanged for an access token. """ redirect_uri = 'https://localhost/facebook/callback/' + app_token = GraphAPI().get_app_access_token(app_id, secret) test_token = facebook.GraphAPI().request( '{0}/{1}/accounts/test-users'.format( facebook.GraphAPI().version, From 938180ae08a5a0c4f433dd9cd9fc2e650c4f0de1 Mon Sep 17 00:00:00 2001 From: Roy Stewart Date: Fri, 13 Oct 2017 03:56:39 +0000 Subject: [PATCH 09/16] Correct error in test method. --- test/test_facebook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_facebook.py b/test/test_facebook.py index e2df074c..6a0852ba 100644 --- a/test/test_facebook.py +++ b/test/test_facebook.py @@ -197,7 +197,7 @@ def test_get_code_from_token(self): """ redirect_uri = 'https://localhost/facebook/callback/' app_token = GraphAPI().get_app_access_token(app_id, secret) - test_token = facebook.GraphAPI().request( + test_token = facebook.GraphAPI(app_token).request( '{0}/{1}/accounts/test-users'.format( facebook.GraphAPI().version, self.app_id))[0]['access_token'] From 2fa066a0c1896a3adbee46066132f1d2960ec6e7 Mon Sep 17 00:00:00 2001 From: Roy Stewart Date: Mon, 16 Oct 2017 01:53:45 +0000 Subject: [PATCH 10/16] Correct error in test method --- test/test_facebook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_facebook.py b/test/test_facebook.py index 6a0852ba..e0939df4 100644 --- a/test/test_facebook.py +++ b/test/test_facebook.py @@ -196,7 +196,7 @@ def test_get_code_from_token(self): exchanged for an access token. """ redirect_uri = 'https://localhost/facebook/callback/' - app_token = GraphAPI().get_app_access_token(app_id, secret) + app_token = facbook.GraphAPI().get_app_access_token(app_id, secret) test_token = facebook.GraphAPI(app_token).request( '{0}/{1}/accounts/test-users'.format( facebook.GraphAPI().version, From 228f6d072bd185337f662727fdcb65d389addbf4 Mon Sep 17 00:00:00 2001 From: Roy Stewart Date: Mon, 16 Oct 2017 02:22:14 +0000 Subject: [PATCH 11/16] Correct typo --- test/test_facebook.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_facebook.py b/test/test_facebook.py index e0939df4..be6efd52 100644 --- a/test/test_facebook.py +++ b/test/test_facebook.py @@ -196,15 +196,15 @@ def test_get_code_from_token(self): exchanged for an access token. """ redirect_uri = 'https://localhost/facebook/callback/' - app_token = facbook.GraphAPI().get_app_access_token(app_id, secret) + app_token = facebook.GraphAPI().get_app_access_token(app_id, secret) test_token = facebook.GraphAPI(app_token).request( '{0}/{1}/accounts/test-users'.format( facebook.GraphAPI().version, self.app_id))[0]['access_token'] - self.assertTrue(facebook.GraphAPI(test_token).get_code_from_token( + self.assertIsNotNone(facebook.GraphAPI(test_token).get_code_from_token( self.app_id, self.secret, redirect_uri), 'Code not returned by get_code_from_token method') - self.assertTrue(facebook.GraphApi().get_access_token_from_code( + self.assertIsNotNone(facebook.GraphApi().get_access_token_from_code( self.app_id, self.secret), 'Invalid code returned by' 'get_code_from_token method') From 91f090fa5756897eebc2409e45d1ef3049cd4688 Mon Sep 17 00:00:00 2001 From: Roy Stewart Date: Mon, 16 Oct 2017 02:31:54 +0000 Subject: [PATCH 12/16] Correct error in test method. --- test/test_facebook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_facebook.py b/test/test_facebook.py index be6efd52..515af212 100644 --- a/test/test_facebook.py +++ b/test/test_facebook.py @@ -196,7 +196,7 @@ def test_get_code_from_token(self): exchanged for an access token. """ redirect_uri = 'https://localhost/facebook/callback/' - app_token = facebook.GraphAPI().get_app_access_token(app_id, secret) + app_token = facebook.GraphAPI().get_app_access_token(self.app_id, self.secret) test_token = facebook.GraphAPI(app_token).request( '{0}/{1}/accounts/test-users'.format( facebook.GraphAPI().version, From 5acdfbb7bd4a9b14b0e66308722fdfadd61ed1fc Mon Sep 17 00:00:00 2001 From: Roy Stewart Date: Mon, 16 Oct 2017 18:36:56 +0000 Subject: [PATCH 13/16] Correct error in test method --- test/test_facebook.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/test_facebook.py b/test/test_facebook.py index 515af212..b57908f7 100644 --- a/test/test_facebook.py +++ b/test/test_facebook.py @@ -196,11 +196,7 @@ def test_get_code_from_token(self): exchanged for an access token. """ redirect_uri = 'https://localhost/facebook/callback/' - app_token = facebook.GraphAPI().get_app_access_token(self.app_id, self.secret) - test_token = facebook.GraphAPI(app_token).request( - '{0}/{1}/accounts/test-users'.format( - facebook.GraphAPI().version, - self.app_id))[0]['access_token'] + test_token = self.test_users[0]['access_token'] self.assertIsNotNone(facebook.GraphAPI(test_token).get_code_from_token( self.app_id, self.secret, redirect_uri), 'Code not returned by get_code_from_token method') From cdabeb1a8c432221bf6d262b2c61d6ec275a2159 Mon Sep 17 00:00:00 2001 From: Roy Stewart Date: Tue, 17 Oct 2017 02:01:29 +0000 Subject: [PATCH 14/16] Correct error in test method. --- test/test_facebook.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/test_facebook.py b/test/test_facebook.py index b57908f7..fa29a433 100644 --- a/test/test_facebook.py +++ b/test/test_facebook.py @@ -196,11 +196,15 @@ def test_get_code_from_token(self): exchanged for an access token. """ redirect_uri = 'https://localhost/facebook/callback/' + app_token = facebook.GraphAPI().get_app_access_token() + graph = facebook.GraphAPU(app_token) + create_test_users(aelf.app_id, facebook.GraphAPI(),1) test_token = self.test_users[0]['access_token'] - self.assertIsNotNone(facebook.GraphAPI(test_token).get_code_from_token( + graph = facebook.GraphAPI(test_token) + self.assertIsNotNone(graph.get_code_from_token( self.app_id, self.secret, redirect_uri), 'Code not returned by get_code_from_token method') - self.assertIsNotNone(facebook.GraphApi().get_access_token_from_code( + self.assertIsNotNone(graph.get_access_token_from_code( self.app_id, self.secret), 'Invalid code returned by' 'get_code_from_token method') From 0ff19e5a32706eb47c754577358f37bf56d08dce Mon Sep 17 00:00:00 2001 From: Roy Stewart Date: Tue, 17 Oct 2017 02:10:40 +0000 Subject: [PATCH 15/16] Correct error in test method. --- test/test_facebook.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/test_facebook.py b/test/test_facebook.py index fa29a433..892709a7 100644 --- a/test/test_facebook.py +++ b/test/test_facebook.py @@ -196,9 +196,10 @@ def test_get_code_from_token(self): exchanged for an access token. """ redirect_uri = 'https://localhost/facebook/callback/' - app_token = facebook.GraphAPI().get_app_access_token() - graph = facebook.GraphAPU(app_token) - create_test_users(aelf.app_id, facebook.GraphAPI(),1) + app_token = facebook.GraphAPI().get_app_access_token(self.app_id, + self.secret) + create_test_users(aelf.app_id, + facebook.GraphAPI(app_token), 1) test_token = self.test_users[0]['access_token'] graph = facebook.GraphAPI(test_token) self.assertIsNotNone(graph.get_code_from_token( From f5be416c2b14f01e69494027f3737dc5b1694245 Mon Sep 17 00:00:00 2001 From: Roy Stewart Date: Tue, 17 Oct 2017 02:25:39 +0000 Subject: [PATCH 16/16] Correct typo. --- test/test_facebook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_facebook.py b/test/test_facebook.py index 892709a7..9d14bfb2 100644 --- a/test/test_facebook.py +++ b/test/test_facebook.py @@ -198,7 +198,7 @@ def test_get_code_from_token(self): redirect_uri = 'https://localhost/facebook/callback/' app_token = facebook.GraphAPI().get_app_access_token(self.app_id, self.secret) - create_test_users(aelf.app_id, + self.create_test_users(self.app_id, facebook.GraphAPI(app_token), 1) test_token = self.test_users[0]['access_token'] graph = facebook.GraphAPI(test_token)