Handle content-type header charset value for streaming API
[twitter-api-cdsw] / oauthlib / oauth2 / rfc6749 / grant_types / authorization_code.py
1 # -*- coding: utf-8 -*-
2 """
3 oauthlib.oauth2.rfc6749.grant_types
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 """
6 from __future__ import unicode_literals, absolute_import
7
8 import json
9 import logging
10
11 from oauthlib import common
12 from oauthlib.uri_validate import is_absolute_uri
13
14 from .base import GrantTypeBase
15 from .. import errors
16 from ..request_validator import RequestValidator
17
18 log = logging.getLogger(__name__)
19
20
21 class AuthorizationCodeGrant(GrantTypeBase):
22
23     """`Authorization Code Grant`_
24
25     The authorization code grant type is used to obtain both access
26     tokens and refresh tokens and is optimized for confidential clients.
27     Since this is a redirection-based flow, the client must be capable of
28     interacting with the resource owner's user-agent (typically a web
29     browser) and capable of receiving incoming requests (via redirection)
30     from the authorization server::
31
32         +----------+
33         | Resource |
34         |   Owner  |
35         |          |
36         +----------+
37              ^
38              |
39             (B)
40         +----|-----+          Client Identifier      +---------------+
41         |         -+----(A)-- & Redirection URI ---->|               |
42         |  User-   |                                 | Authorization |
43         |  Agent  -+----(B)-- User authenticates --->|     Server    |
44         |          |                                 |               |
45         |         -+----(C)-- Authorization Code ---<|               |
46         +-|----|---+                                 +---------------+
47           |    |                                         ^      v
48          (A)  (C)                                        |      |
49           |    |                                         |      |
50           ^    v                                         |      |
51         +---------+                                      |      |
52         |         |>---(D)-- Authorization Code ---------'      |
53         |  Client |          & Redirection URI                  |
54         |         |                                             |
55         |         |<---(E)----- Access Token -------------------'
56         +---------+       (w/ Optional Refresh Token)
57
58     Note: The lines illustrating steps (A), (B), and (C) are broken into
59     two parts as they pass through the user-agent.
60
61     Figure 3: Authorization Code Flow
62
63     The flow illustrated in Figure 3 includes the following steps:
64
65     (A)  The client initiates the flow by directing the resource owner's
66          user-agent to the authorization endpoint.  The client includes
67          its client identifier, requested scope, local state, and a
68          redirection URI to which the authorization server will send the
69          user-agent back once access is granted (or denied).
70
71     (B)  The authorization server authenticates the resource owner (via
72          the user-agent) and establishes whether the resource owner
73          grants or denies the client's access request.
74
75     (C)  Assuming the resource owner grants access, the authorization
76          server redirects the user-agent back to the client using the
77          redirection URI provided earlier (in the request or during
78          client registration).  The redirection URI includes an
79          authorization code and any local state provided by the client
80          earlier.
81
82     (D)  The client requests an access token from the authorization
83          server's token endpoint by including the authorization code
84          received in the previous step.  When making the request, the
85          client authenticates with the authorization server.  The client
86          includes the redirection URI used to obtain the authorization
87          code for verification.
88
89     (E)  The authorization server authenticates the client, validates the
90          authorization code, and ensures that the redirection URI
91          received matches the URI used to redirect the client in
92          step (C).  If valid, the authorization server responds back with
93          an access token and, optionally, a refresh token.
94
95     .. _`Authorization Code Grant`: http://tools.ietf.org/html/rfc6749#section-4.1
96     """
97
98     def __init__(self, request_validator=None):
99         self.request_validator = request_validator or RequestValidator()
100
101     def create_authorization_code(self, request):
102         """Generates an authorization grant represented as a dictionary."""
103         grant = {'code': common.generate_token()}
104         if hasattr(request, 'state') and request.state:
105             grant['state'] = request.state
106         log.debug('Created authorization code grant %r for request %r.',
107                   grant, request)
108         return grant
109
110     def create_authorization_response(self, request, token_handler):
111         """
112         The client constructs the request URI by adding the following
113         parameters to the query component of the authorization endpoint URI
114         using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
115
116         response_type
117                 REQUIRED.  Value MUST be set to "code".
118         client_id
119                 REQUIRED.  The client identifier as described in `Section 2.2`_.
120         redirect_uri
121                 OPTIONAL.  As described in `Section 3.1.2`_.
122         scope
123                 OPTIONAL.  The scope of the access request as described by
124                 `Section 3.3`_.
125         state
126                 RECOMMENDED.  An opaque value used by the client to maintain
127                 state between the request and callback.  The authorization
128                 server includes this value when redirecting the user-agent back
129                 to the client.  The parameter SHOULD be used for preventing
130                 cross-site request forgery as described in `Section 10.12`_.
131
132         The client directs the resource owner to the constructed URI using an
133         HTTP redirection response, or by other means available to it via the
134         user-agent.
135
136         :param request: oauthlib.commong.Request
137         :param token_handler: A token handler instace, for example of type
138                               oauthlib.oauth2.BearerToken.
139         :returns: headers, body, status
140         :raises: FatalClientError on invalid redirect URI or client id.
141                  ValueError if scopes are not set on the request object.
142
143         A few examples::
144
145             >>> from your_validator import your_validator
146             >>> request = Request('https://example.com/authorize?client_id=valid'
147             ...                   '&redirect_uri=http%3A%2F%2Fclient.com%2F')
148             >>> from oauthlib.common import Request
149             >>> from oauthlib.oauth2 import AuthorizationCodeGrant, BearerToken
150             >>> token = BearerToken(your_validator)
151             >>> grant = AuthorizationCodeGrant(your_validator)
152             >>> grant.create_authorization_response(request, token)
153             Traceback (most recent call last):
154                 File "<stdin>", line 1, in <module>
155                 File "oauthlib/oauth2/rfc6749/grant_types.py", line 513, in create_authorization_response
156                     raise ValueError('Scopes must be set on post auth.')
157             ValueError: Scopes must be set on post auth.
158             >>> request.scopes = ['authorized', 'in', 'some', 'form']
159             >>> grant.create_authorization_response(request, token)
160             (u'http://client.com/?error=invalid_request&error_description=Missing+response_type+parameter.', None, None, 400)
161             >>> request = Request('https://example.com/authorize?client_id=valid'
162             ...                   '&redirect_uri=http%3A%2F%2Fclient.com%2F'
163             ...                   '&response_type=code')
164             >>> request.scopes = ['authorized', 'in', 'some', 'form']
165             >>> grant.create_authorization_response(request, token)
166             (u'http://client.com/?code=u3F05aEObJuP2k7DordviIgW5wl52N', None, None, 200)
167             >>> # If the client id or redirect uri fails validation
168             >>> grant.create_authorization_response(request, token)
169             Traceback (most recent call last):
170                 File "<stdin>", line 1, in <module>
171                 File "oauthlib/oauth2/rfc6749/grant_types.py", line 515, in create_authorization_response
172                     >>> grant.create_authorization_response(request, token)
173                 File "oauthlib/oauth2/rfc6749/grant_types.py", line 591, in validate_authorization_request
174             oauthlib.oauth2.rfc6749.errors.InvalidClientIdError
175
176         .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
177         .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
178         .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
179         .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
180         .. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
181         """
182         try:
183             # request.scopes is only mandated in post auth and both pre and
184             # post auth use validate_authorization_request
185             if not request.scopes:
186                 raise ValueError('Scopes must be set on post auth.')
187
188             self.validate_authorization_request(request)
189             log.debug('Pre resource owner authorization validation ok for %r.',
190                       request)
191
192         # If the request fails due to a missing, invalid, or mismatching
193         # redirection URI, or if the client identifier is missing or invalid,
194         # the authorization server SHOULD inform the resource owner of the
195         # error and MUST NOT automatically redirect the user-agent to the
196         # invalid redirection URI.
197         except errors.FatalClientError as e:
198             log.debug('Fatal client error during validation of %r. %r.',
199                       request, e)
200             raise
201
202         # If the resource owner denies the access request or if the request
203         # fails for reasons other than a missing or invalid redirection URI,
204         # the authorization server informs the client by adding the following
205         # parameters to the query component of the redirection URI using the
206         # "application/x-www-form-urlencoded" format, per Appendix B:
207         # http://tools.ietf.org/html/rfc6749#appendix-B
208         except errors.OAuth2Error as e:
209             log.debug('Client error during validation of %r. %r.', request, e)
210             request.redirect_uri = request.redirect_uri or self.error_uri
211             return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples)}, None, 302
212
213         grant = self.create_authorization_code(request)
214         log.debug('Saving grant %r for %r.', grant, request)
215         self.request_validator.save_authorization_code(
216             request.client_id, grant, request)
217         return {'Location': common.add_params_to_uri(request.redirect_uri, grant.items())}, None, 302
218
219     def create_token_response(self, request, token_handler):
220         """Validate the authorization code.
221
222         The client MUST NOT use the authorization code more than once. If an
223         authorization code is used more than once, the authorization server
224         MUST deny the request and SHOULD revoke (when possible) all tokens
225         previously issued based on that authorization code. The authorization
226         code is bound to the client identifier and redirection URI.
227         """
228         headers = {
229             'Content-Type': 'application/json',
230             'Cache-Control': 'no-store',
231             'Pragma': 'no-cache',
232         }
233         try:
234             self.validate_token_request(request)
235             log.debug('Token request validation ok for %r.', request)
236         except errors.OAuth2Error as e:
237             log.debug('Client error during validation of %r. %r.', request, e)
238             return headers, e.json, e.status_code
239
240         token = token_handler.create_token(request, refresh_token=True)
241         self.request_validator.invalidate_authorization_code(
242             request.client_id, request.code, request)
243         return headers, json.dumps(token), 200
244
245     def validate_authorization_request(self, request):
246         """Check the authorization request for normal and fatal errors.
247
248         A normal error could be a missing response_type parameter or the client
249         attempting to access scope it is not allowed to ask authorization for.
250         Normal errors can safely be included in the redirection URI and
251         sent back to the client.
252
253         Fatal errors occur when the client_id or redirect_uri is invalid or
254         missing. These must be caught by the provider and handled, how this
255         is done is outside of the scope of OAuthLib but showing an error
256         page describing the issue is a good idea.
257         """
258
259         # First check for fatal errors
260
261         # If the request fails due to a missing, invalid, or mismatching
262         # redirection URI, or if the client identifier is missing or invalid,
263         # the authorization server SHOULD inform the resource owner of the
264         # error and MUST NOT automatically redirect the user-agent to the
265         # invalid redirection URI.
266
267         # First check duplicate parameters
268         for param in ('client_id', 'response_type', 'redirect_uri', 'scope', 'state'):
269             try:
270                 duplicate_params = request.duplicate_params
271             except ValueError:
272                 raise errors.InvalidRequestFatalError(description='Unable to parse query string', request=request)
273             if param in duplicate_params:
274                 raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request)
275
276         # REQUIRED. The client identifier as described in Section 2.2.
277         # http://tools.ietf.org/html/rfc6749#section-2.2
278         if not request.client_id:
279             raise errors.MissingClientIdError(request=request)
280
281         if not self.request_validator.validate_client_id(request.client_id, request):
282             raise errors.InvalidClientIdError(request=request)
283
284         # OPTIONAL. As described in Section 3.1.2.
285         # http://tools.ietf.org/html/rfc6749#section-3.1.2
286         log.debug('Validating redirection uri %s for client %s.',
287                   request.redirect_uri, request.client_id)
288         if request.redirect_uri is not None:
289             request.using_default_redirect_uri = False
290             log.debug('Using provided redirect_uri %s', request.redirect_uri)
291             if not is_absolute_uri(request.redirect_uri):
292                 raise errors.InvalidRedirectURIError(request=request)
293
294             if not self.request_validator.validate_redirect_uri(
295                     request.client_id, request.redirect_uri, request):
296                 raise errors.MismatchingRedirectURIError(request=request)
297         else:
298             request.redirect_uri = self.request_validator.get_default_redirect_uri(
299                 request.client_id, request)
300             request.using_default_redirect_uri = True
301             log.debug('Using default redirect_uri %s.', request.redirect_uri)
302             if not request.redirect_uri:
303                 raise errors.MissingRedirectURIError(request=request)
304
305         # Then check for normal errors.
306
307         # If the resource owner denies the access request or if the request
308         # fails for reasons other than a missing or invalid redirection URI,
309         # the authorization server informs the client by adding the following
310         # parameters to the query component of the redirection URI using the
311         # "application/x-www-form-urlencoded" format, per Appendix B.
312         # http://tools.ietf.org/html/rfc6749#appendix-B
313
314         # Note that the correct parameters to be added are automatically
315         # populated through the use of specific exceptions.
316
317         # REQUIRED.
318         if request.response_type is None:
319             raise errors.MissingResponseTypeError(request=request)
320         # Value MUST be set to "code".
321         elif request.response_type != 'code':
322             raise errors.UnsupportedResponseTypeError(request=request)
323
324         if not self.request_validator.validate_response_type(request.client_id,
325                                                              request.response_type,
326                                                              request.client, request):
327
328             log.debug('Client %s is not authorized to use response_type %s.',
329                       request.client_id, request.response_type)
330             raise errors.UnauthorizedClientError(request=request)
331
332         # OPTIONAL. The scope of the access request as described by Section 3.3
333         # http://tools.ietf.org/html/rfc6749#section-3.3
334         self.validate_scopes(request)
335
336         return request.scopes, {
337             'client_id': request.client_id,
338             'redirect_uri': request.redirect_uri,
339             'response_type': request.response_type,
340             'state': request.state,
341             'request': request,
342         }
343
344     def validate_token_request(self, request):
345         # REQUIRED. Value MUST be set to "authorization_code".
346         if request.grant_type != 'authorization_code':
347             raise errors.UnsupportedGrantTypeError(request=request)
348
349         if request.code is None:
350             raise errors.InvalidRequestError(
351                 description='Missing code parameter.', request=request)
352
353         for param in ('client_id', 'grant_type', 'redirect_uri'):
354             if param in request.duplicate_params:
355                 raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param,
356                                                  request=request)
357
358         if self.request_validator.client_authentication_required(request):
359             # If the client type is confidential or the client was issued client
360             # credentials (or assigned other authentication requirements), the
361             # client MUST authenticate with the authorization server as described
362             # in Section 3.2.1.
363             # http://tools.ietf.org/html/rfc6749#section-3.2.1
364             if not self.request_validator.authenticate_client(request):
365                 log.debug('Client authentication failed, %r.', request)
366                 raise errors.InvalidClientError(request=request)
367         elif not self.request_validator.authenticate_client_id(request.client_id, request):
368             # REQUIRED, if the client is not authenticating with the
369             # authorization server as described in Section 3.2.1.
370             # http://tools.ietf.org/html/rfc6749#section-3.2.1
371             log.debug('Client authentication failed, %r.', request)
372             raise errors.InvalidClientError(request=request)
373
374         if not hasattr(request.client, 'client_id'):
375             raise NotImplementedError('Authenticate client must set the '
376                                       'request.client.client_id attribute '
377                                       'in authenticate_client.')
378
379         # Ensure client is authorized use of this grant type
380         self.validate_grant_type(request)
381
382         # REQUIRED. The authorization code received from the
383         # authorization server.
384         if not self.request_validator.validate_code(request.client_id,
385                                                     request.code, request.client, request):
386             log.debug('Client, %r (%r), is not allowed access to scopes %r.',
387                       request.client_id, request.client, request.scopes)
388             raise errors.InvalidGrantError(request=request)
389
390         for attr in ('user', 'scopes'):
391             if getattr(request, attr, None) is None:
392                 log.debug('request.%s was not set on code validation.', attr)
393
394         # REQUIRED, if the "redirect_uri" parameter was included in the
395         # authorization request as described in Section 4.1.1, and their
396         # values MUST be identical.
397         if not self.request_validator.confirm_redirect_uri(request.client_id, request.code,
398                                                            request.redirect_uri, request.client):
399             log.debug('Redirect_uri (%r) invalid for client %r (%r).',
400                       request.redirect_uri, request.client_id, request.client)
401             raise errors.AccessDeniedError(request=request)

Benjamin Mako Hill || Want to submit a patch?