From 4241af878f87e2fb1c5d9294685136697c922249 Mon Sep 17 00:00:00 2001 From: Sean Ray Date: Fri, 19 Apr 2024 14:40:14 -0400 Subject: [PATCH 1/3] Support non-x-www-form-urlencoded bodies returned from refresh_token_request compliance hook * Handle the case where a non-x-www-form-urlencoded string is returned from the refresh_token_request compliance hook, and allow it to be used as a raw string in the subsequent request * Add wix compliance fix to send token request and refresh requests with JSON bodies * Add unit tests for wix compliance fixes --- requests_oauthlib/compliance_fixes/wix.py | 36 +++++++++++++++++++ requests_oauthlib/oauth2_session.py | 8 ++++- tests/test_compliance_fixes.py | 44 +++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 requests_oauthlib/compliance_fixes/wix.py diff --git a/requests_oauthlib/compliance_fixes/wix.py b/requests_oauthlib/compliance_fixes/wix.py new file mode 100644 index 0000000..121a56b --- /dev/null +++ b/requests_oauthlib/compliance_fixes/wix.py @@ -0,0 +1,36 @@ +import json +from oauthlib.common import urldecode + +""" +Wix requires the request body for token requests to be sent in JSON format +instead of x-www-form-urlencoded. +""" +def wix_compliance_fix(session): + + def _non_compliant_access_token_request_body( + url: str, headers: dict, request_kwargs: dict + ): + """ + Move the request body from the `data` kwarg to the `json` kwarg, + and set the `Content-Type` header to `application/json`. + """ + headers["Content-Type"] = "application/json" + request_kwargs["json"] = request_kwargs["data"] + del request_kwargs["data"] + return url, headers, request_kwargs + + def _non_compliant_refresh_token_request_body( + token_url: str, headers: dict, body: str + ): + """ + Convert the body from a urlencoded string to a JSON string, + and set the `Content-Type` header to `application/json`. + """ + headers["Content-Type"] = "application/json" + body = json.dumps(dict(urldecode(body))) + return token_url, headers, body + + session.register_compliance_hook("access_token_request", _non_compliant_access_token_request_body) + session.register_compliance_hook("refresh_token_request", _non_compliant_refresh_token_request_body) + + return session diff --git a/requests_oauthlib/oauth2_session.py b/requests_oauthlib/oauth2_session.py index 93cc4d7..fc0c0da 100644 --- a/requests_oauthlib/oauth2_session.py +++ b/requests_oauthlib/oauth2_session.py @@ -473,9 +473,15 @@ def refresh_token( log.debug("Invoking refresh_token_request hook %s.", hook) token_url, headers, body = hook(token_url, headers, body) + try: + body = dict(urldecode(body)) + except ValueError: + log.debug("Could not decode body as urlencoded data. Using body in request as is: %s", body) + pass + r = self.post( token_url, - data=dict(urldecode(body)), + data=body, auth=auth, timeout=timeout, headers=headers, diff --git a/tests/test_compliance_fixes.py b/tests/test_compliance_fixes.py index c5166bd..3892136 100644 --- a/tests/test_compliance_fixes.py +++ b/tests/test_compliance_fixes.py @@ -16,6 +16,7 @@ from requests_oauthlib.compliance_fixes import instagram_compliance_fix from requests_oauthlib.compliance_fixes import plentymarkets_compliance_fix from requests_oauthlib.compliance_fixes import ebay_compliance_fix +from requests_oauthlib.compliance_fixes.wix import wix_compliance_fix class FacebookComplianceFixTest(TestCase): @@ -385,3 +386,46 @@ def test_refresh_token(self): "https://example.com/refresh", ) assert token["token_type"] == "Bearer" + + +class WixComplianceFixTest(TestCase): + + def setUp(self): + wix = OAuth2Session() + self.session = wix_compliance_fix(wix) + + def test_access_token_request_sent_as_json(self): + mocker = requests_mock.Mocker() + mocker.post( + "https://www.wixapis.com/oauth/access", + request_headers={"Content-Type": "application/json"}, + json={"access_token": "sample_access_token", "refresh_token": "sample_refresh_token"}, + additional_matcher=lambda req: req.json() == {'grant_type': 'authorization_code', 'code': 'sample_code'} + ) + mocker.start() + self.addCleanup(mocker.stop) + + token = self.session.fetch_token( + "https://www.wixapis.com/oauth/access", + code="sample_code" + ) + + self.assertEqual(token, {"access_token": "sample_access_token", "refresh_token": "sample_refresh_token"}) + + def test_refresh_token_request_sent_as_json(self): + mocker = requests_mock.Mocker() + mocker.post( + "https://www.wixapis.com/oauth/access", + request_headers={"Content-Type": "application/json"}, + json={"access_token": "sample_access_token", "refresh_token": "sample_refresh_token"}, + additional_matcher=lambda req: req.json() == {'grant_type': 'refresh_token', 'refresh_token': 'sample_refresh_token'} + ) + mocker.start() + self.addCleanup(mocker.stop) + + token = self.session.refresh_token( + "https://www.wixapis.com/oauth/access", + refresh_token="sample_refresh_token" + ) + + self.assertEqual(token, {"access_token": "sample_access_token", "refresh_token": "sample_refresh_token"}) From a2443bb0f21250daabb27c59a306e5b1e6cfcd13 Mon Sep 17 00:00:00 2001 From: Sean Ray Date: Fri, 23 Aug 2024 10:27:40 -0400 Subject: [PATCH 2/3] specifically target application/x-www-form-urlencoded content type instead of handling valueerror --- requests_oauthlib/oauth2_session.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/requests_oauthlib/oauth2_session.py b/requests_oauthlib/oauth2_session.py index fc0c0da..05ae35d 100644 --- a/requests_oauthlib/oauth2_session.py +++ b/requests_oauthlib/oauth2_session.py @@ -473,11 +473,8 @@ def refresh_token( log.debug("Invoking refresh_token_request hook %s.", hook) token_url, headers, body = hook(token_url, headers, body) - try: + if headers['Content-Type'] == "application/x-www-form-urlencoded": body = dict(urldecode(body)) - except ValueError: - log.debug("Could not decode body as urlencoded data. Using body in request as is: %s", body) - pass r = self.post( token_url, From 6d8b6459ff16295626d922479535b5e13ccaff7b Mon Sep 17 00:00:00 2001 From: Sean Ray Date: Fri, 23 Aug 2024 11:14:47 -0400 Subject: [PATCH 3/3] add compliance fix to __init__.py --- requests_oauthlib/compliance_fixes/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/requests_oauthlib/compliance_fixes/__init__.py b/requests_oauthlib/compliance_fixes/__init__.py index 8815ea0..ee4378e 100644 --- a/requests_oauthlib/compliance_fixes/__init__.py +++ b/requests_oauthlib/compliance_fixes/__init__.py @@ -7,3 +7,4 @@ from .weibo import weibo_compliance_fix from .plentymarkets import plentymarkets_compliance_fix from .ebay import ebay_compliance_fix +from .wix import wix_compliance_fix