1 # -*- coding: utf-8 -*-
3 oauthlib.oauth2.rfc6749
4 ~~~~~~~~~~~~~~~~~~~~~~~
6 This module is an implementation of various logic needed
7 for consuming OAuth 2.0 RFC6749.
9 from __future__ import absolute_import, unicode_literals
13 from oauthlib.common import generate_token
14 from oauthlib.oauth2.rfc6749 import tokens
15 from oauthlib.oauth2.rfc6749.parameters import parse_token_response
16 from oauthlib.oauth2.rfc6749.parameters import prepare_token_request
17 from oauthlib.oauth2.rfc6749.parameters import prepare_token_revocation_request
18 from oauthlib.oauth2.rfc6749.errors import TokenExpiredError
19 from oauthlib.oauth2.rfc6749.errors import InsecureTransportError
20 from oauthlib.oauth2.rfc6749.utils import is_secure_transport
23 AUTH_HEADER = 'auth_header'
28 'Content-Type': 'application/x-www-form-urlencoded'
33 """Base OAuth2 client responsible for access token management.
35 This class also acts as a generic interface providing methods common to all
36 client types such as ``prepare_authorization_request`` and
37 ``prepare_token_revocation_request``. The ``prepare_x_request`` methods are
38 the recommended way of interacting with clients (as opposed to the abstract
39 prepare uri/body/etc methods). They are recommended over the older set
40 because they are easier to use (more consistent) and add a few additional
41 security checks, such as HTTPS and state checking.
43 Some of these methods require further implementation only provided by the
44 specific purpose clients such as
45 :py:class:`oauthlib.oauth2.MobileApplicationClient` and thus you should always
46 seek to use the client class matching the OAuth workflow you need. For
47 Python, this is usually :py:class:`oauthlib.oauth2.WebApplicationClient`.
51 def __init__(self, client_id,
52 default_token_placement=AUTH_HEADER,
62 state_generator=generate_token,
64 """Initialize a client with commonly used attributes.
66 :param client_id: Client identifier given by the OAuth provider upon
69 :param default_token_placement: Tokens can be supplied in the Authorization
70 header (default), the URL query component (``query``) or the request
73 :param token_type: OAuth 2 token type. Defaults to Bearer. Change this
74 if you specify the ``access_token`` parameter and know it is of a
75 different token type, such as a MAC, JWT or SAML token. Can
76 also be supplied as ``token_type`` inside the ``token`` dict parameter.
78 :param access_token: An access token (string) used to authenticate
79 requests to protected resources. Can also be supplied inside the
80 ``token`` dict parameter.
82 :param refresh_token: A refresh token (string) used to refresh expired
83 tokens. Can also be supplide inside the ``token`` dict parameter.
85 :param mac_key: Encryption key used with MAC tokens.
87 :param mac_algorithm: Hashing algorithm for MAC tokens.
89 :param token: A dict of token attributes such as ``access_token``,
90 ``token_type`` and ``expires_at``.
92 :param scope: A list of default scopes to request authorization for.
94 :param state: A CSRF protection string used during authorization.
96 :param redirect_url: The redirection endpoint on the client side to which
97 the user returns after authorization.
99 :param state_generator: A no argument state generation callable. Defaults
100 to :py:meth:`oauthlib.common.generate_token`.
103 self.client_id = client_id
104 self.default_token_placement = default_token_placement
105 self.token_type = token_type
106 self.access_token = access_token
107 self.refresh_token = refresh_token
108 self.mac_key = mac_key
109 self.mac_algorithm = mac_algorithm
110 self.token = token or {}
112 self.state_generator = state_generator
114 self.redirect_url = redirect_url
115 self._expires_at = None
116 self._populate_attributes(self.token)
119 def token_types(self):
120 """Supported token types and their respective methods
122 Additional tokens can be supported by extending this dictionary.
124 The Bearer token spec is stable and safe to use.
126 The MAC token spec is not yet stable and support for MAC tokens
127 is experimental and currently matching version 00 of the spec.
130 'Bearer': self._add_bearer_token,
131 'MAC': self._add_mac_token
134 def prepare_request_uri(self, *args, **kwargs):
135 """Abstract method used to create request URIs."""
136 raise NotImplementedError("Must be implemented by inheriting classes.")
138 def prepare_request_body(self, *args, **kwargs):
139 """Abstract method used to create request bodies."""
140 raise NotImplementedError("Must be implemented by inheriting classes.")
142 def parse_request_uri_response(self, *args, **kwargs):
143 """Abstract method used to parse redirection responses."""
145 def add_token(self, uri, http_method='GET', body=None, headers=None,
146 token_placement=None, **kwargs):
147 """Add token to the request uri, body or authorization header.
149 The access token type provides the client with the information
150 required to successfully utilize the access token to make a protected
151 resource request (along with type-specific attributes). The client
152 MUST NOT use an access token if it does not understand the token
155 For example, the "bearer" token type defined in
156 [`I-D.ietf-oauth-v2-bearer`_] is utilized by simply including the access
157 token string in the request:
161 GET /resource/1 HTTP/1.1
163 Authorization: Bearer mF_9.B5f-4.1JqM
165 while the "mac" token type defined in [`I-D.ietf-oauth-v2-http-mac`_] is
166 utilized by issuing a MAC key together with the access token which is
167 used to sign certain components of the HTTP requests:
171 GET /resource/1 HTTP/1.1
173 Authorization: MAC id="h480djs93hd8",
174 nonce="274312:dj83hs9s",
175 mac="kDZvddkndxvhGRXZhvuDjEWhGeE="
177 .. _`I-D.ietf-oauth-v2-bearer`: http://tools.ietf.org/html/rfc6749#section-12.2
178 .. _`I-D.ietf-oauth-v2-http-mac`: http://tools.ietf.org/html/rfc6749#section-12.2
180 if not is_secure_transport(uri):
181 raise InsecureTransportError()
183 token_placement = token_placement or self.default_token_placement
185 case_insensitive_token_types = dict(
186 (k.lower(), v) for k, v in self.token_types.items())
187 if not self.token_type.lower() in case_insensitive_token_types:
188 raise ValueError("Unsupported token type: %s" % self.token_type)
190 if not self.access_token:
191 raise ValueError("Missing access token.")
193 if self._expires_at and self._expires_at < time.time():
194 raise TokenExpiredError()
196 return case_insensitive_token_types[self.token_type.lower()](uri, http_method, body,
197 headers, token_placement, **kwargs)
199 def prepare_authorization_request(self, authorization_url, state=None,
200 redirect_url=None, scope=None, **kwargs):
201 """Prepare the authorization request.
203 This is the first step in many OAuth flows in which the user is
204 redirected to a certain authorization URL. This method adds
205 required parameters to the authorization URL.
207 :param authorization_url: Provider authorization endpoint URL.
209 :param state: CSRF protection string. Will be automatically created if
210 not provided. The generated state is available via the ``state``
211 attribute. Clients should verify that the state is unchanged and
212 present in the authorization response. This verification is done
213 automatically if using the ``authorization_response`` parameter
214 with ``prepare_token_request``.
216 :param redirect_url: Redirect URL to which the user will be returned
217 after authorization. Must be provided unless previously setup with
218 the provider. If provided then it must also be provided in the
221 :param kwargs: Additional parameters to included in the request.
223 :returns: The prepared request tuple with (url, headers, body).
225 if not is_secure_transport(authorization_url):
226 raise InsecureTransportError()
228 self.state = state or self.state_generator()
229 self.redirect_url = redirect_url or self.redirect_url
230 self.scope = scope or self.scope
231 auth_url = self.prepare_request_uri(
232 authorization_url, redirect_uri=self.redirect_uri,
233 scope=self.scope, state=self.state, **kwargs)
234 return auth_url, FORM_ENC_HEADERS, ''
236 def prepare_token_request(self, token_url, authorization_response=None,
237 redirect_url=None, state=None, body='', **kwargs):
238 """Prepare a token creation request.
240 Note that these requests usually require client authentication, either
241 by including client_id or a set of provider specific authentication
244 :param token_url: Provider token creation endpoint URL.
246 :param authorization_response: The full redirection URL string, i.e.
247 the location to which the user was redirected after successfull
248 authorization. Used to mine credentials needed to obtain a token
249 in this step, such as authorization code.
251 :param redirect_url: The redirect_url supplied with the authorization
252 request (if there was one).
254 :param body: Request body (URL encoded string).
256 :param kwargs: Additional parameters to included in the request.
258 :returns: The prepared request tuple with (url, headers, body).
260 if not is_secure_transport(token_url):
261 raise InsecureTransportError()
263 state = state or self.state
264 if authorization_response:
265 self.parse_request_uri_response(
266 authorization_response, state=state)
267 self.redirect_url = redirect_url or self.redirect_url
268 body = self.prepare_request_body(body=body,
269 redirect_uri=self.redirect_url, **kwargs)
271 return token_url, FORM_ENC_HEADERS, body
273 def prepare_refresh_token_request(self, token_url, refresh_token=None,
274 body='', scope=None, **kwargs):
275 """Prepare an access token refresh request.
277 Expired access tokens can be replaced by new access tokens without
278 going through the OAuth dance if the client obtained a refresh token.
279 This refresh token and authentication credentials can be used to
280 obtain a new access token, and possibly a new refresh token.
282 :param token_url: Provider token refresh endpoint URL.
284 :param refresh_token: Refresh token string.
286 :param body: Request body (URL encoded string).
288 :param scope: List of scopes to request. Must be equal to
289 or a subset of the scopes granted when obtaining the refresh
292 :param kwargs: Additional parameters to included in the request.
294 :returns: The prepared request tuple with (url, headers, body).
296 if not is_secure_transport(token_url):
297 raise InsecureTransportError()
299 self.scope = scope or self.scope
300 body = self._client.prepare_refresh_body(body=body,
301 refresh_token=refresh_token, scope=self.scope, **kwargs)
302 return token_url, FORM_ENC_HEADERS, body
304 def prepare_token_revocation_request(self, revocation_url, token,
305 token_type_hint="access_token", body='', callback=None, **kwargs):
306 """Prepare a token revocation request.
308 :param revocation_url: Provider token revocation endpoint URL.
310 :param token: The access or refresh token to be revoked (string).
312 :param token_type_hint: ``"access_token"`` (default) or
313 ``"refresh_token"``. This is optional and if you wish to not pass it you
314 must provide ``token_type_hint=None``.
316 :param callback: A jsonp callback such as ``package.callback`` to be invoked
317 upon receiving the response. Not that it should not include a () suffix.
319 :param kwargs: Additional parameters to included in the request.
321 :returns: The prepared request tuple with (url, headers, body).
323 Note that JSONP request may use GET requests as the parameters will
324 be added to the request URL query as opposed to the request body.
326 An example of a revocation request
330 POST /revoke HTTP/1.1
331 Host: server.example.com
332 Content-Type: application/x-www-form-urlencoded
333 Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
335 token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token
337 An example of a jsonp revocation request
341 GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1
342 Host: server.example.com
343 Content-Type: application/x-www-form-urlencoded
344 Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
346 and an error response
350 package.myCallback({"error":"unsupported_token_type"});
352 Note that these requests usually require client credentials, client_id in
353 the case for public clients and provider specific authentication
354 credentials for confidential clients.
356 if not is_secure_transport(revocation_url):
357 raise InsecureTransportError()
359 return prepare_token_revocation_request(revocation_url, token,
360 token_type_hint=token_type_hint, body=body, callback=callback,
363 def parse_request_body_response(self, body, scope=None, **kwargs):
364 """Parse the JSON response body.
366 If the access token request is valid and authorized, the
367 authorization server issues an access token as described in
368 `Section 5.1`_. A refresh token SHOULD NOT be included. If the request
369 failed client authentication or is invalid, the authorization server
370 returns an error response as described in `Section 5.2`_.
372 :param body: The response body from the token request.
373 :param scope: Scopes originally requested.
374 :return: Dictionary of token parameters.
375 :raises: Warning if scope has changed. OAuth2Error if response is invalid.
377 These response are json encoded and could easily be parsed without
378 the assistance of OAuthLib. However, there are a few subtle issues
379 to be aware of regarding the response which are helpfully addressed
380 through the raising of various errors.
382 A successful response should always contain
385 The access token issued by the authorization server. Often
389 The type of the token issued as described in `Section 7.1`_.
392 While it is not mandated it is recommended that the provider include
395 The lifetime in seconds of the access token. For
396 example, the value "3600" denotes that the access token will
397 expire in one hour from the time the response was generated.
398 If omitted, the authorization server SHOULD provide the
399 expiration time via other means or document the default value.
402 Providers may supply this in all responses but are required to only
403 if it has changed since the authorization request.
405 .. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
406 .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
407 .. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
409 self.token = parse_token_response(body, scope=scope)
410 self._populate_attributes(self.token)
413 def prepare_refresh_body(self, body='', refresh_token=None, scope=None, **kwargs):
414 """Prepare an access token request, using a refresh token.
416 If the authorization server issued a refresh token to the client, the
417 client makes a refresh request to the token endpoint by adding the
418 following parameters using the "application/x-www-form-urlencoded"
419 format in the HTTP request entity-body:
422 REQUIRED. Value MUST be set to "refresh_token".
424 REQUIRED. The refresh token issued to the client.
426 OPTIONAL. The scope of the access request as described by
427 Section 3.3. The requested scope MUST NOT include any scope
428 not originally granted by the resource owner, and if omitted is
429 treated as equal to the scope originally granted by the
432 refresh_token = refresh_token or self.refresh_token
433 return prepare_token_request('refresh_token', body=body, scope=scope,
434 refresh_token=refresh_token, **kwargs)
436 def _add_bearer_token(self, uri, http_method='GET', body=None,
437 headers=None, token_placement=None):
438 """Add a bearer token to the request uri, body or authorization header."""
439 if token_placement == AUTH_HEADER:
440 headers = tokens.prepare_bearer_headers(self.access_token, headers)
442 elif token_placement == URI_QUERY:
443 uri = tokens.prepare_bearer_uri(self.access_token, uri)
445 elif token_placement == BODY:
446 body = tokens.prepare_bearer_body(self.access_token, body)
449 raise ValueError("Invalid token placement.")
450 return uri, headers, body
452 def _add_mac_token(self, uri, http_method='GET', body=None,
453 headers=None, token_placement=AUTH_HEADER, ext=None, **kwargs):
454 """Add a MAC token to the request authorization header.
456 Warning: MAC token support is experimental as the spec is not yet stable.
458 headers = tokens.prepare_mac_header(self.access_token, uri,
459 self.mac_key, http_method, headers=headers, body=body, ext=ext,
460 hash_algorithm=self.mac_algorithm, **kwargs)
461 return uri, headers, body
463 def _populate_attributes(self, response):
464 """Add commonly used values such as access_token to self."""
466 if 'access_token' in response:
467 self.access_token = response.get('access_token')
469 if 'refresh_token' in response:
470 self.refresh_token = response.get('refresh_token')
472 if 'token_type' in response:
473 self.token_type = response.get('token_type')
475 if 'expires_in' in response:
476 self.expires_in = response.get('expires_in')
477 self._expires_at = time.time() + int(self.expires_in)
479 if 'expires_at' in response:
480 self._expires_at = int(response.get('expires_at'))
482 if 'code' in response:
483 self.code = response.get('code')
485 if 'mac_key' in response:
486 self.mac_key = response.get('mac_key')
488 if 'mac_algorithm' in response:
489 self.mac_algorithm = response.get('mac_algorithm')