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