123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490 |
- # -*- coding: utf-8 -*-
- """
- oauthlib.oauth2.rfc6749
- ~~~~~~~~~~~~~~~~~~~~~~~
- This module is an implementation of various logic needed
- for consuming OAuth 2.0 RFC6749.
- """
- from __future__ import absolute_import, unicode_literals
- import time
- from oauthlib.common import generate_token
- from oauthlib.oauth2.rfc6749 import tokens
- from oauthlib.oauth2.rfc6749.parameters import parse_token_response
- from oauthlib.oauth2.rfc6749.parameters import prepare_token_request
- from oauthlib.oauth2.rfc6749.parameters import prepare_token_revocation_request
- from oauthlib.oauth2.rfc6749.errors import TokenExpiredError
- from oauthlib.oauth2.rfc6749.errors import InsecureTransportError
- from oauthlib.oauth2.rfc6749.utils import is_secure_transport
- AUTH_HEADER = 'auth_header'
- URI_QUERY = 'query'
- BODY = 'body'
- FORM_ENC_HEADERS = {
- 'Content-Type': 'application/x-www-form-urlencoded'
- }
- class Client(object):
- """Base OAuth2 client responsible for access token management.
- This class also acts as a generic interface providing methods common to all
- client types such as ``prepare_authorization_request`` and
- ``prepare_token_revocation_request``. The ``prepare_x_request`` methods are
- the recommended way of interacting with clients (as opposed to the abstract
- prepare uri/body/etc methods). They are recommended over the older set
- because they are easier to use (more consistent) and add a few additional
- security checks, such as HTTPS and state checking.
- Some of these methods require further implementation only provided by the
- specific purpose clients such as
- :py:class:`oauthlib.oauth2.MobileApplicationClient` and thus you should always
- seek to use the client class matching the OAuth workflow you need. For
- Python, this is usually :py:class:`oauthlib.oauth2.WebApplicationClient`.
- """
- def __init__(self, client_id,
- default_token_placement=AUTH_HEADER,
- token_type='Bearer',
- access_token=None,
- refresh_token=None,
- mac_key=None,
- mac_algorithm=None,
- token=None,
- scope=None,
- state=None,
- redirect_url=None,
- state_generator=generate_token,
- **kwargs):
- """Initialize a client with commonly used attributes.
- :param client_id: Client identifier given by the OAuth provider upon
- registration.
- :param default_token_placement: Tokens can be supplied in the Authorization
- header (default), the URL query component (``query``) or the request
- body (``body``).
- :param token_type: OAuth 2 token type. Defaults to Bearer. Change this
- if you specify the ``access_token`` parameter and know it is of a
- different token type, such as a MAC, JWT or SAML token. Can
- also be supplied as ``token_type`` inside the ``token`` dict parameter.
- :param access_token: An access token (string) used to authenticate
- requests to protected resources. Can also be supplied inside the
- ``token`` dict parameter.
- :param refresh_token: A refresh token (string) used to refresh expired
- tokens. Can also be supplied inside the ``token`` dict parameter.
- :param mac_key: Encryption key used with MAC tokens.
- :param mac_algorithm: Hashing algorithm for MAC tokens.
- :param token: A dict of token attributes such as ``access_token``,
- ``token_type`` and ``expires_at``.
- :param scope: A list of default scopes to request authorization for.
- :param state: A CSRF protection string used during authorization.
- :param redirect_url: The redirection endpoint on the client side to which
- the user returns after authorization.
- :param state_generator: A no argument state generation callable. Defaults
- to :py:meth:`oauthlib.common.generate_token`.
- """
- self.client_id = client_id
- self.default_token_placement = default_token_placement
- self.token_type = token_type
- self.access_token = access_token
- self.refresh_token = refresh_token
- self.mac_key = mac_key
- self.mac_algorithm = mac_algorithm
- self.token = token or {}
- self.scope = scope
- self.state_generator = state_generator
- self.state = state
- self.redirect_url = redirect_url
- self._expires_at = None
- self._populate_attributes(self.token)
- @property
- def token_types(self):
- """Supported token types and their respective methods
- Additional tokens can be supported by extending this dictionary.
- The Bearer token spec is stable and safe to use.
- The MAC token spec is not yet stable and support for MAC tokens
- is experimental and currently matching version 00 of the spec.
- """
- return {
- 'Bearer': self._add_bearer_token,
- 'MAC': self._add_mac_token
- }
- def prepare_request_uri(self, *args, **kwargs):
- """Abstract method used to create request URIs."""
- raise NotImplementedError("Must be implemented by inheriting classes.")
- def prepare_request_body(self, *args, **kwargs):
- """Abstract method used to create request bodies."""
- raise NotImplementedError("Must be implemented by inheriting classes.")
- def parse_request_uri_response(self, *args, **kwargs):
- """Abstract method used to parse redirection responses."""
- def add_token(self, uri, http_method='GET', body=None, headers=None,
- token_placement=None, **kwargs):
- """Add token to the request uri, body or authorization header.
- The access token type provides the client with the information
- required to successfully utilize the access token to make a protected
- resource request (along with type-specific attributes). The client
- MUST NOT use an access token if it does not understand the token
- type.
- For example, the "bearer" token type defined in
- [`I-D.ietf-oauth-v2-bearer`_] is utilized by simply including the access
- token string in the request:
- .. code-block:: http
- GET /resource/1 HTTP/1.1
- Host: example.com
- Authorization: Bearer mF_9.B5f-4.1JqM
- while the "mac" token type defined in [`I-D.ietf-oauth-v2-http-mac`_] is
- utilized by issuing a MAC key together with the access token which is
- used to sign certain components of the HTTP requests:
- .. code-block:: http
- GET /resource/1 HTTP/1.1
- Host: example.com
- Authorization: MAC id="h480djs93hd8",
- nonce="274312:dj83hs9s",
- mac="kDZvddkndxvhGRXZhvuDjEWhGeE="
- .. _`I-D.ietf-oauth-v2-bearer`: http://tools.ietf.org/html/rfc6749#section-12.2
- .. _`I-D.ietf-oauth-v2-http-mac`: http://tools.ietf.org/html/rfc6749#section-12.2
- """
- if not is_secure_transport(uri):
- raise InsecureTransportError()
- token_placement = token_placement or self.default_token_placement
- case_insensitive_token_types = dict(
- (k.lower(), v) for k, v in self.token_types.items())
- if not self.token_type.lower() in case_insensitive_token_types:
- raise ValueError("Unsupported token type: %s" % self.token_type)
- if not self.access_token:
- raise ValueError("Missing access token.")
- if self._expires_at and self._expires_at < time.time():
- raise TokenExpiredError()
- return case_insensitive_token_types[self.token_type.lower()](uri, http_method, body,
- headers, token_placement, **kwargs)
- def prepare_authorization_request(self, authorization_url, state=None,
- redirect_url=None, scope=None, **kwargs):
- """Prepare the authorization request.
- This is the first step in many OAuth flows in which the user is
- redirected to a certain authorization URL. This method adds
- required parameters to the authorization URL.
- :param authorization_url: Provider authorization endpoint URL.
- :param state: CSRF protection string. Will be automatically created if
- not provided. The generated state is available via the ``state``
- attribute. Clients should verify that the state is unchanged and
- present in the authorization response. This verification is done
- automatically if using the ``authorization_response`` parameter
- with ``prepare_token_request``.
- :param redirect_url: Redirect URL to which the user will be returned
- after authorization. Must be provided unless previously setup with
- the provider. If provided then it must also be provided in the
- token request.
- :param kwargs: Additional parameters to included in the request.
- :returns: The prepared request tuple with (url, headers, body).
- """
- if not is_secure_transport(authorization_url):
- raise InsecureTransportError()
- self.state = state or self.state_generator()
- self.redirect_url = redirect_url or self.redirect_url
- self.scope = scope or self.scope
- auth_url = self.prepare_request_uri(
- authorization_url, redirect_uri=self.redirect_url,
- scope=self.scope, state=self.state, **kwargs)
- return auth_url, FORM_ENC_HEADERS, ''
- def prepare_token_request(self, token_url, authorization_response=None,
- redirect_url=None, state=None, body='', **kwargs):
- """Prepare a token creation request.
- Note that these requests usually require client authentication, either
- by including client_id or a set of provider specific authentication
- credentials.
- :param token_url: Provider token creation endpoint URL.
- :param authorization_response: The full redirection URL string, i.e.
- the location to which the user was redirected after successfull
- authorization. Used to mine credentials needed to obtain a token
- in this step, such as authorization code.
- :param redirect_url: The redirect_url supplied with the authorization
- request (if there was one).
- :param body: Request body (URL encoded string).
- :param kwargs: Additional parameters to included in the request.
- :returns: The prepared request tuple with (url, headers, body).
- """
- if not is_secure_transport(token_url):
- raise InsecureTransportError()
- state = state or self.state
- if authorization_response:
- self.parse_request_uri_response(
- authorization_response, state=state)
- self.redirect_url = redirect_url or self.redirect_url
- body = self.prepare_request_body(body=body,
- redirect_uri=self.redirect_url, **kwargs)
- return token_url, FORM_ENC_HEADERS, body
- def prepare_refresh_token_request(self, token_url, refresh_token=None,
- body='', scope=None, **kwargs):
- """Prepare an access token refresh request.
- Expired access tokens can be replaced by new access tokens without
- going through the OAuth dance if the client obtained a refresh token.
- This refresh token and authentication credentials can be used to
- obtain a new access token, and possibly a new refresh token.
- :param token_url: Provider token refresh endpoint URL.
- :param refresh_token: Refresh token string.
- :param body: Request body (URL encoded string).
- :param scope: List of scopes to request. Must be equal to
- or a subset of the scopes granted when obtaining the refresh
- token.
- :param kwargs: Additional parameters to included in the request.
- :returns: The prepared request tuple with (url, headers, body).
- """
- if not is_secure_transport(token_url):
- raise InsecureTransportError()
- self.scope = scope or self.scope
- body = self.prepare_refresh_body(body=body,
- refresh_token=refresh_token, scope=self.scope, **kwargs)
- return token_url, FORM_ENC_HEADERS, body
- def prepare_token_revocation_request(self, revocation_url, token,
- token_type_hint="access_token", body='', callback=None, **kwargs):
- """Prepare a token revocation request.
- :param revocation_url: Provider token revocation endpoint URL.
- :param token: The access or refresh token to be revoked (string).
- :param token_type_hint: ``"access_token"`` (default) or
- ``"refresh_token"``. This is optional and if you wish to not pass it you
- must provide ``token_type_hint=None``.
- :param callback: A jsonp callback such as ``package.callback`` to be invoked
- upon receiving the response. Not that it should not include a () suffix.
- :param kwargs: Additional parameters to included in the request.
- :returns: The prepared request tuple with (url, headers, body).
- Note that JSONP request may use GET requests as the parameters will
- be added to the request URL query as opposed to the request body.
- An example of a revocation request
- .. code-block: http
- POST /revoke HTTP/1.1
- Host: server.example.com
- Content-Type: application/x-www-form-urlencoded
- Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
- token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token
- An example of a jsonp revocation request
- .. code-block: http
- GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1
- Host: server.example.com
- Content-Type: application/x-www-form-urlencoded
- Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
- and an error response
- .. code-block: http
- package.myCallback({"error":"unsupported_token_type"});
- Note that these requests usually require client credentials, client_id in
- the case for public clients and provider specific authentication
- credentials for confidential clients.
- """
- if not is_secure_transport(revocation_url):
- raise InsecureTransportError()
- return prepare_token_revocation_request(revocation_url, token,
- token_type_hint=token_type_hint, body=body, callback=callback,
- **kwargs)
- def parse_request_body_response(self, body, scope=None, **kwargs):
- """Parse the JSON response body.
- If the access token request is valid and authorized, the
- authorization server issues an access token as described in
- `Section 5.1`_. A refresh token SHOULD NOT be included. If the request
- failed client authentication or is invalid, the authorization server
- returns an error response as described in `Section 5.2`_.
- :param body: The response body from the token request.
- :param scope: Scopes originally requested.
- :return: Dictionary of token parameters.
- :raises: Warning if scope has changed. OAuth2Error if response is invalid.
- These response are json encoded and could easily be parsed without
- the assistance of OAuthLib. However, there are a few subtle issues
- to be aware of regarding the response which are helpfully addressed
- through the raising of various errors.
- A successful response should always contain
- **access_token**
- The access token issued by the authorization server. Often
- a random string.
- **token_type**
- The type of the token issued as described in `Section 7.1`_.
- Commonly ``Bearer``.
- While it is not mandated it is recommended that the provider include
- **expires_in**
- The lifetime in seconds of the access token. For
- example, the value "3600" denotes that the access token will
- expire in one hour from the time the response was generated.
- If omitted, the authorization server SHOULD provide the
- expiration time via other means or document the default value.
- **scope**
- Providers may supply this in all responses but are required to only
- if it has changed since the authorization request.
- .. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
- .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
- .. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
- """
- self.token = parse_token_response(body, scope=scope)
- self._populate_attributes(self.token)
- return self.token
- def prepare_refresh_body(self, body='', refresh_token=None, scope=None, **kwargs):
- """Prepare an access token request, using a refresh token.
- If the authorization server issued a refresh token to the client, the
- client makes a refresh request to the token endpoint by adding the
- following parameters using the "application/x-www-form-urlencoded"
- format in the HTTP request entity-body:
- grant_type
- REQUIRED. Value MUST be set to "refresh_token".
- refresh_token
- REQUIRED. The refresh token issued to the client.
- scope
- OPTIONAL. The scope of the access request as described by
- Section 3.3. The requested scope MUST NOT include any scope
- not originally granted by the resource owner, and if omitted is
- treated as equal to the scope originally granted by the
- resource owner.
- """
- refresh_token = refresh_token or self.refresh_token
- return prepare_token_request('refresh_token', body=body, scope=scope,
- refresh_token=refresh_token, **kwargs)
- def _add_bearer_token(self, uri, http_method='GET', body=None,
- headers=None, token_placement=None):
- """Add a bearer token to the request uri, body or authorization header."""
- if token_placement == AUTH_HEADER:
- headers = tokens.prepare_bearer_headers(self.access_token, headers)
- elif token_placement == URI_QUERY:
- uri = tokens.prepare_bearer_uri(self.access_token, uri)
- elif token_placement == BODY:
- body = tokens.prepare_bearer_body(self.access_token, body)
- else:
- raise ValueError("Invalid token placement.")
- return uri, headers, body
- def _add_mac_token(self, uri, http_method='GET', body=None,
- headers=None, token_placement=AUTH_HEADER, ext=None, **kwargs):
- """Add a MAC token to the request authorization header.
- Warning: MAC token support is experimental as the spec is not yet stable.
- """
- headers = tokens.prepare_mac_header(self.access_token, uri,
- self.mac_key, http_method, headers=headers, body=body, ext=ext,
- hash_algorithm=self.mac_algorithm, **kwargs)
- return uri, headers, body
- def _populate_attributes(self, response):
- """Add commonly used values such as access_token to self."""
- if 'access_token' in response:
- self.access_token = response.get('access_token')
- if 'refresh_token' in response:
- self.refresh_token = response.get('refresh_token')
- if 'token_type' in response:
- self.token_type = response.get('token_type')
- if 'expires_in' in response:
- self.expires_in = response.get('expires_in')
- self._expires_at = time.time() + int(self.expires_in)
- if 'expires_at' in response:
- self._expires_at = int(response.get('expires_at'))
- if 'code' in response:
- self.code = response.get('code')
- if 'mac_key' in response:
- self.mac_key = response.get('mac_key')
- if 'mac_algorithm' in response:
- self.mac_algorithm = response.get('mac_algorithm')
|