web_application.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. # -*- coding: utf-8 -*-
  2. """
  3. oauthlib.oauth2.rfc6749
  4. ~~~~~~~~~~~~~~~~~~~~~~~
  5. This module is an implementation of various logic needed
  6. for consuming and providing OAuth 2.0 RFC6749.
  7. """
  8. import warnings
  9. from ..parameters import (
  10. parse_authorization_code_response, prepare_grant_uri,
  11. prepare_token_request,
  12. )
  13. from .base import Client
  14. class WebApplicationClient(Client):
  15. """A client utilizing the authorization code grant workflow.
  16. A web application is a confidential client running on a web
  17. server. Resource owners access the client via an HTML user
  18. interface rendered in a user-agent on the device used by the
  19. resource owner. The client credentials as well as any access
  20. token issued to the client are stored on the web server and are
  21. not exposed to or accessible by the resource owner.
  22. The authorization code grant type is used to obtain both access
  23. tokens and refresh tokens and is optimized for confidential clients.
  24. As a redirection-based flow, the client must be capable of
  25. interacting with the resource owner's user-agent (typically a web
  26. browser) and capable of receiving incoming requests (via redirection)
  27. from the authorization server.
  28. """
  29. grant_type = 'authorization_code'
  30. def __init__(self, client_id, code=None, **kwargs):
  31. super().__init__(client_id, **kwargs)
  32. self.code = code
  33. def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
  34. state=None, code_challenge=None, code_challenge_method='plain', **kwargs):
  35. """Prepare the authorization code request URI
  36. The client constructs the request URI by adding the following
  37. parameters to the query component of the authorization endpoint URI
  38. using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
  39. :param redirect_uri: OPTIONAL. The redirect URI must be an absolute URI
  40. and it should have been registered with the OAuth
  41. provider prior to use. As described in `Section 3.1.2`_.
  42. :param scope: OPTIONAL. The scope of the access request as described by
  43. Section 3.3`_. These may be any string but are commonly
  44. URIs or various categories such as ``videos`` or ``documents``.
  45. :param state: RECOMMENDED. An opaque value used by the client to maintain
  46. state between the request and callback. The authorization
  47. server includes this value when redirecting the user-agent back
  48. to the client. The parameter SHOULD be used for preventing
  49. cross-site request forgery as described in `Section 10.12`_.
  50. :param code_challenge: OPTIONAL. PKCE parameter. REQUIRED if PKCE is enforced.
  51. A challenge derived from the code_verifier that is sent in the
  52. authorization request, to be verified against later.
  53. :param code_challenge_method: OPTIONAL. PKCE parameter. A method that was used to derive code challenge.
  54. Defaults to "plain" if not present in the request.
  55. :param kwargs: Extra arguments to include in the request URI.
  56. In addition to supplied parameters, OAuthLib will append the ``client_id``
  57. that was provided in the constructor as well as the mandatory ``response_type``
  58. argument, set to ``code``::
  59. >>> from oauthlib.oauth2 import WebApplicationClient
  60. >>> client = WebApplicationClient('your_id')
  61. >>> client.prepare_request_uri('https://example.com')
  62. 'https://example.com?client_id=your_id&response_type=code'
  63. >>> client.prepare_request_uri('https://example.com', redirect_uri='https://a.b/callback')
  64. 'https://example.com?client_id=your_id&response_type=code&redirect_uri=https%3A%2F%2Fa.b%2Fcallback'
  65. >>> client.prepare_request_uri('https://example.com', scope=['profile', 'pictures'])
  66. 'https://example.com?client_id=your_id&response_type=code&scope=profile+pictures'
  67. >>> client.prepare_request_uri('https://example.com', code_challenge='kjasBS523KdkAILD2k78NdcJSk2k3KHG6')
  68. 'https://example.com?client_id=your_id&response_type=code&code_challenge=kjasBS523KdkAILD2k78NdcJSk2k3KHG6'
  69. >>> client.prepare_request_uri('https://example.com', code_challenge_method='S256')
  70. 'https://example.com?client_id=your_id&response_type=code&code_challenge_method=S256'
  71. >>> client.prepare_request_uri('https://example.com', foo='bar')
  72. 'https://example.com?client_id=your_id&response_type=code&foo=bar'
  73. .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
  74. .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
  75. .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
  76. .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
  77. .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
  78. """
  79. scope = self.scope if scope is None else scope
  80. return prepare_grant_uri(uri, self.client_id, 'code',
  81. redirect_uri=redirect_uri, scope=scope, state=state, code_challenge=code_challenge,
  82. code_challenge_method=code_challenge_method, **kwargs)
  83. def prepare_request_body(self, code=None, redirect_uri=None, body='',
  84. include_client_id=True, code_verifier=None, **kwargs):
  85. """Prepare the access token request body.
  86. The client makes a request to the token endpoint by adding the
  87. following parameters using the "application/x-www-form-urlencoded"
  88. format in the HTTP request entity-body:
  89. :param code: REQUIRED. The authorization code received from the
  90. authorization server.
  91. :param redirect_uri: REQUIRED, if the "redirect_uri" parameter was included in the
  92. authorization request as described in `Section 4.1.1`_, and their
  93. values MUST be identical.
  94. :param body: Existing request body (URL encoded string) to embed parameters
  95. into. This may contain extra parameters. Default ''.
  96. :param include_client_id: `True` (default) to send the `client_id` in the
  97. body of the upstream request. This is required
  98. if the client is not authenticating with the
  99. authorization server as described in `Section 3.2.1`_.
  100. :type include_client_id: Boolean
  101. :param code_verifier: OPTIONAL. A cryptographically random string that is used to correlate the
  102. authorization request to the token request.
  103. :param kwargs: Extra parameters to include in the token request.
  104. In addition OAuthLib will add the ``grant_type`` parameter set to
  105. ``authorization_code``.
  106. If the client type is confidential or the client was issued client
  107. credentials (or assigned other authentication requirements), the
  108. client MUST authenticate with the authorization server as described
  109. in `Section 3.2.1`_::
  110. >>> from oauthlib.oauth2 import WebApplicationClient
  111. >>> client = WebApplicationClient('your_id')
  112. >>> client.prepare_request_body(code='sh35ksdf09sf')
  113. 'grant_type=authorization_code&code=sh35ksdf09sf'
  114. >>> client.prepare_request_body(code_verifier='KB46DCKJ873NCGXK5GD682NHDKK34GR')
  115. 'grant_type=authorization_code&code_verifier=KB46DCKJ873NCGXK5GD682NHDKK34GR'
  116. >>> client.prepare_request_body(code='sh35ksdf09sf', foo='bar')
  117. 'grant_type=authorization_code&code=sh35ksdf09sf&foo=bar'
  118. `Section 3.2.1` also states:
  119. In the "authorization_code" "grant_type" request to the token
  120. endpoint, an unauthenticated client MUST send its "client_id" to
  121. prevent itself from inadvertently accepting a code intended for a
  122. client with a different "client_id". This protects the client from
  123. substitution of the authentication code. (It provides no additional
  124. security for the protected resource.)
  125. .. _`Section 4.1.1`: https://tools.ietf.org/html/rfc6749#section-4.1.1
  126. .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
  127. """
  128. code = code or self.code
  129. if 'client_id' in kwargs:
  130. warnings.warn("`client_id` has been deprecated in favor of "
  131. "`include_client_id`, a boolean value which will "
  132. "include the already configured `self.client_id`.",
  133. DeprecationWarning)
  134. if kwargs['client_id'] != self.client_id:
  135. raise ValueError("`client_id` was supplied as an argument, but "
  136. "it does not match `self.client_id`")
  137. kwargs['client_id'] = self.client_id
  138. kwargs['include_client_id'] = include_client_id
  139. return prepare_token_request(self.grant_type, code=code, body=body,
  140. redirect_uri=redirect_uri, code_verifier=code_verifier, **kwargs)
  141. def parse_request_uri_response(self, uri, state=None):
  142. """Parse the URI query for code and state.
  143. If the resource owner grants the access request, the authorization
  144. server issues an authorization code and delivers it to the client by
  145. adding the following parameters to the query component of the
  146. redirection URI using the "application/x-www-form-urlencoded" format:
  147. :param uri: The callback URI that resulted from the user being redirected
  148. back from the provider to you, the client.
  149. :param state: The state provided in the authorization request.
  150. **code**
  151. The authorization code generated by the authorization server.
  152. The authorization code MUST expire shortly after it is issued
  153. to mitigate the risk of leaks. A maximum authorization code
  154. lifetime of 10 minutes is RECOMMENDED. The client MUST NOT
  155. use the authorization code more than once. If an authorization
  156. code is used more than once, the authorization server MUST deny
  157. the request and SHOULD revoke (when possible) all tokens
  158. previously issued based on that authorization code.
  159. The authorization code is bound to the client identifier and
  160. redirection URI.
  161. **state**
  162. If the "state" parameter was present in the authorization request.
  163. This method is mainly intended to enforce strict state checking with
  164. the added benefit of easily extracting parameters from the URI::
  165. >>> from oauthlib.oauth2 import WebApplicationClient
  166. >>> client = WebApplicationClient('your_id')
  167. >>> uri = 'https://example.com/callback?code=sdfkjh345&state=sfetw45'
  168. >>> client.parse_request_uri_response(uri, state='sfetw45')
  169. {'state': 'sfetw45', 'code': 'sdfkjh345'}
  170. >>> client.parse_request_uri_response(uri, state='other')
  171. Traceback (most recent call last):
  172. File "<stdin>", line 1, in <module>
  173. File "oauthlib/oauth2/rfc6749/__init__.py", line 357, in parse_request_uri_response
  174. back from the provider to you, the client.
  175. File "oauthlib/oauth2/rfc6749/parameters.py", line 153, in parse_authorization_code_response
  176. raise MismatchingStateError()
  177. oauthlib.oauth2.rfc6749.errors.MismatchingStateError
  178. """
  179. response = parse_authorization_code_response(uri, state=state)
  180. self.populate_code_attributes(response)
  181. return response