test_compliance_fixes.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. from unittest import TestCase
  2. import requests
  3. import requests_mock
  4. import time
  5. from urllib.parse import urlparse, parse_qs
  6. from oauthlib.oauth2.rfc6749.errors import InvalidGrantError
  7. from requests_oauthlib import OAuth2Session
  8. from requests_oauthlib.compliance_fixes import facebook_compliance_fix
  9. from requests_oauthlib.compliance_fixes import fitbit_compliance_fix
  10. from requests_oauthlib.compliance_fixes import mailchimp_compliance_fix
  11. from requests_oauthlib.compliance_fixes import weibo_compliance_fix
  12. from requests_oauthlib.compliance_fixes import slack_compliance_fix
  13. from requests_oauthlib.compliance_fixes import instagram_compliance_fix
  14. from requests_oauthlib.compliance_fixes import plentymarkets_compliance_fix
  15. from requests_oauthlib.compliance_fixes import ebay_compliance_fix
  16. class FacebookComplianceFixTest(TestCase):
  17. def setUp(self):
  18. mocker = requests_mock.Mocker()
  19. mocker.post(
  20. "https://graph.facebook.com/oauth/access_token",
  21. text="access_token=urlencoded",
  22. headers={"Content-Type": "text/plain"},
  23. )
  24. mocker.start()
  25. self.addCleanup(mocker.stop)
  26. facebook = OAuth2Session("someclientid", redirect_uri="https://i.b")
  27. self.session = facebook_compliance_fix(facebook)
  28. def test_fetch_access_token(self):
  29. token = self.session.fetch_token(
  30. "https://graph.facebook.com/oauth/access_token",
  31. client_secret="someclientsecret",
  32. authorization_response="https://i.b/?code=hello",
  33. )
  34. self.assertEqual(token, {"access_token": "urlencoded", "token_type": "Bearer"})
  35. class FitbitComplianceFixTest(TestCase):
  36. def setUp(self):
  37. self.mocker = requests_mock.Mocker()
  38. self.mocker.post(
  39. "https://api.fitbit.com/oauth2/token",
  40. json={"errors": [{"errorType": "invalid_grant"}]},
  41. )
  42. self.mocker.start()
  43. self.addCleanup(self.mocker.stop)
  44. fitbit = OAuth2Session("someclientid", redirect_uri="https://i.b")
  45. self.session = fitbit_compliance_fix(fitbit)
  46. def test_fetch_access_token(self):
  47. self.assertRaises(
  48. InvalidGrantError,
  49. self.session.fetch_token,
  50. "https://api.fitbit.com/oauth2/token",
  51. client_secret="someclientsecret",
  52. authorization_response="https://i.b/?code=hello",
  53. )
  54. self.mocker.post(
  55. "https://api.fitbit.com/oauth2/token", json={"access_token": "fitbit"}
  56. )
  57. token = self.session.fetch_token(
  58. "https://api.fitbit.com/oauth2/token", client_secret="good"
  59. )
  60. self.assertEqual(token, {"access_token": "fitbit"})
  61. def test_refresh_token(self):
  62. self.assertRaises(
  63. InvalidGrantError,
  64. self.session.refresh_token,
  65. "https://api.fitbit.com/oauth2/token",
  66. auth=requests.auth.HTTPBasicAuth("someclientid", "someclientsecret"),
  67. )
  68. self.mocker.post(
  69. "https://api.fitbit.com/oauth2/token",
  70. json={"access_token": "access", "refresh_token": "refresh"},
  71. )
  72. token = self.session.refresh_token(
  73. "https://api.fitbit.com/oauth2/token",
  74. auth=requests.auth.HTTPBasicAuth("someclientid", "someclientsecret"),
  75. )
  76. self.assertEqual(token["access_token"], "access")
  77. self.assertEqual(token["refresh_token"], "refresh")
  78. class MailChimpComplianceFixTest(TestCase):
  79. def setUp(self):
  80. mocker = requests_mock.Mocker()
  81. mocker.post(
  82. "https://login.mailchimp.com/oauth2/token",
  83. json={"access_token": "mailchimp", "expires_in": 0, "scope": None},
  84. )
  85. mocker.start()
  86. self.addCleanup(mocker.stop)
  87. mailchimp = OAuth2Session("someclientid", redirect_uri="https://i.b")
  88. self.session = mailchimp_compliance_fix(mailchimp)
  89. def test_fetch_access_token(self):
  90. token = self.session.fetch_token(
  91. "https://login.mailchimp.com/oauth2/token",
  92. client_secret="someclientsecret",
  93. authorization_response="https://i.b/?code=hello",
  94. )
  95. # Times should be close
  96. approx_expires_at = time.time() + 3600
  97. actual_expires_at = token.pop("expires_at")
  98. self.assertAlmostEqual(actual_expires_at, approx_expires_at, places=2)
  99. # Other token values exact
  100. self.assertEqual(token, {"access_token": "mailchimp", "expires_in": 3600})
  101. # And no scope at all
  102. self.assertNotIn("scope", token)
  103. class WeiboComplianceFixTest(TestCase):
  104. def setUp(self):
  105. mocker = requests_mock.Mocker()
  106. mocker.post(
  107. "https://api.weibo.com/oauth2/access_token", json={"access_token": "weibo"}
  108. )
  109. mocker.start()
  110. self.addCleanup(mocker.stop)
  111. weibo = OAuth2Session("someclientid", redirect_uri="https://i.b")
  112. self.session = weibo_compliance_fix(weibo)
  113. def test_fetch_access_token(self):
  114. token = self.session.fetch_token(
  115. "https://api.weibo.com/oauth2/access_token",
  116. client_secret="someclientsecret",
  117. authorization_response="https://i.b/?code=hello",
  118. )
  119. self.assertEqual(token, {"access_token": "weibo", "token_type": "Bearer"})
  120. class SlackComplianceFixTest(TestCase):
  121. def setUp(self):
  122. mocker = requests_mock.Mocker()
  123. mocker.post(
  124. "https://slack.com/api/oauth.access",
  125. json={"access_token": "xoxt-23984754863-2348975623103", "scope": "read"},
  126. )
  127. for method in ("GET", "POST"):
  128. mocker.request(
  129. method=method,
  130. url="https://slack.com/api/auth.test",
  131. json={
  132. "ok": True,
  133. "url": "https://myteam.slack.com/",
  134. "team": "My Team",
  135. "user": "cal",
  136. "team_id": "T12345",
  137. "user_id": "U12345",
  138. },
  139. )
  140. mocker.start()
  141. self.addCleanup(mocker.stop)
  142. slack = OAuth2Session("someclientid", redirect_uri="https://i.b")
  143. self.session = slack_compliance_fix(slack)
  144. def test_protected_request(self):
  145. self.session.token = {"access_token": "dummy-access-token"}
  146. response = self.session.get("https://slack.com/api/auth.test")
  147. url = response.request.url
  148. query = parse_qs(urlparse(url).query)
  149. self.assertNotIn("token", query)
  150. body = response.request.body
  151. data = parse_qs(body)
  152. self.assertEqual(data["token"], ["dummy-access-token"])
  153. def test_protected_request_override_token_get(self):
  154. self.session.token = {"access_token": "dummy-access-token"}
  155. response = self.session.get(
  156. "https://slack.com/api/auth.test", data={"token": "different-token"}
  157. )
  158. url = response.request.url
  159. query = parse_qs(urlparse(url).query)
  160. self.assertNotIn("token", query)
  161. body = response.request.body
  162. data = parse_qs(body)
  163. self.assertEqual(data["token"], ["different-token"])
  164. def test_protected_request_override_token_post(self):
  165. self.session.token = {"access_token": "dummy-access-token"}
  166. response = self.session.post(
  167. "https://slack.com/api/auth.test", data={"token": "different-token"}
  168. )
  169. url = response.request.url
  170. query = parse_qs(urlparse(url).query)
  171. self.assertNotIn("token", query)
  172. body = response.request.body
  173. data = parse_qs(body)
  174. self.assertEqual(data["token"], ["different-token"])
  175. def test_protected_request_override_token_url(self):
  176. self.session.token = {"access_token": "dummy-access-token"}
  177. response = self.session.get(
  178. "https://slack.com/api/auth.test?token=different-token"
  179. )
  180. url = response.request.url
  181. query = parse_qs(urlparse(url).query)
  182. self.assertEqual(query["token"], ["different-token"])
  183. self.assertIsNone(response.request.body)
  184. class InstagramComplianceFixTest(TestCase):
  185. def setUp(self):
  186. mocker = requests_mock.Mocker()
  187. mocker.request(
  188. method="GET",
  189. url="https://api.instagram.com/v1/users/self",
  190. json={
  191. "data": {
  192. "id": "1574083",
  193. "username": "snoopdogg",
  194. "full_name": "Snoop Dogg",
  195. "profile_picture": "http://distillery.s3.amazonaws.com/profiles/profile_1574083_75sq_1295469061.jpg",
  196. "bio": "This is my bio",
  197. "website": "http://snoopdogg.com",
  198. "is_business": False,
  199. "counts": {"media": 1320, "follows": 420, "followed_by": 3410},
  200. }
  201. },
  202. )
  203. mocker.start()
  204. self.addCleanup(mocker.stop)
  205. instagram = OAuth2Session("someclientid", redirect_uri="https://i.b")
  206. self.session = instagram_compliance_fix(instagram)
  207. def test_protected_request(self):
  208. self.session.token = {"access_token": "dummy-access-token"}
  209. response = self.session.get("https://api.instagram.com/v1/users/self")
  210. url = response.request.url
  211. query = parse_qs(urlparse(url).query)
  212. self.assertIn("access_token", query)
  213. self.assertEqual(query["access_token"], ["dummy-access-token"])
  214. def test_protected_request_dont_override(self):
  215. """check that if the access_token param
  216. already exist we don't override it"""
  217. self.session.token = {"access_token": "dummy-access-token"}
  218. response = self.session.get(
  219. "https://api.instagram.com/v1/users/self?access_token=correct-access-token"
  220. )
  221. url = response.request.url
  222. query = parse_qs(urlparse(url).query)
  223. self.assertIn("access_token", query)
  224. self.assertEqual(query["access_token"], ["correct-access-token"])
  225. class PlentymarketsComplianceFixTest(TestCase):
  226. def setUp(self):
  227. mocker = requests_mock.Mocker()
  228. mocker.post(
  229. "https://shop.plentymarkets-cloud02.com",
  230. json={
  231. "accessToken": "ecUN1r8KhJewMCdLAmpHOdZ4O0ofXKB9zf6CXK61",
  232. "tokenType": "Bearer",
  233. "expiresIn": 86400,
  234. "refreshToken": "iG2kBGIjcXaRE4xmTVUnv7xwxX7XMcWCHqJmFaSX",
  235. },
  236. headers={"Content-Type": "application/json"},
  237. )
  238. mocker.start()
  239. self.addCleanup(mocker.stop)
  240. plentymarkets = OAuth2Session("someclientid", redirect_uri="https://i.b")
  241. self.session = plentymarkets_compliance_fix(plentymarkets)
  242. def test_fetch_access_token(self):
  243. token = self.session.fetch_token(
  244. "https://shop.plentymarkets-cloud02.com",
  245. authorization_response="https://i.b/?code=hello",
  246. )
  247. approx_expires_at = time.time() + 86400
  248. actual_expires_at = token.pop("expires_at")
  249. self.assertAlmostEqual(actual_expires_at, approx_expires_at, places=2)
  250. self.assertEqual(
  251. token,
  252. {
  253. "access_token": "ecUN1r8KhJewMCdLAmpHOdZ4O0ofXKB9zf6CXK61",
  254. "expires_in": 86400,
  255. "token_type": "Bearer",
  256. "refresh_token": "iG2kBGIjcXaRE4xmTVUnv7xwxX7XMcWCHqJmFaSX",
  257. },
  258. )
  259. class EbayComplianceFixTest(TestCase):
  260. def setUp(self):
  261. mocker = requests_mock.Mocker()
  262. mocker.post(
  263. "https://api.ebay.com/identity/v1/oauth2/token",
  264. json={
  265. "access_token": "this is the access token",
  266. "expires_in": 7200,
  267. "token_type": "Application Access Token",
  268. },
  269. headers={"Content-Type": "application/json"},
  270. )
  271. mocker.start()
  272. self.addCleanup(mocker.stop)
  273. session = OAuth2Session()
  274. self.fixed_session = ebay_compliance_fix(session)
  275. def test_fetch_access_token(self):
  276. token = self.fixed_session.fetch_token(
  277. "https://api.ebay.com/identity/v1/oauth2/token",
  278. authorization_response="https://i.b/?code=hello",
  279. )
  280. assert token["token_type"] == "Bearer"
  281. def access_and_refresh_token_request_compliance_fix_test(session, client_secret):
  282. def _non_compliant_header(url, headers, body):
  283. headers["X-Client-Secret"] = client_secret
  284. return url, headers, body
  285. session.register_compliance_hook("access_token_request", _non_compliant_header)
  286. session.register_compliance_hook("refresh_token_request", _non_compliant_header)
  287. return session
  288. class RefreshTokenRequestComplianceFixTest(TestCase):
  289. value_to_test_for = "value_to_test_for"
  290. def setUp(self):
  291. mocker = requests_mock.Mocker()
  292. mocker.post(
  293. "https://example.com/token",
  294. request_headers={"X-Client-Secret": self.value_to_test_for},
  295. json={
  296. "access_token": "this is the access token",
  297. "expires_in": 7200,
  298. "token_type": "Bearer",
  299. },
  300. headers={"Content-Type": "application/json"},
  301. )
  302. mocker.post(
  303. "https://example.com/refresh",
  304. request_headers={"X-Client-Secret": self.value_to_test_for},
  305. json={
  306. "access_token": "this is the access token",
  307. "expires_in": 7200,
  308. "token_type": "Bearer",
  309. },
  310. headers={"Content-Type": "application/json"},
  311. )
  312. mocker.start()
  313. self.addCleanup(mocker.stop)
  314. session = OAuth2Session()
  315. self.fixed_session = access_and_refresh_token_request_compliance_fix_test(
  316. session, self.value_to_test_for
  317. )
  318. def test_access_token(self):
  319. token = self.fixed_session.fetch_token(
  320. "https://example.com/token",
  321. authorization_response="https://i.b/?code=hello",
  322. )
  323. assert token["token_type"] == "Bearer"
  324. def test_refresh_token(self):
  325. token = self.fixed_session.refresh_token(
  326. "https://example.com/refresh",
  327. )
  328. assert token["token_type"] == "Bearer"