test_web_application.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. # -*- coding: utf-8 -*-
  2. import os
  3. import urllib.parse as urlparse
  4. import warnings
  5. from unittest.mock import patch
  6. from oauthlib import common, signals
  7. from oauthlib.oauth2 import (
  8. BackendApplicationClient, Client, LegacyApplicationClient,
  9. MobileApplicationClient, WebApplicationClient,
  10. )
  11. from oauthlib.oauth2.rfc6749 import errors, utils
  12. from oauthlib.oauth2.rfc6749.clients import AUTH_HEADER, BODY, URI_QUERY
  13. from tests.unittest import TestCase
  14. @patch('time.time', new=lambda: 1000)
  15. class WebApplicationClientTest(TestCase):
  16. client_id = "someclientid"
  17. client_secret = 'someclientsecret'
  18. uri = "https://example.com/path?query=world"
  19. uri_id = uri + "&response_type=code&client_id=" + client_id
  20. uri_redirect = uri_id + "&redirect_uri=http%3A%2F%2Fmy.page.com%2Fcallback"
  21. redirect_uri = "http://my.page.com/callback"
  22. code_verifier = "code_verifier"
  23. scope = ["/profile"]
  24. state = "xyz"
  25. code_challenge = "code_challenge"
  26. code_challenge_method = "S256"
  27. uri_scope = uri_id + "&scope=%2Fprofile"
  28. uri_state = uri_id + "&state=" + state
  29. uri_code_challenge = uri_id + "&code_challenge=" + code_challenge + "&code_challenge_method=" + code_challenge_method
  30. uri_code_challenge_method = uri_id + "&code_challenge=" + code_challenge + "&code_challenge_method=plain"
  31. kwargs = {
  32. "some": "providers",
  33. "require": "extra arguments"
  34. }
  35. uri_kwargs = uri_id + "&some=providers&require=extra+arguments"
  36. uri_authorize_code = uri_redirect + "&scope=%2Fprofile&state=" + state
  37. code = "zzzzaaaa"
  38. body = "not=empty"
  39. body_code = "not=empty&grant_type=authorization_code&code={}&client_id={}".format(code, client_id)
  40. body_redirect = body_code + "&redirect_uri=http%3A%2F%2Fmy.page.com%2Fcallback"
  41. body_code_verifier = body_code + "&code_verifier=code_verifier"
  42. body_kwargs = body_code + "&some=providers&require=extra+arguments"
  43. response_uri = "https://client.example.com/cb?code=zzzzaaaa&state=xyz"
  44. response = {"code": "zzzzaaaa", "state": "xyz"}
  45. token_json = ('{ "access_token":"2YotnFZFEjr1zCsicMWpAA",'
  46. ' "token_type":"example",'
  47. ' "expires_in":3600,'
  48. ' "scope":"/profile",'
  49. ' "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",'
  50. ' "example_parameter":"example_value"}')
  51. token = {
  52. "access_token": "2YotnFZFEjr1zCsicMWpAA",
  53. "token_type": "example",
  54. "expires_in": 3600,
  55. "expires_at": 4600,
  56. "scope": scope,
  57. "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
  58. "example_parameter": "example_value"
  59. }
  60. def test_auth_grant_uri(self):
  61. client = WebApplicationClient(self.client_id)
  62. # Basic, no extra arguments
  63. uri = client.prepare_request_uri(self.uri)
  64. self.assertURLEqual(uri, self.uri_id)
  65. # With redirection uri
  66. uri = client.prepare_request_uri(self.uri, redirect_uri=self.redirect_uri)
  67. self.assertURLEqual(uri, self.uri_redirect)
  68. # With scope
  69. uri = client.prepare_request_uri(self.uri, scope=self.scope)
  70. self.assertURLEqual(uri, self.uri_scope)
  71. # With state
  72. uri = client.prepare_request_uri(self.uri, state=self.state)
  73. self.assertURLEqual(uri, self.uri_state)
  74. # with code_challenge and code_challenge_method
  75. uri = client.prepare_request_uri(self.uri, code_challenge=self.code_challenge, code_challenge_method=self.code_challenge_method)
  76. self.assertURLEqual(uri, self.uri_code_challenge)
  77. # with no code_challenge_method
  78. uri = client.prepare_request_uri(self.uri, code_challenge=self.code_challenge)
  79. self.assertURLEqual(uri, self.uri_code_challenge_method)
  80. # With extra parameters through kwargs
  81. uri = client.prepare_request_uri(self.uri, **self.kwargs)
  82. self.assertURLEqual(uri, self.uri_kwargs)
  83. def test_request_body(self):
  84. client = WebApplicationClient(self.client_id, code=self.code)
  85. # Basic, no extra arguments
  86. body = client.prepare_request_body(body=self.body)
  87. self.assertFormBodyEqual(body, self.body_code)
  88. rclient = WebApplicationClient(self.client_id)
  89. body = rclient.prepare_request_body(code=self.code, body=self.body)
  90. self.assertFormBodyEqual(body, self.body_code)
  91. # With redirection uri
  92. body = client.prepare_request_body(body=self.body, redirect_uri=self.redirect_uri)
  93. self.assertFormBodyEqual(body, self.body_redirect)
  94. # With code verifier
  95. body = client.prepare_request_body(body=self.body, code_verifier=self.code_verifier)
  96. self.assertFormBodyEqual(body, self.body_code_verifier)
  97. # With extra parameters
  98. body = client.prepare_request_body(body=self.body, **self.kwargs)
  99. self.assertFormBodyEqual(body, self.body_kwargs)
  100. def test_parse_grant_uri_response(self):
  101. client = WebApplicationClient(self.client_id)
  102. # Parse code and state
  103. response = client.parse_request_uri_response(self.response_uri, state=self.state)
  104. self.assertEqual(response, self.response)
  105. self.assertEqual(client.code, self.code)
  106. # Mismatching state
  107. self.assertRaises(errors.MismatchingStateError,
  108. client.parse_request_uri_response,
  109. self.response_uri,
  110. state="invalid")
  111. def test_populate_attributes(self):
  112. client = WebApplicationClient(self.client_id)
  113. response_uri = (self.response_uri +
  114. "&access_token=EVIL-TOKEN"
  115. "&refresh_token=EVIL-TOKEN"
  116. "&mac_key=EVIL-KEY")
  117. client.parse_request_uri_response(response_uri, self.state)
  118. self.assertEqual(client.code, self.code)
  119. # We must not accidentally pick up any further security
  120. # credentials at this point.
  121. self.assertIsNone(client.access_token)
  122. self.assertIsNone(client.refresh_token)
  123. self.assertIsNone(client.mac_key)
  124. def test_parse_token_response(self):
  125. client = WebApplicationClient(self.client_id)
  126. # Parse code and state
  127. response = client.parse_request_body_response(self.token_json, scope=self.scope)
  128. self.assertEqual(response, self.token)
  129. self.assertEqual(client.access_token, response.get("access_token"))
  130. self.assertEqual(client.refresh_token, response.get("refresh_token"))
  131. self.assertEqual(client.token_type, response.get("token_type"))
  132. # Mismatching state
  133. self.assertRaises(Warning, client.parse_request_body_response, self.token_json, scope="invalid")
  134. os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = '1'
  135. token = client.parse_request_body_response(self.token_json, scope="invalid")
  136. self.assertTrue(token.scope_changed)
  137. scope_changes_recorded = []
  138. def record_scope_change(sender, message, old, new):
  139. scope_changes_recorded.append((message, old, new))
  140. signals.scope_changed.connect(record_scope_change)
  141. try:
  142. client.parse_request_body_response(self.token_json, scope="invalid")
  143. self.assertEqual(len(scope_changes_recorded), 1)
  144. message, old, new = scope_changes_recorded[0]
  145. self.assertEqual(message, 'Scope has changed from "invalid" to "/profile".')
  146. self.assertEqual(old, ['invalid'])
  147. self.assertEqual(new, ['/profile'])
  148. finally:
  149. signals.scope_changed.disconnect(record_scope_change)
  150. del os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE']
  151. def test_prepare_authorization_requeset(self):
  152. client = WebApplicationClient(self.client_id)
  153. url, header, body = client.prepare_authorization_request(
  154. self.uri, redirect_url=self.redirect_uri, state=self.state, scope=self.scope)
  155. self.assertURLEqual(url, self.uri_authorize_code)
  156. # verify default header and body only
  157. self.assertEqual(header, {'Content-Type': 'application/x-www-form-urlencoded'})
  158. self.assertEqual(body, '')
  159. def test_prepare_request_body(self):
  160. """
  161. see issue #585
  162. https://github.com/oauthlib/oauthlib/issues/585
  163. `prepare_request_body` should support the following scenarios:
  164. 1. Include client_id alone in the body (default)
  165. 2. Include client_id and client_secret in auth and not include them in the body (RFC preferred solution)
  166. 3. Include client_id and client_secret in the body (RFC alternative solution)
  167. 4. Include client_id in the body and an empty string for client_secret.
  168. """
  169. client = WebApplicationClient(self.client_id)
  170. # scenario 1, default behavior to include `client_id`
  171. r1 = client.prepare_request_body()
  172. self.assertEqual(r1, 'grant_type=authorization_code&client_id=%s' % self.client_id)
  173. r1b = client.prepare_request_body(include_client_id=True)
  174. self.assertEqual(r1b, 'grant_type=authorization_code&client_id=%s' % self.client_id)
  175. # scenario 2, do not include `client_id` in the body, so it can be sent in auth.
  176. r2 = client.prepare_request_body(include_client_id=False)
  177. self.assertEqual(r2, 'grant_type=authorization_code')
  178. # scenario 3, Include client_id and client_secret in the body (RFC alternative solution)
  179. # the order of kwargs being appended is not guaranteed. for brevity, check the 2 permutations instead of sorting
  180. r3 = client.prepare_request_body(client_secret=self.client_secret)
  181. r3_params = dict(urlparse.parse_qsl(r3, keep_blank_values=True))
  182. self.assertEqual(len(r3_params.keys()), 3)
  183. self.assertEqual(r3_params['grant_type'], 'authorization_code')
  184. self.assertEqual(r3_params['client_id'], self.client_id)
  185. self.assertEqual(r3_params['client_secret'], self.client_secret)
  186. r3b = client.prepare_request_body(include_client_id=True, client_secret=self.client_secret)
  187. r3b_params = dict(urlparse.parse_qsl(r3b, keep_blank_values=True))
  188. self.assertEqual(len(r3b_params.keys()), 3)
  189. self.assertEqual(r3b_params['grant_type'], 'authorization_code')
  190. self.assertEqual(r3b_params['client_id'], self.client_id)
  191. self.assertEqual(r3b_params['client_secret'], self.client_secret)
  192. # scenario 4, `client_secret` is an empty string
  193. r4 = client.prepare_request_body(include_client_id=True, client_secret='')
  194. r4_params = dict(urlparse.parse_qsl(r4, keep_blank_values=True))
  195. self.assertEqual(len(r4_params.keys()), 3)
  196. self.assertEqual(r4_params['grant_type'], 'authorization_code')
  197. self.assertEqual(r4_params['client_id'], self.client_id)
  198. self.assertEqual(r4_params['client_secret'], '')
  199. # scenario 4b, `client_secret` is `None`
  200. r4b = client.prepare_request_body(include_client_id=True, client_secret=None)
  201. r4b_params = dict(urlparse.parse_qsl(r4b, keep_blank_values=True))
  202. self.assertEqual(len(r4b_params.keys()), 2)
  203. self.assertEqual(r4b_params['grant_type'], 'authorization_code')
  204. self.assertEqual(r4b_params['client_id'], self.client_id)
  205. # scenario Warnings
  206. with warnings.catch_warnings(record=True) as w:
  207. warnings.simplefilter("always") # catch all
  208. # warning1 - raise a DeprecationWarning if a `client_id` is submitted
  209. rWarnings1 = client.prepare_request_body(client_id=self.client_id)
  210. self.assertEqual(len(w), 1)
  211. self.assertIsInstance(w[0].message, DeprecationWarning)
  212. # testing the exact warning message in Python2&Python3 is a pain
  213. # scenario Exceptions
  214. # exception1 - raise a ValueError if the a different `client_id` is submitted
  215. with self.assertRaises(ValueError) as cm:
  216. client.prepare_request_body(client_id='different_client_id')
  217. # testing the exact exception message in Python2&Python3 is a pain