mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-14 17:22:56 -07:00
Include oauthlib module
This commit is contained in:
parent
363d1b07ca
commit
06b684c899
48 changed files with 8620 additions and 704 deletions
34
lib/oauthlib/oauth2/__init__.py
Normal file
34
lib/oauthlib/oauth2/__init__.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module is a wrapper for the most recent implementation of OAuth 2.0 Client
|
||||
and Server classes.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from .rfc6749.clients import Client
|
||||
from .rfc6749.clients import WebApplicationClient
|
||||
from .rfc6749.clients import MobileApplicationClient
|
||||
from .rfc6749.clients import LegacyApplicationClient
|
||||
from .rfc6749.clients import BackendApplicationClient
|
||||
from .rfc6749.clients import ServiceApplicationClient
|
||||
from .rfc6749.endpoints import AuthorizationEndpoint
|
||||
from .rfc6749.endpoints import TokenEndpoint
|
||||
from .rfc6749.endpoints import ResourceEndpoint
|
||||
from .rfc6749.endpoints import RevocationEndpoint
|
||||
from .rfc6749.endpoints import Server
|
||||
from .rfc6749.endpoints import WebApplicationServer
|
||||
from .rfc6749.endpoints import MobileApplicationServer
|
||||
from .rfc6749.endpoints import LegacyApplicationServer
|
||||
from .rfc6749.endpoints import BackendApplicationServer
|
||||
from .rfc6749.errors import *
|
||||
from .rfc6749.grant_types import AuthorizationCodeGrant
|
||||
from .rfc6749.grant_types import ImplicitGrant
|
||||
from .rfc6749.grant_types import ResourceOwnerPasswordCredentialsGrant
|
||||
from .rfc6749.grant_types import ClientCredentialsGrant
|
||||
from .rfc6749.grant_types import RefreshTokenGrant
|
||||
from .rfc6749.request_validator import RequestValidator
|
||||
from .rfc6749.tokens import BearerToken, OAuth2Token
|
||||
from .rfc6749.utils import is_secure_transport
|
66
lib/oauthlib/oauth2/rfc6749/__init__.py
Normal file
66
lib/oauthlib/oauth2/rfc6749/__init__.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import functools
|
||||
import logging
|
||||
|
||||
from .errors import TemporarilyUnavailableError, ServerError
|
||||
from .errors import FatalClientError, OAuth2Error
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseEndpoint(object):
|
||||
|
||||
def __init__(self):
|
||||
self._available = True
|
||||
self._catch_errors = False
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
return self._available
|
||||
|
||||
@available.setter
|
||||
def available(self, available):
|
||||
self._available = available
|
||||
|
||||
@property
|
||||
def catch_errors(self):
|
||||
return self._catch_errors
|
||||
|
||||
@catch_errors.setter
|
||||
def catch_errors(self, catch_errors):
|
||||
self._catch_errors = catch_errors
|
||||
|
||||
|
||||
def catch_errors_and_unavailability(f):
|
||||
@functools.wraps(f)
|
||||
def wrapper(endpoint, uri, *args, **kwargs):
|
||||
if not endpoint.available:
|
||||
e = TemporarilyUnavailableError()
|
||||
log.info('Endpoint unavailable, ignoring request %s.' % uri)
|
||||
return {}, e.json, 503
|
||||
|
||||
if endpoint.catch_errors:
|
||||
try:
|
||||
return f(endpoint, uri, *args, **kwargs)
|
||||
except OAuth2Error:
|
||||
raise
|
||||
except FatalClientError:
|
||||
raise
|
||||
except Exception as e:
|
||||
error = ServerError()
|
||||
log.warning(
|
||||
'Exception caught while processing request, %s.' % e)
|
||||
return {}, error.json, 500
|
||||
else:
|
||||
return f(endpoint, uri, *args, **kwargs)
|
||||
return wrapper
|
16
lib/oauthlib/oauth2/rfc6749/clients/__init__.py
Normal file
16
lib/oauthlib/oauth2/rfc6749/clients/__init__.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
# -*- 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
|
||||
|
||||
from .base import *
|
||||
from .web_application import WebApplicationClient
|
||||
from .mobile_application import MobileApplicationClient
|
||||
from .legacy_application import LegacyApplicationClient
|
||||
from .backend_application import BackendApplicationClient
|
||||
from .service_application import ServiceApplicationClient
|
61
lib/oauthlib/oauth2/rfc6749/clients/backend_application.py
Normal file
61
lib/oauthlib/oauth2/rfc6749/clients/backend_application.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from .base import Client
|
||||
from ..parameters import prepare_token_request
|
||||
from ..parameters import parse_token_response
|
||||
|
||||
|
||||
class BackendApplicationClient(Client):
|
||||
|
||||
"""A public client utilizing the client credentials grant workflow.
|
||||
|
||||
The client can request an access token using only its client
|
||||
credentials (or other supported means of authentication) when the
|
||||
client is requesting access to the protected resources under its
|
||||
control, or those of another resource owner which has been previously
|
||||
arranged with the authorization server (the method of which is beyond
|
||||
the scope of this specification).
|
||||
|
||||
The client credentials grant type MUST only be used by confidential
|
||||
clients.
|
||||
|
||||
Since the client authentication is used as the authorization grant,
|
||||
no additional authorization request is needed.
|
||||
"""
|
||||
|
||||
def prepare_request_body(self, body='', scope=None, **kwargs):
|
||||
"""Add the client credentials to the request body.
|
||||
|
||||
The client makes a request to the token endpoint by adding the
|
||||
following parameters using the "application/x-www-form-urlencoded"
|
||||
format per `Appendix B`_ in the HTTP request entity-body:
|
||||
|
||||
:param scope: The scope of the access request as described by
|
||||
`Section 3.3`_.
|
||||
:param kwargs: Extra credentials to include in the token request.
|
||||
|
||||
The client MUST authenticate with the authorization server as
|
||||
described in `Section 3.2.1`_.
|
||||
|
||||
The prepared body will include all provided credentials as well as
|
||||
the ``grant_type`` parameter set to ``client_credentials``::
|
||||
|
||||
>>> from oauthlib.oauth2 import BackendApplicationClient
|
||||
>>> client = BackendApplicationClient('your_id')
|
||||
>>> client.prepare_request_body(scope=['hello', 'world'])
|
||||
'grant_type=client_credentials&scope=hello+world'
|
||||
|
||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
.. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
|
||||
"""
|
||||
return prepare_token_request('client_credentials', body=body,
|
||||
scope=scope, **kwargs)
|
490
lib/oauthlib/oauth2/rfc6749/clients/base.py
Normal file
490
lib/oauthlib/oauth2/rfc6749/clients/base.py
Normal file
|
@ -0,0 +1,490 @@
|
|||
# -*- 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 supplide 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')
|
||||
|
73
lib/oauthlib/oauth2/rfc6749/clients/legacy_application.py
Normal file
73
lib/oauthlib/oauth2/rfc6749/clients/legacy_application.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from .base import Client
|
||||
from ..parameters import prepare_token_request
|
||||
from ..parameters import parse_token_response
|
||||
|
||||
|
||||
class LegacyApplicationClient(Client):
|
||||
|
||||
"""A public client using the resource owner password and username directly.
|
||||
|
||||
The resource owner password credentials grant type is suitable in
|
||||
cases where the resource owner has a trust relationship with the
|
||||
client, such as the device operating system or a highly privileged
|
||||
application. The authorization server should take special care when
|
||||
enabling this grant type, and only allow it when other flows are not
|
||||
viable.
|
||||
|
||||
The grant type is suitable for clients capable of obtaining the
|
||||
resource owner's credentials (username and password, typically using
|
||||
an interactive form). It is also used to migrate existing clients
|
||||
using direct authentication schemes such as HTTP Basic or Digest
|
||||
authentication to OAuth by converting the stored credentials to an
|
||||
access token.
|
||||
|
||||
The method through which the client obtains the resource owner
|
||||
credentials is beyond the scope of this specification. The client
|
||||
MUST discard the credentials once an access token has been obtained.
|
||||
"""
|
||||
|
||||
def __init__(self, client_id, **kwargs):
|
||||
super(LegacyApplicationClient, self).__init__(client_id, **kwargs)
|
||||
|
||||
def prepare_request_body(self, username, password, body='', scope=None, **kwargs):
|
||||
"""Add the resource owner password and username to the request body.
|
||||
|
||||
The client makes a request to the token endpoint by adding the
|
||||
following parameters using the "application/x-www-form-urlencoded"
|
||||
format per `Appendix B`_ in the HTTP request entity-body:
|
||||
|
||||
:param username: The resource owner username.
|
||||
:param password: The resource owner password.
|
||||
:param scope: The scope of the access request as described by
|
||||
`Section 3.3`_.
|
||||
:param kwargs: Extra credentials to include in the token request.
|
||||
|
||||
If the client type is confidential or the client was issued client
|
||||
credentials (or assigned other authentication requirements), the
|
||||
client MUST authenticate with the authorization server as described
|
||||
in `Section 3.2.1`_.
|
||||
|
||||
The prepared body will include all provided credentials as well as
|
||||
the ``grant_type`` parameter set to ``password``::
|
||||
|
||||
>>> from oauthlib.oauth2 import LegacyApplicationClient
|
||||
>>> client = LegacyApplicationClient('your_id')
|
||||
>>> client.prepare_request_body(username='foo', password='bar', scope=['hello', 'world'])
|
||||
'grant_type=password&username=foo&scope=hello+world&password=bar'
|
||||
|
||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
.. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
|
||||
"""
|
||||
return prepare_token_request('password', body=body, username=username,
|
||||
password=password, scope=scope, **kwargs)
|
173
lib/oauthlib/oauth2/rfc6749/clients/mobile_application.py
Normal file
173
lib/oauthlib/oauth2/rfc6749/clients/mobile_application.py
Normal file
|
@ -0,0 +1,173 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from .base import Client
|
||||
from ..parameters import prepare_grant_uri
|
||||
from ..parameters import parse_implicit_response
|
||||
|
||||
|
||||
class MobileApplicationClient(Client):
|
||||
|
||||
"""A public client utilizing the implicit code grant workflow.
|
||||
|
||||
A user-agent-based application is a public client in which the
|
||||
client code is downloaded from a web server and executes within a
|
||||
user-agent (e.g. web browser) on the device used by the resource
|
||||
owner. Protocol data and credentials are easily accessible (and
|
||||
often visible) to the resource owner. Since such applications
|
||||
reside within the user-agent, they can make seamless use of the
|
||||
user-agent capabilities when requesting authorization.
|
||||
|
||||
The implicit grant type is used to obtain access tokens (it does not
|
||||
support the issuance of refresh tokens) and is optimized for public
|
||||
clients known to operate a particular redirection URI. These clients
|
||||
are typically implemented in a browser using a scripting language
|
||||
such as JavaScript.
|
||||
|
||||
As a redirection-based flow, the client must be capable of
|
||||
interacting with the resource owner's user-agent (typically a web
|
||||
browser) and capable of receiving incoming requests (via redirection)
|
||||
from the authorization server.
|
||||
|
||||
Unlike the authorization code grant type in which the client makes
|
||||
separate requests for authorization and access token, the client
|
||||
receives the access token as the result of the authorization request.
|
||||
|
||||
The implicit grant type does not include client authentication, and
|
||||
relies on the presence of the resource owner and the registration of
|
||||
the redirection URI. Because the access token is encoded into the
|
||||
redirection URI, it may be exposed to the resource owner and other
|
||||
applications residing on the same device.
|
||||
"""
|
||||
|
||||
def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
|
||||
state=None, **kwargs):
|
||||
"""Prepare the implicit grant request URI.
|
||||
|
||||
The client constructs the request URI by adding the following
|
||||
parameters to the query component of the authorization endpoint URI
|
||||
using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
|
||||
|
||||
:param redirect_uri: OPTIONAL. The redirect URI must be an absolute URI
|
||||
and it should have been registerd with the OAuth
|
||||
provider prior to use. As described in `Section 3.1.2`_.
|
||||
|
||||
:param scope: OPTIONAL. The scope of the access request as described by
|
||||
Section 3.3`_. These may be any string but are commonly
|
||||
URIs or various categories such as ``videos`` or ``documents``.
|
||||
|
||||
:param state: RECOMMENDED. An opaque value used by the client to maintain
|
||||
state between the request and callback. The authorization
|
||||
server includes this value when redirecting the user-agent back
|
||||
to the client. The parameter SHOULD be used for preventing
|
||||
cross-site request forgery as described in `Section 10.12`_.
|
||||
|
||||
:param kwargs: Extra arguments to include in the request URI.
|
||||
|
||||
In addition to supplied parameters, OAuthLib will append the ``client_id``
|
||||
that was provided in the constructor as well as the mandatory ``response_type``
|
||||
argument, set to ``token``::
|
||||
|
||||
>>> from oauthlib.oauth2 import MobileApplicationClient
|
||||
>>> client = MobileApplicationClient('your_id')
|
||||
>>> client.prepare_request_uri('https://example.com')
|
||||
'https://example.com?client_id=your_id&response_type=token'
|
||||
>>> client.prepare_request_uri('https://example.com', redirect_uri='https://a.b/callback')
|
||||
'https://example.com?client_id=your_id&response_type=token&redirect_uri=https%3A%2F%2Fa.b%2Fcallback'
|
||||
>>> client.prepare_request_uri('https://example.com', scope=['profile', 'pictures'])
|
||||
'https://example.com?client_id=your_id&response_type=token&scope=profile+pictures'
|
||||
>>> client.prepare_request_uri('https://example.com', foo='bar')
|
||||
'https://example.com?client_id=your_id&response_type=token&foo=bar'
|
||||
|
||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
||||
.. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
|
||||
.. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
.. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
|
||||
"""
|
||||
return prepare_grant_uri(uri, self.client_id, 'token',
|
||||
redirect_uri=redirect_uri, state=state, scope=scope, **kwargs)
|
||||
|
||||
def parse_request_uri_response(self, uri, state=None, scope=None):
|
||||
"""Parse the response URI fragment.
|
||||
|
||||
If the resource owner grants the access request, the authorization
|
||||
server issues an access token and delivers it to the client by adding
|
||||
the following parameters to the fragment component of the redirection
|
||||
URI using the "application/x-www-form-urlencoded" format:
|
||||
|
||||
:param uri: The callback URI that resulted from the user being redirected
|
||||
back from the provider to you, the client.
|
||||
:param state: The state provided in the authorization request.
|
||||
:param scope: The scopes provided in the authorization request.
|
||||
:return: Dictionary of token parameters.
|
||||
:raises: OAuth2Error if response is invalid.
|
||||
|
||||
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``.
|
||||
|
||||
**state**
|
||||
If you provided the state parameter in the authorization phase, then
|
||||
the provider is required to include that exact state value in the
|
||||
response.
|
||||
|
||||
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.
|
||||
|
||||
A few example responses can be seen below::
|
||||
|
||||
>>> response_uri = 'https://example.com/callback#access_token=sdlfkj452&state=ss345asyht&token_type=Bearer&scope=hello+world'
|
||||
>>> from oauthlib.oauth2 import MobileApplicationClient
|
||||
>>> client = MobileApplicationClient('your_id')
|
||||
>>> client.parse_request_uri_response(response_uri)
|
||||
{
|
||||
'access_token': 'sdlfkj452',
|
||||
'token_type': 'Bearer',
|
||||
'state': 'ss345asyht',
|
||||
'scope': [u'hello', u'world']
|
||||
}
|
||||
>>> client.parse_request_uri_response(response_uri, state='other')
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
File "oauthlib/oauth2/rfc6749/__init__.py", line 598, in parse_request_uri_response
|
||||
**scope**
|
||||
File "oauthlib/oauth2/rfc6749/parameters.py", line 197, in parse_implicit_response
|
||||
raise ValueError("Mismatching or missing state in params.")
|
||||
ValueError: Mismatching or missing state in params.
|
||||
>>> def alert_scope_changed(message, old, new):
|
||||
... print(message, old, new)
|
||||
...
|
||||
>>> oauthlib.signals.scope_changed.connect(alert_scope_changed)
|
||||
>>> client.parse_request_body_response(response_body, scope=['other'])
|
||||
('Scope has changed from "other" to "hello world".', ['other'], ['hello', 'world'])
|
||||
|
||||
.. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
|
||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
"""
|
||||
self.token = parse_implicit_response(uri, state=state, scope=scope)
|
||||
self._populate_attributes(self.token)
|
||||
return self.token
|
176
lib/oauthlib/oauth2/rfc6749/clients/service_application.py
Normal file
176
lib/oauthlib/oauth2/rfc6749/clients/service_application.py
Normal file
|
@ -0,0 +1,176 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import time
|
||||
|
||||
from oauthlib.common import to_unicode
|
||||
|
||||
from .base import Client
|
||||
from ..parameters import prepare_token_request
|
||||
from ..parameters import parse_token_response
|
||||
|
||||
|
||||
class ServiceApplicationClient(Client):
|
||||
"""A public client utilizing the JWT bearer grant.
|
||||
|
||||
JWT bearer tokes can be used to request an access token when a client
|
||||
wishes to utilize an existing trust relationship, expressed through the
|
||||
semantics of (and digital signature or keyed message digest calculated
|
||||
over) the JWT, without a direct user approval step at the authorization
|
||||
server.
|
||||
|
||||
This grant type does not involve an authorization step. It may be
|
||||
used by both public and confidential clients.
|
||||
"""
|
||||
|
||||
grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
|
||||
|
||||
def __init__(self, client_id, private_key=None, subject=None, issuer=None,
|
||||
audience=None, **kwargs):
|
||||
"""Initalize a JWT client with defaults for implicit use later.
|
||||
|
||||
:param client_id: Client identifier given by the OAuth provider upon
|
||||
registration.
|
||||
|
||||
:param private_key: Private key used for signing and encrypting.
|
||||
Must be given as a string.
|
||||
|
||||
:param subject: The principal that is the subject of the JWT, i.e.
|
||||
which user is the token requested on behalf of.
|
||||
For example, ``foo@example.com.
|
||||
|
||||
:param issuer: The JWT MUST contain an "iss" (issuer) claim that
|
||||
contains a unique identifier for the entity that issued
|
||||
the JWT. For example, ``your-client@provider.com``.
|
||||
|
||||
:param audience: A value identifying the authorization server as an
|
||||
intended audience, e.g.
|
||||
``https://provider.com/oauth2/token``.
|
||||
|
||||
:param kwargs: Additional arguments to pass to base client, such as
|
||||
state and token. See Client.__init__.__doc__ for
|
||||
details.
|
||||
"""
|
||||
super(ServiceApplicationClient, self).__init__(client_id, **kwargs)
|
||||
self.private_key = private_key
|
||||
self.subject = subject
|
||||
self.issuer = issuer
|
||||
self.audience = audience
|
||||
|
||||
def prepare_request_body(self,
|
||||
private_key=None,
|
||||
subject=None,
|
||||
issuer=None,
|
||||
audience=None,
|
||||
expires_at=None,
|
||||
issued_at=None,
|
||||
extra_claims=None,
|
||||
body='',
|
||||
scope=None,
|
||||
**kwargs):
|
||||
"""Create and add a JWT assertion to the request body.
|
||||
|
||||
:param private_key: Private key used for signing and encrypting.
|
||||
Must be given as a string.
|
||||
|
||||
:param subject: (sub) The principal that is the subject of the JWT,
|
||||
i.e. which user is the token requested on behalf of.
|
||||
For example, ``foo@example.com.
|
||||
|
||||
:param issuer: (iss) The JWT MUST contain an "iss" (issuer) claim that
|
||||
contains a unique identifier for the entity that issued
|
||||
the JWT. For example, ``your-client@provider.com``.
|
||||
|
||||
:param audience: (aud) A value identifying the authorization server as an
|
||||
intended audience, e.g.
|
||||
``https://provider.com/oauth2/token``.
|
||||
|
||||
:param expires_at: A unix expiration timestamp for the JWT. Defaults
|
||||
to an hour from now, i.e. ``time.time() + 3600``.
|
||||
|
||||
:param issued_at: A unix timestamp of when the JWT was created.
|
||||
Defaults to now, i.e. ``time.time()``.
|
||||
|
||||
:param not_before: A unix timestamp after which the JWT may be used.
|
||||
Not included unless provided.
|
||||
|
||||
:param jwt_id: A unique JWT token identifier. Not included unless
|
||||
provided.
|
||||
|
||||
:param extra_claims: A dict of additional claims to include in the JWT.
|
||||
|
||||
:param scope: The scope of the access request.
|
||||
|
||||
:param body: Request body (string) with extra parameters.
|
||||
|
||||
:param kwargs: Extra credentials to include in the token request.
|
||||
|
||||
The "scope" parameter may be used, as defined in the Assertion
|
||||
Framework for OAuth 2.0 Client Authentication and Authorization Grants
|
||||
[I-D.ietf-oauth-assertions] specification, to indicate the requested
|
||||
scope.
|
||||
|
||||
Authentication of the client is optional, as described in
|
||||
`Section 3.2.1`_ of OAuth 2.0 [RFC6749] and consequently, the
|
||||
"client_id" is only needed when a form of client authentication that
|
||||
relies on the parameter is used.
|
||||
|
||||
The following non-normative example demonstrates an Access Token
|
||||
Request with a JWT as an authorization grant (with extra line breaks
|
||||
for display purposes only):
|
||||
|
||||
.. code-block: http
|
||||
|
||||
POST /token.oauth2 HTTP/1.1
|
||||
Host: as.example.com
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
|
||||
&assertion=eyJhbGciOiJFUzI1NiJ9.
|
||||
eyJpc3Mi[...omitted for brevity...].
|
||||
J9l-ZhwP[...omitted for brevity...]
|
||||
|
||||
.. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
|
||||
"""
|
||||
import jwt
|
||||
|
||||
key = private_key or self.private_key
|
||||
if not key:
|
||||
raise ValueError('An encryption key must be supplied to make JWT'
|
||||
' token requests.')
|
||||
claim = {
|
||||
'iss': issuer or self.issuer,
|
||||
'aud': audience or self.issuer,
|
||||
'sub': subject or self.issuer,
|
||||
'exp': int(expires_at or time.time() + 3600),
|
||||
'iat': int(issued_at or time.time()),
|
||||
}
|
||||
|
||||
for attr in ('iss', 'aud', 'sub'):
|
||||
if claim[attr] is None:
|
||||
raise ValueError(
|
||||
'Claim must include %s but none was given.' % attr)
|
||||
|
||||
if 'not_before' in kwargs:
|
||||
claim['nbf'] = kwargs.pop('not_before')
|
||||
|
||||
if 'jwt_id' in kwargs:
|
||||
claim['jti'] = kwargs.pop('jwt_id')
|
||||
|
||||
claim.update(extra_claims or {})
|
||||
|
||||
assertion = jwt.encode(claim, key, 'RS256')
|
||||
assertion = to_unicode(assertion)
|
||||
|
||||
return prepare_token_request(self.grant_type,
|
||||
body=body,
|
||||
assertion=assertion,
|
||||
scope=scope,
|
||||
**kwargs)
|
176
lib/oauthlib/oauth2/rfc6749/clients/web_application.py
Normal file
176
lib/oauthlib/oauth2/rfc6749/clients/web_application.py
Normal file
|
@ -0,0 +1,176 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from .base import Client
|
||||
from ..parameters import prepare_grant_uri, prepare_token_request
|
||||
from ..parameters import parse_authorization_code_response
|
||||
from ..parameters import parse_token_response
|
||||
|
||||
|
||||
class WebApplicationClient(Client):
|
||||
|
||||
"""A client utilizing the authorization code grant workflow.
|
||||
|
||||
A web application is a confidential client running on a web
|
||||
server. Resource owners access the client via an HTML user
|
||||
interface rendered in a user-agent on the device used by the
|
||||
resource owner. The client credentials as well as any access
|
||||
token issued to the client are stored on the web server and are
|
||||
not exposed to or accessible by the resource owner.
|
||||
|
||||
The authorization code grant type is used to obtain both access
|
||||
tokens and refresh tokens and is optimized for confidential clients.
|
||||
As a redirection-based flow, the client must be capable of
|
||||
interacting with the resource owner's user-agent (typically a web
|
||||
browser) and capable of receiving incoming requests (via redirection)
|
||||
from the authorization server.
|
||||
"""
|
||||
|
||||
def __init__(self, client_id, code=None, **kwargs):
|
||||
super(WebApplicationClient, self).__init__(client_id, **kwargs)
|
||||
self.code = code
|
||||
|
||||
def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
|
||||
state=None, **kwargs):
|
||||
"""Prepare the authorization code request URI
|
||||
|
||||
The client constructs the request URI by adding the following
|
||||
parameters to the query component of the authorization endpoint URI
|
||||
using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
|
||||
|
||||
:param redirect_uri: OPTIONAL. The redirect URI must be an absolute URI
|
||||
and it should have been registerd with the OAuth
|
||||
provider prior to use. As described in `Section 3.1.2`_.
|
||||
|
||||
:param scope: OPTIONAL. The scope of the access request as described by
|
||||
Section 3.3`_. These may be any string but are commonly
|
||||
URIs or various categories such as ``videos`` or ``documents``.
|
||||
|
||||
:param state: RECOMMENDED. An opaque value used by the client to maintain
|
||||
state between the request and callback. The authorization
|
||||
server includes this value when redirecting the user-agent back
|
||||
to the client. The parameter SHOULD be used for preventing
|
||||
cross-site request forgery as described in `Section 10.12`_.
|
||||
|
||||
:param kwargs: Extra arguments to include in the request URI.
|
||||
|
||||
In addition to supplied parameters, OAuthLib will append the ``client_id``
|
||||
that was provided in the constructor as well as the mandatory ``response_type``
|
||||
argument, set to ``code``::
|
||||
|
||||
>>> from oauthlib.oauth2 import WebApplicationClient
|
||||
>>> client = WebApplicationClient('your_id')
|
||||
>>> client.prepare_request_uri('https://example.com')
|
||||
'https://example.com?client_id=your_id&response_type=code'
|
||||
>>> client.prepare_request_uri('https://example.com', redirect_uri='https://a.b/callback')
|
||||
'https://example.com?client_id=your_id&response_type=code&redirect_uri=https%3A%2F%2Fa.b%2Fcallback'
|
||||
>>> client.prepare_request_uri('https://example.com', scope=['profile', 'pictures'])
|
||||
'https://example.com?client_id=your_id&response_type=code&scope=profile+pictures'
|
||||
>>> client.prepare_request_uri('https://example.com', foo='bar')
|
||||
'https://example.com?client_id=your_id&response_type=code&foo=bar'
|
||||
|
||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
||||
.. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
|
||||
.. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
.. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
|
||||
"""
|
||||
return prepare_grant_uri(uri, self.client_id, 'code',
|
||||
redirect_uri=redirect_uri, scope=scope, state=state, **kwargs)
|
||||
|
||||
def prepare_request_body(self, client_id=None, code=None, body='',
|
||||
redirect_uri=None, **kwargs):
|
||||
"""Prepare the access token request body.
|
||||
|
||||
The client makes a request to the token endpoint by adding the
|
||||
following parameters using the "application/x-www-form-urlencoded"
|
||||
format in the HTTP request entity-body:
|
||||
|
||||
:param client_id: REQUIRED, if the client is not authenticating with the
|
||||
authorization server as described in `Section 3.2.1`_.
|
||||
|
||||
:param code: REQUIRED. The authorization code received from the
|
||||
authorization server.
|
||||
|
||||
:param redirect_uri: REQUIRED, if the "redirect_uri" parameter was included in the
|
||||
authorization request as described in `Section 4.1.1`_, and their
|
||||
values MUST be identical.
|
||||
|
||||
:param kwargs: Extra parameters to include in the token request.
|
||||
|
||||
In addition OAuthLib will add the ``grant_type`` parameter set to
|
||||
``authorization_code``.
|
||||
|
||||
If the client type is confidential or the client was issued client
|
||||
credentials (or assigned other authentication requirements), the
|
||||
client MUST authenticate with the authorization server as described
|
||||
in `Section 3.2.1`_::
|
||||
|
||||
>>> from oauthlib.oauth2 import WebApplicationClient
|
||||
>>> client = WebApplicationClient('your_id')
|
||||
>>> client.prepare_request_body(code='sh35ksdf09sf')
|
||||
'grant_type=authorization_code&code=sh35ksdf09sf'
|
||||
>>> client.prepare_request_body(code='sh35ksdf09sf', foo='bar')
|
||||
'grant_type=authorization_code&code=sh35ksdf09sf&foo=bar'
|
||||
|
||||
.. _`Section 4.1.1`: http://tools.ietf.org/html/rfc6749#section-4.1.1
|
||||
.. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
|
||||
"""
|
||||
code = code or self.code
|
||||
return prepare_token_request('authorization_code', code=code, body=body,
|
||||
client_id=self.client_id, redirect_uri=redirect_uri, **kwargs)
|
||||
|
||||
def parse_request_uri_response(self, uri, state=None):
|
||||
"""Parse the URI query for code and state.
|
||||
|
||||
If the resource owner grants the access request, the authorization
|
||||
server issues an authorization code and delivers it to the client by
|
||||
adding the following parameters to the query component of the
|
||||
redirection URI using the "application/x-www-form-urlencoded" format:
|
||||
|
||||
:param uri: The callback URI that resulted from the user being redirected
|
||||
back from the provider to you, the client.
|
||||
:param state: The state provided in the authorization request.
|
||||
|
||||
**code**
|
||||
The authorization code generated by the authorization server.
|
||||
The authorization code MUST expire shortly after it is issued
|
||||
to mitigate the risk of leaks. A maximum authorization code
|
||||
lifetime of 10 minutes is RECOMMENDED. The client MUST NOT
|
||||
use the authorization code more than once. If an authorization
|
||||
code is used more than once, the authorization server MUST deny
|
||||
the request and SHOULD revoke (when possible) all tokens
|
||||
previously issued based on that authorization code.
|
||||
The authorization code is bound to the client identifier and
|
||||
redirection URI.
|
||||
|
||||
**state**
|
||||
If the "state" parameter was present in the authorization request.
|
||||
|
||||
This method is mainly intended to enforce strict state checking with
|
||||
the added benefit of easily extracting parameters from the URI::
|
||||
|
||||
>>> from oauthlib.oauth2 import WebApplicationClient
|
||||
>>> client = WebApplicationClient('your_id')
|
||||
>>> uri = 'https://example.com/callback?code=sdfkjh345&state=sfetw45'
|
||||
>>> client.parse_request_uri_response(uri, state='sfetw45')
|
||||
{'state': 'sfetw45', 'code': 'sdfkjh345'}
|
||||
>>> client.parse_request_uri_response(uri, state='other')
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
File "oauthlib/oauth2/rfc6749/__init__.py", line 357, in parse_request_uri_response
|
||||
back from the provider to you, the client.
|
||||
File "oauthlib/oauth2/rfc6749/parameters.py", line 153, in parse_authorization_code_response
|
||||
raise MismatchingStateError()
|
||||
oauthlib.oauth2.rfc6749.errors.MismatchingStateError
|
||||
"""
|
||||
response = parse_authorization_code_response(uri, state=state)
|
||||
self._populate_attributes(response)
|
||||
return response
|
19
lib/oauthlib/oauth2/rfc6749/endpoints/__init__.py
Normal file
19
lib/oauthlib/oauth2/rfc6749/endpoints/__init__.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from .authorization import AuthorizationEndpoint
|
||||
from .token import TokenEndpoint
|
||||
from .resource import ResourceEndpoint
|
||||
from .revocation import RevocationEndpoint
|
||||
from .pre_configured import Server
|
||||
from .pre_configured import WebApplicationServer
|
||||
from .pre_configured import MobileApplicationServer
|
||||
from .pre_configured import LegacyApplicationServer
|
||||
from .pre_configured import BackendApplicationServer
|
114
lib/oauthlib/oauth2/rfc6749/endpoints/authorization.py
Normal file
114
lib/oauthlib/oauth2/rfc6749/endpoints/authorization.py
Normal file
|
@ -0,0 +1,114 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib.common import Request
|
||||
|
||||
from .base import BaseEndpoint, catch_errors_and_unavailability
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AuthorizationEndpoint(BaseEndpoint):
|
||||
|
||||
"""Authorization endpoint - used by the client to obtain authorization
|
||||
from the resource owner via user-agent redirection.
|
||||
|
||||
The authorization endpoint is used to interact with the resource
|
||||
owner and obtain an authorization grant. The authorization server
|
||||
MUST first verify the identity of the resource owner. The way in
|
||||
which the authorization server authenticates the resource owner (e.g.
|
||||
username and password login, session cookies) is beyond the scope of
|
||||
this specification.
|
||||
|
||||
The endpoint URI MAY include an "application/x-www-form-urlencoded"
|
||||
formatted (per `Appendix B`_) query component,
|
||||
which MUST be retained when adding additional query parameters. The
|
||||
endpoint URI MUST NOT include a fragment component::
|
||||
|
||||
https://example.com/path?query=component # OK
|
||||
https://example.com/path?query=component#fragment # Not OK
|
||||
|
||||
Since requests to the authorization endpoint result in user
|
||||
authentication and the transmission of clear-text credentials (in the
|
||||
HTTP response), the authorization server MUST require the use of TLS
|
||||
as described in Section 1.6 when sending requests to the
|
||||
authorization endpoint::
|
||||
|
||||
# We will deny any request which URI schema is not with https
|
||||
|
||||
The authorization server MUST support the use of the HTTP "GET"
|
||||
method [RFC2616] for the authorization endpoint, and MAY support the
|
||||
use of the "POST" method as well::
|
||||
|
||||
# HTTP method is currently not enforced
|
||||
|
||||
Parameters sent without a value MUST be treated as if they were
|
||||
omitted from the request. The authorization server MUST ignore
|
||||
unrecognized request parameters. Request and response parameters
|
||||
MUST NOT be included more than once::
|
||||
|
||||
# Enforced through the design of oauthlib.common.Request
|
||||
|
||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
||||
"""
|
||||
|
||||
def __init__(self, default_response_type, default_token_type,
|
||||
response_types):
|
||||
BaseEndpoint.__init__(self)
|
||||
self._response_types = response_types
|
||||
self._default_response_type = default_response_type
|
||||
self._default_token_type = default_token_type
|
||||
|
||||
@property
|
||||
def response_types(self):
|
||||
return self._response_types
|
||||
|
||||
@property
|
||||
def default_response_type(self):
|
||||
return self._default_response_type
|
||||
|
||||
@property
|
||||
def default_response_type_handler(self):
|
||||
return self.response_types.get(self.default_response_type)
|
||||
|
||||
@property
|
||||
def default_token_type(self):
|
||||
return self._default_token_type
|
||||
|
||||
@catch_errors_and_unavailability
|
||||
def create_authorization_response(self, uri, http_method='GET', body=None,
|
||||
headers=None, scopes=None, credentials=None):
|
||||
"""Extract response_type and route to the designated handler."""
|
||||
request = Request(
|
||||
uri, http_method=http_method, body=body, headers=headers)
|
||||
request.scopes = scopes
|
||||
# TODO: decide whether this should be a required argument
|
||||
request.user = None # TODO: explain this in docs
|
||||
for k, v in (credentials or {}).items():
|
||||
setattr(request, k, v)
|
||||
response_type_handler = self.response_types.get(
|
||||
request.response_type, self.default_response_type_handler)
|
||||
log.debug('Dispatching response_type %s request to %r.',
|
||||
request.response_type, response_type_handler)
|
||||
return response_type_handler.create_authorization_response(
|
||||
request, self.default_token_type)
|
||||
|
||||
@catch_errors_and_unavailability
|
||||
def validate_authorization_request(self, uri, http_method='GET', body=None,
|
||||
headers=None):
|
||||
"""Extract response_type and route to the designated handler."""
|
||||
request = Request(
|
||||
uri, http_method=http_method, body=body, headers=headers)
|
||||
request.scopes = None
|
||||
response_type_handler = self.response_types.get(
|
||||
request.response_type, self.default_response_type_handler)
|
||||
return response_type_handler.validate_authorization_request(request)
|
65
lib/oauthlib/oauth2/rfc6749/endpoints/base.py
Normal file
65
lib/oauthlib/oauth2/rfc6749/endpoints/base.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import functools
|
||||
import logging
|
||||
|
||||
from ..errors import TemporarilyUnavailableError, ServerError
|
||||
from ..errors import FatalClientError, OAuth2Error
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseEndpoint(object):
|
||||
|
||||
def __init__(self):
|
||||
self._available = True
|
||||
self._catch_errors = False
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
return self._available
|
||||
|
||||
@available.setter
|
||||
def available(self, available):
|
||||
self._available = available
|
||||
|
||||
@property
|
||||
def catch_errors(self):
|
||||
return self._catch_errors
|
||||
|
||||
@catch_errors.setter
|
||||
def catch_errors(self, catch_errors):
|
||||
self._catch_errors = catch_errors
|
||||
|
||||
|
||||
def catch_errors_and_unavailability(f):
|
||||
@functools.wraps(f)
|
||||
def wrapper(endpoint, uri, *args, **kwargs):
|
||||
if not endpoint.available:
|
||||
e = TemporarilyUnavailableError()
|
||||
log.info('Endpoint unavailable, ignoring request %s.' % uri)
|
||||
return {}, e.json, 503
|
||||
|
||||
if endpoint.catch_errors:
|
||||
try:
|
||||
return f(endpoint, uri, *args, **kwargs)
|
||||
except OAuth2Error:
|
||||
raise
|
||||
except FatalClientError:
|
||||
raise
|
||||
except Exception as e:
|
||||
error = ServerError()
|
||||
log.warning(
|
||||
'Exception caught while processing request, %s.' % e)
|
||||
return {}, error.json, 500
|
||||
else:
|
||||
return f(endpoint, uri, *args, **kwargs)
|
||||
return wrapper
|
209
lib/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
Normal file
209
lib/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
Normal file
|
@ -0,0 +1,209 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from ..tokens import BearerToken
|
||||
from ..grant_types import AuthorizationCodeGrant
|
||||
from ..grant_types import ImplicitGrant
|
||||
from ..grant_types import ResourceOwnerPasswordCredentialsGrant
|
||||
from ..grant_types import ClientCredentialsGrant
|
||||
from ..grant_types import RefreshTokenGrant
|
||||
|
||||
from .authorization import AuthorizationEndpoint
|
||||
from .token import TokenEndpoint
|
||||
from .resource import ResourceEndpoint
|
||||
from .revocation import RevocationEndpoint
|
||||
|
||||
|
||||
class Server(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint,
|
||||
RevocationEndpoint):
|
||||
|
||||
"""An all-in-one endpoint featuring all four major grant types."""
|
||||
|
||||
def __init__(self, request_validator, token_expires_in=None,
|
||||
token_generator=None, refresh_token_generator=None,
|
||||
*args, **kwargs):
|
||||
"""Construct a new all-grants-in-one server.
|
||||
|
||||
:param request_validator: An implementation of
|
||||
oauthlib.oauth2.RequestValidator.
|
||||
:param token_expires_in: An int or a function to generate a token
|
||||
expiration offset (in seconds) given a
|
||||
oauthlib.common.Request object.
|
||||
:param token_generator: A function to generate a token from a request.
|
||||
:param refresh_token_generator: A function to generate a token from a
|
||||
request for the refresh token.
|
||||
:param kwargs: Extra parameters to pass to authorization-,
|
||||
token-, resource-, and revocation-endpoint constructors.
|
||||
"""
|
||||
auth_grant = AuthorizationCodeGrant(request_validator)
|
||||
implicit_grant = ImplicitGrant(request_validator)
|
||||
password_grant = ResourceOwnerPasswordCredentialsGrant(
|
||||
request_validator)
|
||||
credentials_grant = ClientCredentialsGrant(request_validator)
|
||||
refresh_grant = RefreshTokenGrant(request_validator)
|
||||
bearer = BearerToken(request_validator, token_generator,
|
||||
token_expires_in, refresh_token_generator)
|
||||
AuthorizationEndpoint.__init__(self, default_response_type='code',
|
||||
response_types={
|
||||
'code': auth_grant,
|
||||
'token': implicit_grant,
|
||||
},
|
||||
default_token_type=bearer)
|
||||
TokenEndpoint.__init__(self, default_grant_type='authorization_code',
|
||||
grant_types={
|
||||
'authorization_code': auth_grant,
|
||||
'password': password_grant,
|
||||
'client_credentials': credentials_grant,
|
||||
'refresh_token': refresh_grant,
|
||||
},
|
||||
default_token_type=bearer)
|
||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||
token_types={'Bearer': bearer})
|
||||
RevocationEndpoint.__init__(self, request_validator)
|
||||
|
||||
|
||||
class WebApplicationServer(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint,
|
||||
RevocationEndpoint):
|
||||
|
||||
"""An all-in-one endpoint featuring Authorization code grant and Bearer tokens."""
|
||||
|
||||
def __init__(self, request_validator, token_generator=None,
|
||||
token_expires_in=None, refresh_token_generator=None, **kwargs):
|
||||
"""Construct a new web application server.
|
||||
|
||||
:param request_validator: An implementation of
|
||||
oauthlib.oauth2.RequestValidator.
|
||||
:param token_expires_in: An int or a function to generate a token
|
||||
expiration offset (in seconds) given a
|
||||
oauthlib.common.Request object.
|
||||
:param token_generator: A function to generate a token from a request.
|
||||
:param refresh_token_generator: A function to generate a token from a
|
||||
request for the refresh token.
|
||||
:param kwargs: Extra parameters to pass to authorization-,
|
||||
token-, resource-, and revocation-endpoint constructors.
|
||||
"""
|
||||
auth_grant = AuthorizationCodeGrant(request_validator)
|
||||
refresh_grant = RefreshTokenGrant(request_validator)
|
||||
bearer = BearerToken(request_validator, token_generator,
|
||||
token_expires_in, refresh_token_generator)
|
||||
AuthorizationEndpoint.__init__(self, default_response_type='code',
|
||||
response_types={'code': auth_grant},
|
||||
default_token_type=bearer)
|
||||
TokenEndpoint.__init__(self, default_grant_type='authorization_code',
|
||||
grant_types={
|
||||
'authorization_code': auth_grant,
|
||||
'refresh_token': refresh_grant,
|
||||
},
|
||||
default_token_type=bearer)
|
||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||
token_types={'Bearer': bearer})
|
||||
RevocationEndpoint.__init__(self, request_validator)
|
||||
|
||||
|
||||
class MobileApplicationServer(AuthorizationEndpoint, ResourceEndpoint,
|
||||
RevocationEndpoint):
|
||||
|
||||
"""An all-in-one endpoint featuring Implicit code grant and Bearer tokens."""
|
||||
|
||||
def __init__(self, request_validator, token_generator=None,
|
||||
token_expires_in=None, refresh_token_generator=None, **kwargs):
|
||||
"""Construct a new implicit grant server.
|
||||
|
||||
:param request_validator: An implementation of
|
||||
oauthlib.oauth2.RequestValidator.
|
||||
:param token_expires_in: An int or a function to generate a token
|
||||
expiration offset (in seconds) given a
|
||||
oauthlib.common.Request object.
|
||||
:param token_generator: A function to generate a token from a request.
|
||||
:param refresh_token_generator: A function to generate a token from a
|
||||
request for the refresh token.
|
||||
:param kwargs: Extra parameters to pass to authorization-,
|
||||
token-, resource-, and revocation-endpoint constructors.
|
||||
"""
|
||||
implicit_grant = ImplicitGrant(request_validator)
|
||||
bearer = BearerToken(request_validator, token_generator,
|
||||
token_expires_in, refresh_token_generator)
|
||||
AuthorizationEndpoint.__init__(self, default_response_type='token',
|
||||
response_types={
|
||||
'token': implicit_grant},
|
||||
default_token_type=bearer)
|
||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||
token_types={'Bearer': bearer})
|
||||
RevocationEndpoint.__init__(self, request_validator,
|
||||
supported_token_types=['access_token'])
|
||||
|
||||
|
||||
class LegacyApplicationServer(TokenEndpoint, ResourceEndpoint,
|
||||
RevocationEndpoint):
|
||||
|
||||
"""An all-in-one endpoint featuring Resource Owner Password Credentials grant and Bearer tokens."""
|
||||
|
||||
def __init__(self, request_validator, token_generator=None,
|
||||
token_expires_in=None, refresh_token_generator=None, **kwargs):
|
||||
"""Construct a resource owner password credentials grant server.
|
||||
|
||||
:param request_validator: An implementation of
|
||||
oauthlib.oauth2.RequestValidator.
|
||||
:param token_expires_in: An int or a function to generate a token
|
||||
expiration offset (in seconds) given a
|
||||
oauthlib.common.Request object.
|
||||
:param token_generator: A function to generate a token from a request.
|
||||
:param refresh_token_generator: A function to generate a token from a
|
||||
request for the refresh token.
|
||||
:param kwargs: Extra parameters to pass to authorization-,
|
||||
token-, resource-, and revocation-endpoint constructors.
|
||||
"""
|
||||
password_grant = ResourceOwnerPasswordCredentialsGrant(
|
||||
request_validator)
|
||||
refresh_grant = RefreshTokenGrant(request_validator)
|
||||
bearer = BearerToken(request_validator, token_generator,
|
||||
token_expires_in, refresh_token_generator)
|
||||
TokenEndpoint.__init__(self, default_grant_type='password',
|
||||
grant_types={
|
||||
'password': password_grant,
|
||||
'refresh_token': refresh_grant,
|
||||
},
|
||||
default_token_type=bearer)
|
||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||
token_types={'Bearer': bearer})
|
||||
RevocationEndpoint.__init__(self, request_validator)
|
||||
|
||||
|
||||
class BackendApplicationServer(TokenEndpoint, ResourceEndpoint,
|
||||
RevocationEndpoint):
|
||||
|
||||
"""An all-in-one endpoint featuring Client Credentials grant and Bearer tokens."""
|
||||
|
||||
def __init__(self, request_validator, token_generator=None,
|
||||
token_expires_in=None, refresh_token_generator=None, **kwargs):
|
||||
"""Construct a client credentials grant server.
|
||||
|
||||
:param request_validator: An implementation of
|
||||
oauthlib.oauth2.RequestValidator.
|
||||
:param token_expires_in: An int or a function to generate a token
|
||||
expiration offset (in seconds) given a
|
||||
oauthlib.common.Request object.
|
||||
:param token_generator: A function to generate a token from a request.
|
||||
:param refresh_token_generator: A function to generate a token from a
|
||||
request for the refresh token.
|
||||
:param kwargs: Extra parameters to pass to authorization-,
|
||||
token-, resource-, and revocation-endpoint constructors.
|
||||
"""
|
||||
credentials_grant = ClientCredentialsGrant(request_validator)
|
||||
bearer = BearerToken(request_validator, token_generator,
|
||||
token_expires_in, refresh_token_generator)
|
||||
TokenEndpoint.__init__(self, default_grant_type='client_credentials',
|
||||
grant_types={
|
||||
'client_credentials': credentials_grant},
|
||||
default_token_type=bearer)
|
||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||
token_types={'Bearer': bearer})
|
||||
RevocationEndpoint.__init__(self, request_validator,
|
||||
supported_token_types=['access_token'])
|
87
lib/oauthlib/oauth2/rfc6749/endpoints/resource.py
Normal file
87
lib/oauthlib/oauth2/rfc6749/endpoints/resource.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib.common import Request
|
||||
|
||||
from .base import BaseEndpoint, catch_errors_and_unavailability
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResourceEndpoint(BaseEndpoint):
|
||||
|
||||
"""Authorizes access to protected resources.
|
||||
|
||||
The client accesses protected resources by presenting the access
|
||||
token to the resource server. The resource server MUST validate the
|
||||
access token and ensure that it has not expired and that its scope
|
||||
covers the requested resource. The methods used by the resource
|
||||
server to validate the access token (as well as any error responses)
|
||||
are beyond the scope of this specification but generally involve an
|
||||
interaction or coordination between the resource server and the
|
||||
authorization server::
|
||||
|
||||
# For most cases, returning a 403 should suffice.
|
||||
|
||||
The method in which the client utilizes the access token to
|
||||
authenticate with the resource server depends on the type of access
|
||||
token issued by the authorization server. Typically, it involves
|
||||
using the HTTP "Authorization" request header field [RFC2617] with an
|
||||
authentication scheme defined by the specification of the access
|
||||
token type used, such as [RFC6750]::
|
||||
|
||||
# Access tokens may also be provided in query and body
|
||||
https://example.com/protected?access_token=kjfch2345sdf # Query
|
||||
access_token=sdf23409df # Body
|
||||
"""
|
||||
|
||||
def __init__(self, default_token, token_types):
|
||||
BaseEndpoint.__init__(self)
|
||||
self._tokens = token_types
|
||||
self._default_token = default_token
|
||||
|
||||
@property
|
||||
def default_token(self):
|
||||
return self._default_token
|
||||
|
||||
@property
|
||||
def default_token_type_handler(self):
|
||||
return self.tokens.get(self.default_token)
|
||||
|
||||
@property
|
||||
def tokens(self):
|
||||
return self._tokens
|
||||
|
||||
@catch_errors_and_unavailability
|
||||
def verify_request(self, uri, http_method='GET', body=None, headers=None,
|
||||
scopes=None):
|
||||
"""Validate client, code etc, return body + headers"""
|
||||
request = Request(uri, http_method, body, headers)
|
||||
request.token_type = self.find_token_type(request)
|
||||
request.scopes = scopes
|
||||
token_type_handler = self.tokens.get(request.token_type,
|
||||
self.default_token_type_handler)
|
||||
log.debug('Dispatching token_type %s request to %r.',
|
||||
request.token_type, token_type_handler)
|
||||
return token_type_handler.validate_request(request), request
|
||||
|
||||
def find_token_type(self, request):
|
||||
"""Token type identification.
|
||||
|
||||
RFC 6749 does not provide a method for easily differentiating between
|
||||
different token types during protected resource access. We estimate
|
||||
the most likely token type (if any) by asking each known token type
|
||||
to give an estimation based on the request.
|
||||
"""
|
||||
estimates = sorted(((t.estimate_type(request), n)
|
||||
for n, t in self.tokens.items()))
|
||||
return estimates[0][1] if len(estimates) else None
|
130
lib/oauthlib/oauth2/rfc6749/endpoints/revocation.py
Normal file
130
lib/oauthlib/oauth2/rfc6749/endpoints/revocation.py
Normal file
|
@ -0,0 +1,130 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.endpoint.revocation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
An implementation of the OAuth 2 `Token Revocation`_ spec (draft 11).
|
||||
|
||||
.. _`Token Revocation`: http://tools.ietf.org/html/draft-ietf-oauth-revocation-11
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib.common import Request
|
||||
|
||||
from .base import BaseEndpoint, catch_errors_and_unavailability
|
||||
from ..errors import InvalidClientError, UnsupportedTokenTypeError
|
||||
from ..errors import InvalidRequestError, OAuth2Error
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RevocationEndpoint(BaseEndpoint):
|
||||
|
||||
"""Token revocation endpoint.
|
||||
|
||||
Endpoint used by authenticated clients to revoke access and refresh tokens.
|
||||
Commonly this will be part of the Authorization Endpoint.
|
||||
"""
|
||||
|
||||
valid_token_types = ('access_token', 'refresh_token')
|
||||
|
||||
def __init__(self, request_validator, supported_token_types=None,
|
||||
enable_jsonp=False):
|
||||
BaseEndpoint.__init__(self)
|
||||
self.request_validator = request_validator
|
||||
self.supported_token_types = (
|
||||
supported_token_types or self.valid_token_types)
|
||||
self.enable_jsonp = enable_jsonp
|
||||
|
||||
@catch_errors_and_unavailability
|
||||
def create_revocation_response(self, uri, http_method='POST', body=None,
|
||||
headers=None):
|
||||
"""Revoke supplied access or refresh token.
|
||||
|
||||
|
||||
The authorization server responds with HTTP status code 200 if the
|
||||
token has been revoked sucessfully or if the client submitted an
|
||||
invalid token.
|
||||
|
||||
Note: invalid tokens do not cause an error response since the client
|
||||
cannot handle such an error in a reasonable way. Moreover, the purpose
|
||||
of the revocation request, invalidating the particular token, is
|
||||
already achieved.
|
||||
|
||||
The content of the response body is ignored by the client as all
|
||||
necessary information is conveyed in the response code.
|
||||
|
||||
An invalid token type hint value is ignored by the authorization server
|
||||
and does not influence the revocation response.
|
||||
"""
|
||||
request = Request(
|
||||
uri, http_method=http_method, body=body, headers=headers)
|
||||
try:
|
||||
self.validate_revocation_request(request)
|
||||
log.debug('Token revocation valid for %r.', request)
|
||||
except OAuth2Error as e:
|
||||
log.debug('Client error during validation of %r. %r.', request, e)
|
||||
response_body = e.json
|
||||
if self.enable_jsonp and request.callback:
|
||||
response_body = '%s(%s);' % (request.callback, response_body)
|
||||
return {}, response_body, e.status_code
|
||||
|
||||
self.request_validator.revoke_token(request.token,
|
||||
request.token_type_hint, request)
|
||||
|
||||
response_body = ''
|
||||
if self.enable_jsonp and request.callback:
|
||||
response_body = request.callback + '();'
|
||||
return {}, response_body, 200
|
||||
|
||||
def validate_revocation_request(self, request):
|
||||
"""Ensure the request is valid.
|
||||
|
||||
The client constructs the request by including the following parameters
|
||||
using the "application/x-www-form-urlencoded" format in the HTTP
|
||||
request entity-body:
|
||||
|
||||
token (REQUIRED). The token that the client wants to get revoked.
|
||||
|
||||
token_type_hint (OPTIONAL). A hint about the type of the token
|
||||
submitted for revocation. Clients MAY pass this parameter in order to
|
||||
help the authorization server to optimize the token lookup. If the
|
||||
server is unable to locate the token using the given hint, it MUST
|
||||
extend its search accross all of its supported token types. An
|
||||
authorization server MAY ignore this parameter, particularly if it is
|
||||
able to detect the token type automatically. This specification
|
||||
defines two such values:
|
||||
|
||||
* access_token: An Access Token as defined in [RFC6749],
|
||||
`section 1.4`_
|
||||
|
||||
* refresh_token: A Refresh Token as defined in [RFC6749],
|
||||
`section 1.5`_
|
||||
|
||||
Specific implementations, profiles, and extensions of this
|
||||
specification MAY define other values for this parameter using
|
||||
the registry defined in `Section 4.1.2`_.
|
||||
|
||||
The client also includes its authentication credentials as described in
|
||||
`Section 2.3`_. of [`RFC6749`_].
|
||||
|
||||
.. _`section 1.4`: http://tools.ietf.org/html/rfc6749#section-1.4
|
||||
.. _`section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5
|
||||
.. _`section 2.3`: http://tools.ietf.org/html/rfc6749#section-2.3
|
||||
.. _`Section 4.1.2`: http://tools.ietf.org/html/draft-ietf-oauth-revocation-11#section-4.1.2
|
||||
.. _`RFC6749`: http://tools.ietf.org/html/rfc6749
|
||||
"""
|
||||
if not request.token:
|
||||
raise InvalidRequestError(request=request,
|
||||
description='Missing token parameter.')
|
||||
|
||||
if self.request_validator.client_authentication_required(request):
|
||||
if not self.request_validator.authenticate_client(request):
|
||||
raise InvalidClientError(request=request)
|
||||
|
||||
if (request.token_type_hint and
|
||||
request.token_type_hint in self.valid_token_types and
|
||||
request.token_type_hint not in self.supported_token_types):
|
||||
raise UnsupportedTokenTypeError(request=request)
|
100
lib/oauthlib/oauth2/rfc6749/endpoints/token.py
Normal file
100
lib/oauthlib/oauth2/rfc6749/endpoints/token.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib.common import Request
|
||||
|
||||
from .base import BaseEndpoint, catch_errors_and_unavailability
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TokenEndpoint(BaseEndpoint):
|
||||
|
||||
"""Token issuing endpoint.
|
||||
|
||||
The token endpoint is used by the client to obtain an access token by
|
||||
presenting its authorization grant or refresh token. The token
|
||||
endpoint is used with every authorization grant except for the
|
||||
implicit grant type (since an access token is issued directly).
|
||||
|
||||
The means through which the client obtains the location of the token
|
||||
endpoint are beyond the scope of this specification, but the location
|
||||
is typically provided in the service documentation.
|
||||
|
||||
The endpoint URI MAY include an "application/x-www-form-urlencoded"
|
||||
formatted (per `Appendix B`_) query component,
|
||||
which MUST be retained when adding additional query parameters. The
|
||||
endpoint URI MUST NOT include a fragment component::
|
||||
|
||||
https://example.com/path?query=component # OK
|
||||
https://example.com/path?query=component#fragment # Not OK
|
||||
|
||||
Since requests to the authorization endpoint result in user
|
||||
Since requests to the token endpoint result in the transmission of
|
||||
clear-text credentials (in the HTTP request and response), the
|
||||
authorization server MUST require the use of TLS as described in
|
||||
Section 1.6 when sending requests to the token endpoint::
|
||||
|
||||
# We will deny any request which URI schema is not with https
|
||||
|
||||
The client MUST use the HTTP "POST" method when making access token
|
||||
requests::
|
||||
|
||||
# HTTP method is currently not enforced
|
||||
|
||||
Parameters sent without a value MUST be treated as if they were
|
||||
omitted from the request. The authorization server MUST ignore
|
||||
unrecognized request parameters. Request and response parameters
|
||||
MUST NOT be included more than once::
|
||||
|
||||
# Delegated to each grant type.
|
||||
|
||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
||||
"""
|
||||
|
||||
def __init__(self, default_grant_type, default_token_type, grant_types):
|
||||
BaseEndpoint.__init__(self)
|
||||
self._grant_types = grant_types
|
||||
self._default_token_type = default_token_type
|
||||
self._default_grant_type = default_grant_type
|
||||
|
||||
@property
|
||||
def grant_types(self):
|
||||
return self._grant_types
|
||||
|
||||
@property
|
||||
def default_grant_type(self):
|
||||
return self._default_grant_type
|
||||
|
||||
@property
|
||||
def default_grant_type_handler(self):
|
||||
return self.grant_types.get(self.default_grant_type)
|
||||
|
||||
@property
|
||||
def default_token_type(self):
|
||||
return self._default_token_type
|
||||
|
||||
@catch_errors_and_unavailability
|
||||
def create_token_response(self, uri, http_method='GET', body=None,
|
||||
headers=None, credentials=None):
|
||||
"""Extract grant_type and route to the designated handler."""
|
||||
request = Request(
|
||||
uri, http_method=http_method, body=body, headers=headers)
|
||||
request.scopes = None
|
||||
request.extra_credentials = credentials
|
||||
grant_type_handler = self.grant_types.get(request.grant_type,
|
||||
self.default_grant_type_handler)
|
||||
log.debug('Dispatching grant_type %s request to %r.',
|
||||
request.grant_type, grant_type_handler)
|
||||
return grant_type_handler.create_token_response(
|
||||
request, self.default_token_type)
|
271
lib/oauthlib/oauth2/rfc6749/errors.py
Normal file
271
lib/oauthlib/oauth2/rfc6749/errors.py
Normal file
|
@ -0,0 +1,271 @@
|
|||
# coding=utf-8
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.errors
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Error used both by OAuth 2 clients and providers to represent the spec
|
||||
defined error responses for all four core grant types.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
import json
|
||||
from oauthlib.common import urlencode, add_params_to_uri
|
||||
|
||||
|
||||
class OAuth2Error(Exception):
|
||||
error = None
|
||||
status_code = 400
|
||||
description = ''
|
||||
|
||||
def __init__(self, description=None, uri=None, state=None, status_code=None,
|
||||
request=None):
|
||||
"""
|
||||
description: A human-readable ASCII [USASCII] text providing
|
||||
additional information, used to assist the client
|
||||
developer in understanding the error that occurred.
|
||||
Values for the "error_description" parameter MUST NOT
|
||||
include characters outside the set
|
||||
x20-21 / x23-5B / x5D-7E.
|
||||
|
||||
uri: A URI identifying a human-readable web page with information
|
||||
about the error, used to provide the client developer with
|
||||
additional information about the error. Values for the
|
||||
"error_uri" parameter MUST conform to the URI- Reference
|
||||
syntax, and thus MUST NOT include characters outside the set
|
||||
x21 / x23-5B / x5D-7E.
|
||||
|
||||
state: A CSRF protection value received from the client.
|
||||
|
||||
request: Oauthlib Request object
|
||||
"""
|
||||
self.description = description or self.description
|
||||
message = '(%s) %s' % (self.error, self.description)
|
||||
if request:
|
||||
message += ' ' + repr(request)
|
||||
super(OAuth2Error, self).__init__(message)
|
||||
|
||||
self.uri = uri
|
||||
self.state = state
|
||||
|
||||
if status_code:
|
||||
self.status_code = status_code
|
||||
|
||||
if request:
|
||||
self.redirect_uri = request.redirect_uri
|
||||
self.client_id = request.client_id
|
||||
self.scopes = request.scopes
|
||||
self.response_type = request.response_type
|
||||
self.grant_type = request.grant_type
|
||||
if not state:
|
||||
self.state = request.state
|
||||
|
||||
def in_uri(self, uri):
|
||||
return add_params_to_uri(uri, self.twotuples)
|
||||
|
||||
@property
|
||||
def twotuples(self):
|
||||
error = [('error', self.error)]
|
||||
if self.description:
|
||||
error.append(('error_description', self.description))
|
||||
if self.uri:
|
||||
error.append(('error_uri', self.uri))
|
||||
if self.state:
|
||||
error.append(('state', self.state))
|
||||
return error
|
||||
|
||||
@property
|
||||
def urlencoded(self):
|
||||
return urlencode(self.twotuples)
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
return json.dumps(dict(self.twotuples))
|
||||
|
||||
|
||||
class TokenExpiredError(OAuth2Error):
|
||||
error = 'token_expired'
|
||||
|
||||
|
||||
class InsecureTransportError(OAuth2Error):
|
||||
error = 'insecure_transport'
|
||||
description = 'OAuth 2 MUST utilize https.'
|
||||
|
||||
|
||||
class MismatchingStateError(OAuth2Error):
|
||||
error = 'mismatching_state'
|
||||
description = 'CSRF Warning! State not equal in request and response.'
|
||||
|
||||
|
||||
class MissingCodeError(OAuth2Error):
|
||||
error = 'missing_code'
|
||||
|
||||
|
||||
class MissingTokenError(OAuth2Error):
|
||||
error = 'missing_token'
|
||||
|
||||
|
||||
class MissingTokenTypeError(OAuth2Error):
|
||||
error = 'missing_token_type'
|
||||
|
||||
|
||||
class FatalClientError(OAuth2Error):
|
||||
|
||||
"""Errors during authorization where user should not be redirected back.
|
||||
|
||||
If the request fails due to a missing, invalid, or mismatching
|
||||
redirection URI, or if the client identifier is missing or invalid,
|
||||
the authorization server SHOULD inform the resource owner of the
|
||||
error and MUST NOT automatically redirect the user-agent to the
|
||||
invalid redirection URI.
|
||||
|
||||
Instead the user should be informed of the error by the provider itself.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class InvalidRequestFatalError(FatalClientError):
|
||||
"""For fatal errors, the request is missing a required parameter, includes
|
||||
an invalid parameter value, includes a parameter more than once, or is
|
||||
otherwise malformed.
|
||||
"""
|
||||
error = 'invalid_request'
|
||||
|
||||
|
||||
class InvalidRedirectURIError(InvalidRequestFatalError):
|
||||
description = 'Invalid redirect URI.'
|
||||
|
||||
|
||||
class MissingRedirectURIError(InvalidRequestFatalError):
|
||||
description = 'Missing redirect URI.'
|
||||
|
||||
|
||||
class MismatchingRedirectURIError(InvalidRequestFatalError):
|
||||
description = 'Mismatching redirect URI.'
|
||||
|
||||
|
||||
class InvalidClientIdError(InvalidRequestFatalError):
|
||||
description = 'Invalid client_id parameter value.'
|
||||
|
||||
|
||||
class MissingClientIdError(InvalidRequestFatalError):
|
||||
description = 'Missing client_id parameter.'
|
||||
|
||||
|
||||
class InvalidRequestError(OAuth2Error):
|
||||
|
||||
"""The request is missing a required parameter, includes an invalid
|
||||
parameter value, includes a parameter more than once, or is
|
||||
otherwise malformed.
|
||||
"""
|
||||
error = 'invalid_request'
|
||||
|
||||
|
||||
class MissingResponseTypeError(InvalidRequestError):
|
||||
description = 'Missing response_type parameter.'
|
||||
|
||||
|
||||
class AccessDeniedError(OAuth2Error):
|
||||
|
||||
"""The resource owner or authorization server denied the request."""
|
||||
error = 'access_denied'
|
||||
status_code = 401
|
||||
|
||||
|
||||
class UnsupportedResponseTypeError(OAuth2Error):
|
||||
|
||||
"""The authorization server does not support obtaining an authorization
|
||||
code using this method.
|
||||
"""
|
||||
error = 'unsupported_response_type'
|
||||
|
||||
|
||||
class InvalidScopeError(OAuth2Error):
|
||||
|
||||
"""The requested scope is invalid, unknown, or malformed."""
|
||||
error = 'invalid_scope'
|
||||
status_code = 401
|
||||
|
||||
|
||||
class ServerError(OAuth2Error):
|
||||
|
||||
"""The authorization server encountered an unexpected condition that
|
||||
prevented it from fulfilling the request. (This error code is needed
|
||||
because a 500 Internal Server Error HTTP status code cannot be returned
|
||||
to the client via a HTTP redirect.)
|
||||
"""
|
||||
error = 'server_error'
|
||||
|
||||
|
||||
class TemporarilyUnavailableError(OAuth2Error):
|
||||
|
||||
"""The authorization server is currently unable to handle the request
|
||||
due to a temporary overloading or maintenance of the server.
|
||||
(This error code is needed because a 503 Service Unavailable HTTP
|
||||
status code cannot be returned to the client via a HTTP redirect.)
|
||||
"""
|
||||
error = 'temporarily_unavailable'
|
||||
|
||||
|
||||
class InvalidClientError(OAuth2Error):
|
||||
|
||||
"""Client authentication failed (e.g. unknown client, no client
|
||||
authentication included, or unsupported authentication method).
|
||||
The authorization server MAY return an HTTP 401 (Unauthorized) status
|
||||
code to indicate which HTTP authentication schemes are supported.
|
||||
If the client attempted to authenticate via the "Authorization" request
|
||||
header field, the authorization server MUST respond with an
|
||||
HTTP 401 (Unauthorized) status code, and include the "WWW-Authenticate"
|
||||
response header field matching the authentication scheme used by the
|
||||
client.
|
||||
"""
|
||||
error = 'invalid_client'
|
||||
status_code = 401
|
||||
|
||||
|
||||
class InvalidGrantError(OAuth2Error):
|
||||
|
||||
"""The provided authorization grant (e.g. authorization code, resource
|
||||
owner credentials) or refresh token is invalid, expired, revoked, does
|
||||
not match the redirection URI used in the authorization request, or was
|
||||
issued to another client.
|
||||
"""
|
||||
error = 'invalid_grant'
|
||||
status_code = 401
|
||||
|
||||
|
||||
class UnauthorizedClientError(OAuth2Error):
|
||||
|
||||
"""The authenticated client is not authorized to use this authorization
|
||||
grant type.
|
||||
"""
|
||||
error = 'unauthorized_client'
|
||||
status_code = 401
|
||||
|
||||
|
||||
class UnsupportedGrantTypeError(OAuth2Error):
|
||||
|
||||
"""The authorization grant type is not supported by the authorization
|
||||
server.
|
||||
"""
|
||||
error = 'unsupported_grant_type'
|
||||
|
||||
|
||||
class UnsupportedTokenTypeError(OAuth2Error):
|
||||
|
||||
"""The authorization server does not support the revocation of the
|
||||
presented token type. I.e. the client tried to revoke an access token
|
||||
on a server not supporting this feature.
|
||||
"""
|
||||
error = 'unsupported_token_type'
|
||||
|
||||
|
||||
def raise_from_error(error, params=None):
|
||||
import inspect
|
||||
import sys
|
||||
kwargs = {
|
||||
'description': params.get('error_description'),
|
||||
'uri': params.get('error_uri'),
|
||||
'state': params.get('state')
|
||||
}
|
||||
for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass):
|
||||
if cls.error == error:
|
||||
raise cls(**kwargs)
|
12
lib/oauthlib/oauth2/rfc6749/grant_types/__init__.py
Normal file
12
lib/oauthlib/oauth2/rfc6749/grant_types/__init__.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
from .authorization_code import AuthorizationCodeGrant
|
||||
from .implicit import ImplicitGrant
|
||||
from .resource_owner_password_credentials import ResourceOwnerPasswordCredentialsGrant
|
||||
from .client_credentials import ClientCredentialsGrant
|
||||
from .refresh_token import RefreshTokenGrant
|
402
lib/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
Normal file
402
lib/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
Normal file
|
@ -0,0 +1,402 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from oauthlib import common
|
||||
from oauthlib.uri_validate import is_absolute_uri
|
||||
|
||||
from .base import GrantTypeBase
|
||||
from .. import errors
|
||||
from ..request_validator import RequestValidator
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AuthorizationCodeGrant(GrantTypeBase):
|
||||
|
||||
"""`Authorization Code Grant`_
|
||||
|
||||
The authorization code grant type is used to obtain both access
|
||||
tokens and refresh tokens and is optimized for confidential clients.
|
||||
Since this is a redirection-based flow, the client must be capable of
|
||||
interacting with the resource owner's user-agent (typically a web
|
||||
browser) and capable of receiving incoming requests (via redirection)
|
||||
from the authorization server::
|
||||
|
||||
+----------+
|
||||
| Resource |
|
||||
| Owner |
|
||||
| |
|
||||
+----------+
|
||||
^
|
||||
|
|
||||
(B)
|
||||
+----|-----+ Client Identifier +---------------+
|
||||
| -+----(A)-- & Redirection URI ---->| |
|
||||
| User- | | Authorization |
|
||||
| Agent -+----(B)-- User authenticates --->| Server |
|
||||
| | | |
|
||||
| -+----(C)-- Authorization Code ---<| |
|
||||
+-|----|---+ +---------------+
|
||||
| | ^ v
|
||||
(A) (C) | |
|
||||
| | | |
|
||||
^ v | |
|
||||
+---------+ | |
|
||||
| |>---(D)-- Authorization Code ---------' |
|
||||
| Client | & Redirection URI |
|
||||
| | |
|
||||
| |<---(E)----- Access Token -------------------'
|
||||
+---------+ (w/ Optional Refresh Token)
|
||||
|
||||
Note: The lines illustrating steps (A), (B), and (C) are broken into
|
||||
two parts as they pass through the user-agent.
|
||||
|
||||
Figure 3: Authorization Code Flow
|
||||
|
||||
The flow illustrated in Figure 3 includes the following steps:
|
||||
|
||||
(A) The client initiates the flow by directing the resource owner's
|
||||
user-agent to the authorization endpoint. The client includes
|
||||
its client identifier, requested scope, local state, and a
|
||||
redirection URI to which the authorization server will send the
|
||||
user-agent back once access is granted (or denied).
|
||||
|
||||
(B) The authorization server authenticates the resource owner (via
|
||||
the user-agent) and establishes whether the resource owner
|
||||
grants or denies the client's access request.
|
||||
|
||||
(C) Assuming the resource owner grants access, the authorization
|
||||
server redirects the user-agent back to the client using the
|
||||
redirection URI provided earlier (in the request or during
|
||||
client registration). The redirection URI includes an
|
||||
authorization code and any local state provided by the client
|
||||
earlier.
|
||||
|
||||
(D) The client requests an access token from the authorization
|
||||
server's token endpoint by including the authorization code
|
||||
received in the previous step. When making the request, the
|
||||
client authenticates with the authorization server. The client
|
||||
includes the redirection URI used to obtain the authorization
|
||||
code for verification.
|
||||
|
||||
(E) The authorization server authenticates the client, validates the
|
||||
authorization code, and ensures that the redirection URI
|
||||
received matches the URI used to redirect the client in
|
||||
step (C). If valid, the authorization server responds back with
|
||||
an access token and, optionally, a refresh token.
|
||||
|
||||
.. _`Authorization Code Grant`: http://tools.ietf.org/html/rfc6749#section-4.1
|
||||
"""
|
||||
|
||||
def __init__(self, request_validator=None, refresh_token=True):
|
||||
self.request_validator = request_validator or RequestValidator()
|
||||
self.refresh_token = refresh_token
|
||||
|
||||
def create_authorization_code(self, request):
|
||||
"""Generates an authorization grant represented as a dictionary."""
|
||||
grant = {'code': common.generate_token()}
|
||||
if hasattr(request, 'state') and request.state:
|
||||
grant['state'] = request.state
|
||||
log.debug('Created authorization code grant %r for request %r.',
|
||||
grant, request)
|
||||
return grant
|
||||
|
||||
def create_authorization_response(self, request, token_handler):
|
||||
"""
|
||||
The client constructs the request URI by adding the following
|
||||
parameters to the query component of the authorization endpoint URI
|
||||
using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
|
||||
|
||||
response_type
|
||||
REQUIRED. Value MUST be set to "code".
|
||||
client_id
|
||||
REQUIRED. The client identifier as described in `Section 2.2`_.
|
||||
redirect_uri
|
||||
OPTIONAL. As described in `Section 3.1.2`_.
|
||||
scope
|
||||
OPTIONAL. The scope of the access request as described by
|
||||
`Section 3.3`_.
|
||||
state
|
||||
RECOMMENDED. An opaque value used by the client to maintain
|
||||
state between the request and callback. The authorization
|
||||
server includes this value when redirecting the user-agent back
|
||||
to the client. The parameter SHOULD be used for preventing
|
||||
cross-site request forgery as described in `Section 10.12`_.
|
||||
|
||||
The client directs the resource owner to the constructed URI using an
|
||||
HTTP redirection response, or by other means available to it via the
|
||||
user-agent.
|
||||
|
||||
:param request: oauthlib.commong.Request
|
||||
:param token_handler: A token handler instace, for example of type
|
||||
oauthlib.oauth2.BearerToken.
|
||||
:returns: headers, body, status
|
||||
:raises: FatalClientError on invalid redirect URI or client id.
|
||||
ValueError if scopes are not set on the request object.
|
||||
|
||||
A few examples::
|
||||
|
||||
>>> from your_validator import your_validator
|
||||
>>> request = Request('https://example.com/authorize?client_id=valid'
|
||||
... '&redirect_uri=http%3A%2F%2Fclient.com%2F')
|
||||
>>> from oauthlib.common import Request
|
||||
>>> from oauthlib.oauth2 import AuthorizationCodeGrant, BearerToken
|
||||
>>> token = BearerToken(your_validator)
|
||||
>>> grant = AuthorizationCodeGrant(your_validator)
|
||||
>>> grant.create_authorization_response(request, token)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
File "oauthlib/oauth2/rfc6749/grant_types.py", line 513, in create_authorization_response
|
||||
raise ValueError('Scopes must be set on post auth.')
|
||||
ValueError: Scopes must be set on post auth.
|
||||
>>> request.scopes = ['authorized', 'in', 'some', 'form']
|
||||
>>> grant.create_authorization_response(request, token)
|
||||
(u'http://client.com/?error=invalid_request&error_description=Missing+response_type+parameter.', None, None, 400)
|
||||
>>> request = Request('https://example.com/authorize?client_id=valid'
|
||||
... '&redirect_uri=http%3A%2F%2Fclient.com%2F'
|
||||
... '&response_type=code')
|
||||
>>> request.scopes = ['authorized', 'in', 'some', 'form']
|
||||
>>> grant.create_authorization_response(request, token)
|
||||
(u'http://client.com/?code=u3F05aEObJuP2k7DordviIgW5wl52N', None, None, 200)
|
||||
>>> # If the client id or redirect uri fails validation
|
||||
>>> grant.create_authorization_response(request, token)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
File "oauthlib/oauth2/rfc6749/grant_types.py", line 515, in create_authorization_response
|
||||
>>> grant.create_authorization_response(request, token)
|
||||
File "oauthlib/oauth2/rfc6749/grant_types.py", line 591, in validate_authorization_request
|
||||
oauthlib.oauth2.rfc6749.errors.InvalidClientIdError
|
||||
|
||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
||||
.. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
|
||||
.. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
.. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
|
||||
"""
|
||||
try:
|
||||
# request.scopes is only mandated in post auth and both pre and
|
||||
# post auth use validate_authorization_request
|
||||
if not request.scopes:
|
||||
raise ValueError('Scopes must be set on post auth.')
|
||||
|
||||
self.validate_authorization_request(request)
|
||||
log.debug('Pre resource owner authorization validation ok for %r.',
|
||||
request)
|
||||
|
||||
# If the request fails due to a missing, invalid, or mismatching
|
||||
# redirection URI, or if the client identifier is missing or invalid,
|
||||
# the authorization server SHOULD inform the resource owner of the
|
||||
# error and MUST NOT automatically redirect the user-agent to the
|
||||
# invalid redirection URI.
|
||||
except errors.FatalClientError as e:
|
||||
log.debug('Fatal client error during validation of %r. %r.',
|
||||
request, e)
|
||||
raise
|
||||
|
||||
# If the resource owner denies the access request or if the request
|
||||
# fails for reasons other than a missing or invalid redirection URI,
|
||||
# the authorization server informs the client by adding the following
|
||||
# parameters to the query component of the redirection URI using the
|
||||
# "application/x-www-form-urlencoded" format, per Appendix B:
|
||||
# http://tools.ietf.org/html/rfc6749#appendix-B
|
||||
except errors.OAuth2Error as e:
|
||||
log.debug('Client error during validation of %r. %r.', request, e)
|
||||
request.redirect_uri = request.redirect_uri or self.error_uri
|
||||
return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples)}, None, 302
|
||||
|
||||
grant = self.create_authorization_code(request)
|
||||
log.debug('Saving grant %r for %r.', grant, request)
|
||||
self.request_validator.save_authorization_code(
|
||||
request.client_id, grant, request)
|
||||
return {'Location': common.add_params_to_uri(request.redirect_uri, grant.items())}, None, 302
|
||||
|
||||
def create_token_response(self, request, token_handler):
|
||||
"""Validate the authorization code.
|
||||
|
||||
The client MUST NOT use the authorization code more than once. If an
|
||||
authorization code is used more than once, the authorization server
|
||||
MUST deny the request and SHOULD revoke (when possible) all tokens
|
||||
previously issued based on that authorization code. The authorization
|
||||
code is bound to the client identifier and redirection URI.
|
||||
"""
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'no-store',
|
||||
'Pragma': 'no-cache',
|
||||
}
|
||||
try:
|
||||
self.validate_token_request(request)
|
||||
log.debug('Token request validation ok for %r.', request)
|
||||
except errors.OAuth2Error as e:
|
||||
log.debug('Client error during validation of %r. %r.', request, e)
|
||||
return headers, e.json, e.status_code
|
||||
|
||||
token = token_handler.create_token(request, refresh_token=self.refresh_token)
|
||||
self.request_validator.invalidate_authorization_code(
|
||||
request.client_id, request.code, request)
|
||||
return headers, json.dumps(token), 200
|
||||
|
||||
def validate_authorization_request(self, request):
|
||||
"""Check the authorization request for normal and fatal errors.
|
||||
|
||||
A normal error could be a missing response_type parameter or the client
|
||||
attempting to access scope it is not allowed to ask authorization for.
|
||||
Normal errors can safely be included in the redirection URI and
|
||||
sent back to the client.
|
||||
|
||||
Fatal errors occur when the client_id or redirect_uri is invalid or
|
||||
missing. These must be caught by the provider and handled, how this
|
||||
is done is outside of the scope of OAuthLib but showing an error
|
||||
page describing the issue is a good idea.
|
||||
"""
|
||||
|
||||
# First check for fatal errors
|
||||
|
||||
# If the request fails due to a missing, invalid, or mismatching
|
||||
# redirection URI, or if the client identifier is missing or invalid,
|
||||
# the authorization server SHOULD inform the resource owner of the
|
||||
# error and MUST NOT automatically redirect the user-agent to the
|
||||
# invalid redirection URI.
|
||||
|
||||
# First check duplicate parameters
|
||||
for param in ('client_id', 'response_type', 'redirect_uri', 'scope', 'state'):
|
||||
try:
|
||||
duplicate_params = request.duplicate_params
|
||||
except ValueError:
|
||||
raise errors.InvalidRequestFatalError(description='Unable to parse query string', request=request)
|
||||
if param in duplicate_params:
|
||||
raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request)
|
||||
|
||||
# REQUIRED. The client identifier as described in Section 2.2.
|
||||
# http://tools.ietf.org/html/rfc6749#section-2.2
|
||||
if not request.client_id:
|
||||
raise errors.MissingClientIdError(request=request)
|
||||
|
||||
if not self.request_validator.validate_client_id(request.client_id, request):
|
||||
raise errors.InvalidClientIdError(request=request)
|
||||
|
||||
# OPTIONAL. As described in Section 3.1.2.
|
||||
# http://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||
log.debug('Validating redirection uri %s for client %s.',
|
||||
request.redirect_uri, request.client_id)
|
||||
if request.redirect_uri is not None:
|
||||
request.using_default_redirect_uri = False
|
||||
log.debug('Using provided redirect_uri %s', request.redirect_uri)
|
||||
if not is_absolute_uri(request.redirect_uri):
|
||||
raise errors.InvalidRedirectURIError(request=request)
|
||||
|
||||
if not self.request_validator.validate_redirect_uri(
|
||||
request.client_id, request.redirect_uri, request):
|
||||
raise errors.MismatchingRedirectURIError(request=request)
|
||||
else:
|
||||
request.redirect_uri = self.request_validator.get_default_redirect_uri(
|
||||
request.client_id, request)
|
||||
request.using_default_redirect_uri = True
|
||||
log.debug('Using default redirect_uri %s.', request.redirect_uri)
|
||||
if not request.redirect_uri:
|
||||
raise errors.MissingRedirectURIError(request=request)
|
||||
|
||||
# Then check for normal errors.
|
||||
|
||||
# If the resource owner denies the access request or if the request
|
||||
# fails for reasons other than a missing or invalid redirection URI,
|
||||
# the authorization server informs the client by adding the following
|
||||
# parameters to the query component of the redirection URI using the
|
||||
# "application/x-www-form-urlencoded" format, per Appendix B.
|
||||
# http://tools.ietf.org/html/rfc6749#appendix-B
|
||||
|
||||
# Note that the correct parameters to be added are automatically
|
||||
# populated through the use of specific exceptions.
|
||||
|
||||
# REQUIRED.
|
||||
if request.response_type is None:
|
||||
raise errors.MissingResponseTypeError(request=request)
|
||||
# Value MUST be set to "code".
|
||||
elif request.response_type != 'code':
|
||||
raise errors.UnsupportedResponseTypeError(request=request)
|
||||
|
||||
if not self.request_validator.validate_response_type(request.client_id,
|
||||
request.response_type,
|
||||
request.client, request):
|
||||
|
||||
log.debug('Client %s is not authorized to use response_type %s.',
|
||||
request.client_id, request.response_type)
|
||||
raise errors.UnauthorizedClientError(request=request)
|
||||
|
||||
# OPTIONAL. The scope of the access request as described by Section 3.3
|
||||
# http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
self.validate_scopes(request)
|
||||
|
||||
return request.scopes, {
|
||||
'client_id': request.client_id,
|
||||
'redirect_uri': request.redirect_uri,
|
||||
'response_type': request.response_type,
|
||||
'state': request.state,
|
||||
'request': request,
|
||||
}
|
||||
|
||||
def validate_token_request(self, request):
|
||||
# REQUIRED. Value MUST be set to "authorization_code".
|
||||
if request.grant_type != 'authorization_code':
|
||||
raise errors.UnsupportedGrantTypeError(request=request)
|
||||
|
||||
if request.code is None:
|
||||
raise errors.InvalidRequestError(
|
||||
description='Missing code parameter.', request=request)
|
||||
|
||||
for param in ('client_id', 'grant_type', 'redirect_uri'):
|
||||
if param in request.duplicate_params:
|
||||
raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param,
|
||||
request=request)
|
||||
|
||||
if self.request_validator.client_authentication_required(request):
|
||||
# If the client type is confidential or the client was issued client
|
||||
# credentials (or assigned other authentication requirements), the
|
||||
# client MUST authenticate with the authorization server as described
|
||||
# in Section 3.2.1.
|
||||
# http://tools.ietf.org/html/rfc6749#section-3.2.1
|
||||
if not self.request_validator.authenticate_client(request):
|
||||
log.debug('Client authentication failed, %r.', request)
|
||||
raise errors.InvalidClientError(request=request)
|
||||
elif not self.request_validator.authenticate_client_id(request.client_id, request):
|
||||
# REQUIRED, if the client is not authenticating with the
|
||||
# authorization server as described in Section 3.2.1.
|
||||
# http://tools.ietf.org/html/rfc6749#section-3.2.1
|
||||
log.debug('Client authentication failed, %r.', request)
|
||||
raise errors.InvalidClientError(request=request)
|
||||
|
||||
if not hasattr(request.client, 'client_id'):
|
||||
raise NotImplementedError('Authenticate client must set the '
|
||||
'request.client.client_id attribute '
|
||||
'in authenticate_client.')
|
||||
|
||||
# Ensure client is authorized use of this grant type
|
||||
self.validate_grant_type(request)
|
||||
|
||||
# REQUIRED. The authorization code received from the
|
||||
# authorization server.
|
||||
if not self.request_validator.validate_code(request.client_id,
|
||||
request.code, request.client, request):
|
||||
log.debug('Client, %r (%r), is not allowed access to scopes %r.',
|
||||
request.client_id, request.client, request.scopes)
|
||||
raise errors.InvalidGrantError(request=request)
|
||||
|
||||
for attr in ('user', 'scopes'):
|
||||
if getattr(request, attr, None) is None:
|
||||
log.debug('request.%s was not set on code validation.', attr)
|
||||
|
||||
# REQUIRED, if the "redirect_uri" parameter was included in the
|
||||
# authorization request as described in Section 4.1.1, and their
|
||||
# values MUST be identical.
|
||||
if not self.request_validator.confirm_redirect_uri(request.client_id, request.code,
|
||||
request.redirect_uri, request.client):
|
||||
log.debug('Redirect_uri (%r) invalid for client %r (%r).',
|
||||
request.redirect_uri, request.client_id, request.client)
|
||||
raise errors.AccessDeniedError(request=request)
|
41
lib/oauthlib/oauth2/rfc6749/grant_types/base.py
Normal file
41
lib/oauthlib/oauth2/rfc6749/grant_types/base.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib.oauth2.rfc6749 import errors, utils
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GrantTypeBase(object):
|
||||
error_uri = None
|
||||
request_validator = None
|
||||
|
||||
def create_authorization_response(self, request, token_handler):
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def create_token_response(self, request, token_handler):
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def validate_grant_type(self, request):
|
||||
client_id = getattr(request, 'client_id', None)
|
||||
if not self.request_validator.validate_grant_type(client_id,
|
||||
request.grant_type, request.client, request):
|
||||
log.debug('Unauthorized from %r (%r) access to grant type %s.',
|
||||
request.client_id, request.client, request.grant_type)
|
||||
raise errors.UnauthorizedClientError(request=request)
|
||||
|
||||
def validate_scopes(self, request):
|
||||
if not request.scopes:
|
||||
request.scopes = utils.scope_to_list(request.scope) or utils.scope_to_list(
|
||||
self.request_validator.get_default_scopes(request.client_id, request))
|
||||
log.debug('Validating access to scopes %r for client %r (%r).',
|
||||
request.scopes, request.client_id, request.client)
|
||||
if not self.request_validator.validate_scopes(request.client_id,
|
||||
request.scopes, request.client, request):
|
||||
raise errors.InvalidScopeError(request=request)
|
112
lib/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
Normal file
112
lib/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
Normal file
|
@ -0,0 +1,112 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from .base import GrantTypeBase
|
||||
from .. import errors
|
||||
from ..request_validator import RequestValidator
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClientCredentialsGrant(GrantTypeBase):
|
||||
|
||||
"""`Client Credentials Grant`_
|
||||
|
||||
The client can request an access token using only its client
|
||||
credentials (or other supported means of authentication) when the
|
||||
client is requesting access to the protected resources under its
|
||||
control, or those of another resource owner that have been previously
|
||||
arranged with the authorization server (the method of which is beyond
|
||||
the scope of this specification).
|
||||
|
||||
The client credentials grant type MUST only be used by confidential
|
||||
clients::
|
||||
|
||||
+---------+ +---------------+
|
||||
: : : :
|
||||
: :>-- A - Client Authentication --->: Authorization :
|
||||
: Client : : Server :
|
||||
: :<-- B ---- Access Token ---------<: :
|
||||
: : : :
|
||||
+---------+ +---------------+
|
||||
|
||||
Figure 6: Client Credentials Flow
|
||||
|
||||
The flow illustrated in Figure 6 includes the following steps:
|
||||
|
||||
(A) The client authenticates with the authorization server and
|
||||
requests an access token from the token endpoint.
|
||||
|
||||
(B) The authorization server authenticates the client, and if valid,
|
||||
issues an access token.
|
||||
|
||||
.. _`Client Credentials Grant`: http://tools.ietf.org/html/rfc6749#section-4.4
|
||||
"""
|
||||
|
||||
def __init__(self, request_validator=None):
|
||||
self.request_validator = request_validator or RequestValidator()
|
||||
|
||||
def create_token_response(self, request, token_handler):
|
||||
"""Return token or error in JSON format.
|
||||
|
||||
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`_.
|
||||
|
||||
.. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
|
||||
.. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
|
||||
"""
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'no-store',
|
||||
'Pragma': 'no-cache',
|
||||
}
|
||||
try:
|
||||
log.debug('Validating access token request, %r.', request)
|
||||
self.validate_token_request(request)
|
||||
except errors.OAuth2Error as e:
|
||||
log.debug('Client error in token request. %s.', e)
|
||||
return headers, e.json, e.status_code
|
||||
|
||||
token = token_handler.create_token(request, refresh_token=False)
|
||||
log.debug('Issuing token to client id %r (%r), %r.',
|
||||
request.client_id, request.client, token)
|
||||
return headers, json.dumps(token), 200
|
||||
|
||||
def validate_token_request(self, request):
|
||||
if not getattr(request, 'grant_type', None):
|
||||
raise errors.InvalidRequestError('Request is missing grant type.',
|
||||
request=request)
|
||||
|
||||
if not request.grant_type == 'client_credentials':
|
||||
raise errors.UnsupportedGrantTypeError(request=request)
|
||||
|
||||
for param in ('grant_type', 'scope'):
|
||||
if param in request.duplicate_params:
|
||||
raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param,
|
||||
request=request)
|
||||
|
||||
log.debug('Authenticating client, %r.', request)
|
||||
if not self.request_validator.authenticate_client(request):
|
||||
log.debug('Client authentication failed, %r.', request)
|
||||
raise errors.InvalidClientError(request=request)
|
||||
else:
|
||||
if not hasattr(request.client, 'client_id'):
|
||||
raise NotImplementedError('Authenticate client must set the '
|
||||
'request.client.client_id attribute '
|
||||
'in authenticate_client.')
|
||||
# Ensure client is authorized use of this grant type
|
||||
self.validate_grant_type(request)
|
||||
|
||||
log.debug('Authorizing access to user %r.', request.user)
|
||||
request.client_id = request.client_id or request.client.client_id
|
||||
self.validate_scopes(request)
|
345
lib/oauthlib/oauth2/rfc6749/grant_types/implicit.py
Normal file
345
lib/oauthlib/oauth2/rfc6749/grant_types/implicit.py
Normal file
|
@ -0,0 +1,345 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib import common
|
||||
from oauthlib.uri_validate import is_absolute_uri
|
||||
|
||||
from .base import GrantTypeBase
|
||||
from .. import errors
|
||||
from ..request_validator import RequestValidator
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ImplicitGrant(GrantTypeBase):
|
||||
|
||||
"""`Implicit Grant`_
|
||||
|
||||
The implicit grant type is used to obtain access tokens (it does not
|
||||
support the issuance of refresh tokens) and is optimized for public
|
||||
clients known to operate a particular redirection URI. These clients
|
||||
are typically implemented in a browser using a scripting language
|
||||
such as JavaScript.
|
||||
|
||||
Unlike the authorization code grant type, in which the client makes
|
||||
separate requests for authorization and for an access token, the
|
||||
client receives the access token as the result of the authorization
|
||||
request.
|
||||
|
||||
The implicit grant type does not include client authentication, and
|
||||
relies on the presence of the resource owner and the registration of
|
||||
the redirection URI. Because the access token is encoded into the
|
||||
redirection URI, it may be exposed to the resource owner and other
|
||||
applications residing on the same device::
|
||||
|
||||
+----------+
|
||||
| Resource |
|
||||
| Owner |
|
||||
| |
|
||||
+----------+
|
||||
^
|
||||
|
|
||||
(B)
|
||||
+----|-----+ Client Identifier +---------------+
|
||||
| -+----(A)-- & Redirection URI --->| |
|
||||
| User- | | Authorization |
|
||||
| Agent -|----(B)-- User authenticates -->| Server |
|
||||
| | | |
|
||||
| |<---(C)--- Redirection URI ----<| |
|
||||
| | with Access Token +---------------+
|
||||
| | in Fragment
|
||||
| | +---------------+
|
||||
| |----(D)--- Redirection URI ---->| Web-Hosted |
|
||||
| | without Fragment | Client |
|
||||
| | | Resource |
|
||||
| (F) |<---(E)------- Script ---------<| |
|
||||
| | +---------------+
|
||||
+-|--------+
|
||||
| |
|
||||
(A) (G) Access Token
|
||||
| |
|
||||
^ v
|
||||
+---------+
|
||||
| |
|
||||
| Client |
|
||||
| |
|
||||
+---------+
|
||||
|
||||
Note: The lines illustrating steps (A) and (B) are broken into two
|
||||
parts as they pass through the user-agent.
|
||||
|
||||
Figure 4: Implicit Grant Flow
|
||||
|
||||
The flow illustrated in Figure 4 includes the following steps:
|
||||
|
||||
(A) The client initiates the flow by directing the resource owner's
|
||||
user-agent to the authorization endpoint. The client includes
|
||||
its client identifier, requested scope, local state, and a
|
||||
redirection URI to which the authorization server will send the
|
||||
user-agent back once access is granted (or denied).
|
||||
|
||||
(B) The authorization server authenticates the resource owner (via
|
||||
the user-agent) and establishes whether the resource owner
|
||||
grants or denies the client's access request.
|
||||
|
||||
(C) Assuming the resource owner grants access, the authorization
|
||||
server redirects the user-agent back to the client using the
|
||||
redirection URI provided earlier. The redirection URI includes
|
||||
the access token in the URI fragment.
|
||||
|
||||
(D) The user-agent follows the redirection instructions by making a
|
||||
request to the web-hosted client resource (which does not
|
||||
include the fragment per [RFC2616]). The user-agent retains the
|
||||
fragment information locally.
|
||||
|
||||
(E) The web-hosted client resource returns a web page (typically an
|
||||
HTML document with an embedded script) capable of accessing the
|
||||
full redirection URI including the fragment retained by the
|
||||
user-agent, and extracting the access token (and other
|
||||
parameters) contained in the fragment.
|
||||
|
||||
(F) The user-agent executes the script provided by the web-hosted
|
||||
client resource locally, which extracts the access token.
|
||||
|
||||
(G) The user-agent passes the access token to the client.
|
||||
|
||||
See `Section 10.3`_ and `Section 10.16`_ for important security considerations
|
||||
when using the implicit grant.
|
||||
|
||||
.. _`Implicit Grant`: http://tools.ietf.org/html/rfc6749#section-4.2
|
||||
.. _`Section 10.3`: http://tools.ietf.org/html/rfc6749#section-10.3
|
||||
.. _`Section 10.16`: http://tools.ietf.org/html/rfc6749#section-10.16
|
||||
"""
|
||||
|
||||
def __init__(self, request_validator=None):
|
||||
self.request_validator = request_validator or RequestValidator()
|
||||
|
||||
def create_authorization_response(self, request, token_handler):
|
||||
"""Create an authorization response.
|
||||
The client constructs the request URI by adding the following
|
||||
parameters to the query component of the authorization endpoint URI
|
||||
using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
|
||||
|
||||
response_type
|
||||
REQUIRED. Value MUST be set to "token".
|
||||
|
||||
client_id
|
||||
REQUIRED. The client identifier as described in `Section 2.2`_.
|
||||
|
||||
redirect_uri
|
||||
OPTIONAL. As described in `Section 3.1.2`_.
|
||||
|
||||
scope
|
||||
OPTIONAL. The scope of the access request as described by
|
||||
`Section 3.3`_.
|
||||
|
||||
state
|
||||
RECOMMENDED. An opaque value used by the client to maintain
|
||||
state between the request and callback. The authorization
|
||||
server includes this value when redirecting the user-agent back
|
||||
to the client. The parameter SHOULD be used for preventing
|
||||
cross-site request forgery as described in `Section 10.12`_.
|
||||
|
||||
The authorization server validates the request to ensure that all
|
||||
required parameters are present and valid. The authorization server
|
||||
MUST verify that the redirection URI to which it will redirect the
|
||||
access token matches a redirection URI registered by the client as
|
||||
described in `Section 3.1.2`_.
|
||||
|
||||
.. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
|
||||
.. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
.. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
|
||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
||||
"""
|
||||
return self.create_token_response(request, token_handler)
|
||||
|
||||
def create_token_response(self, request, token_handler):
|
||||
"""Return token or error embedded in the URI fragment.
|
||||
|
||||
If the resource owner grants the access request, the authorization
|
||||
server issues an access token and delivers it to the client by adding
|
||||
the following parameters to the fragment component of the redirection
|
||||
URI using the "application/x-www-form-urlencoded" format, per
|
||||
`Appendix B`_:
|
||||
|
||||
access_token
|
||||
REQUIRED. The access token issued by the authorization server.
|
||||
|
||||
token_type
|
||||
REQUIRED. The type of the token issued as described in
|
||||
`Section 7.1`_. Value is case insensitive.
|
||||
|
||||
expires_in
|
||||
RECOMMENDED. 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
|
||||
OPTIONAL, if identical to the scope requested by the client;
|
||||
otherwise, REQUIRED. The scope of the access token as
|
||||
described by `Section 3.3`_.
|
||||
|
||||
state
|
||||
REQUIRED if the "state" parameter was present in the client
|
||||
authorization request. The exact value received from the
|
||||
client.
|
||||
|
||||
The authorization server MUST NOT issue a refresh token.
|
||||
|
||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
.. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
|
||||
"""
|
||||
try:
|
||||
# request.scopes is only mandated in post auth and both pre and
|
||||
# post auth use validate_authorization_request
|
||||
if not request.scopes:
|
||||
raise ValueError('Scopes must be set on post auth.')
|
||||
|
||||
self.validate_token_request(request)
|
||||
|
||||
# If the request fails due to a missing, invalid, or mismatching
|
||||
# redirection URI, or if the client identifier is missing or invalid,
|
||||
# the authorization server SHOULD inform the resource owner of the
|
||||
# error and MUST NOT automatically redirect the user-agent to the
|
||||
# invalid redirection URI.
|
||||
except errors.FatalClientError as e:
|
||||
log.debug('Fatal client error during validation of %r. %r.',
|
||||
request, e)
|
||||
raise
|
||||
|
||||
# If the resource owner denies the access request or if the request
|
||||
# fails for reasons other than a missing or invalid redirection URI,
|
||||
# the authorization server informs the client by adding the following
|
||||
# parameters to the fragment component of the redirection URI using the
|
||||
# "application/x-www-form-urlencoded" format, per Appendix B:
|
||||
# http://tools.ietf.org/html/rfc6749#appendix-B
|
||||
except errors.OAuth2Error as e:
|
||||
log.debug('Client error during validation of %r. %r.', request, e)
|
||||
return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples,
|
||||
fragment=True)}, None, 302
|
||||
|
||||
token = token_handler.create_token(request, refresh_token=False)
|
||||
return {'Location': common.add_params_to_uri(request.redirect_uri, token.items(),
|
||||
fragment=True)}, None, 302
|
||||
|
||||
def validate_authorization_request(self, request):
|
||||
return self.validate_token_request(request)
|
||||
|
||||
def validate_token_request(self, request):
|
||||
"""Check the token request for normal and fatal errors.
|
||||
|
||||
This method is very similar to validate_authorization_request in
|
||||
the AuthorizationCodeGrant but differ in a few subtle areas.
|
||||
|
||||
A normal error could be a missing response_type parameter or the client
|
||||
attempting to access scope it is not allowed to ask authorization for.
|
||||
Normal errors can safely be included in the redirection URI and
|
||||
sent back to the client.
|
||||
|
||||
Fatal errors occur when the client_id or redirect_uri is invalid or
|
||||
missing. These must be caught by the provider and handled, how this
|
||||
is done is outside of the scope of OAuthLib but showing an error
|
||||
page describing the issue is a good idea.
|
||||
"""
|
||||
|
||||
# First check for fatal errors
|
||||
|
||||
# If the request fails due to a missing, invalid, or mismatching
|
||||
# redirection URI, or if the client identifier is missing or invalid,
|
||||
# the authorization server SHOULD inform the resource owner of the
|
||||
# error and MUST NOT automatically redirect the user-agent to the
|
||||
# invalid redirection URI.
|
||||
|
||||
# First check duplicate parameters
|
||||
for param in ('client_id', 'response_type', 'redirect_uri', 'scope', 'state'):
|
||||
try:
|
||||
duplicate_params = request.duplicate_params
|
||||
except ValueError:
|
||||
raise errors.InvalidRequestFatalError(description='Unable to parse query string', request=request)
|
||||
if param in duplicate_params:
|
||||
raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request)
|
||||
|
||||
# REQUIRED. The client identifier as described in Section 2.2.
|
||||
# http://tools.ietf.org/html/rfc6749#section-2.2
|
||||
if not request.client_id:
|
||||
raise errors.MissingClientIdError(request=request)
|
||||
|
||||
if not self.request_validator.validate_client_id(request.client_id, request):
|
||||
raise errors.InvalidClientIdError(request=request)
|
||||
|
||||
# OPTIONAL. As described in Section 3.1.2.
|
||||
# http://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||
if request.redirect_uri is not None:
|
||||
request.using_default_redirect_uri = False
|
||||
log.debug('Using provided redirect_uri %s', request.redirect_uri)
|
||||
if not is_absolute_uri(request.redirect_uri):
|
||||
raise errors.InvalidRedirectURIError(request=request)
|
||||
|
||||
# The authorization server MUST verify that the redirection URI
|
||||
# to which it will redirect the access token matches a
|
||||
# redirection URI registered by the client as described in
|
||||
# Section 3.1.2.
|
||||
# http://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||
if not self.request_validator.validate_redirect_uri(
|
||||
request.client_id, request.redirect_uri, request):
|
||||
raise errors.MismatchingRedirectURIError(request=request)
|
||||
else:
|
||||
request.redirect_uri = self.request_validator.get_default_redirect_uri(
|
||||
request.client_id, request)
|
||||
request.using_default_redirect_uri = True
|
||||
log.debug('Using default redirect_uri %s.', request.redirect_uri)
|
||||
if not request.redirect_uri:
|
||||
raise errors.MissingRedirectURIError(request=request)
|
||||
if not is_absolute_uri(request.redirect_uri):
|
||||
raise errors.InvalidRedirectURIError(request=request)
|
||||
|
||||
# Then check for normal errors.
|
||||
|
||||
# If the resource owner denies the access request or if the request
|
||||
# fails for reasons other than a missing or invalid redirection URI,
|
||||
# the authorization server informs the client by adding the following
|
||||
# parameters to the fragment component of the redirection URI using the
|
||||
# "application/x-www-form-urlencoded" format, per Appendix B.
|
||||
# http://tools.ietf.org/html/rfc6749#appendix-B
|
||||
|
||||
# Note that the correct parameters to be added are automatically
|
||||
# populated through the use of specific exceptions
|
||||
|
||||
# REQUIRED.
|
||||
if request.response_type is None:
|
||||
raise errors.MissingResponseTypeError(request=request)
|
||||
# Value MUST be set to "token".
|
||||
elif request.response_type != 'token':
|
||||
raise errors.UnsupportedResponseTypeError(request=request)
|
||||
|
||||
log.debug('Validating use of response_type token for client %r (%r).',
|
||||
request.client_id, request.client)
|
||||
if not self.request_validator.validate_response_type(request.client_id,
|
||||
request.response_type,
|
||||
request.client, request):
|
||||
|
||||
log.debug('Client %s is not authorized to use response_type %s.',
|
||||
request.client_id, request.response_type)
|
||||
raise errors.UnauthorizedClientError(request=request)
|
||||
|
||||
# OPTIONAL. The scope of the access request as described by Section 3.3
|
||||
# http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
self.validate_scopes(request)
|
||||
|
||||
return request.scopes, {
|
||||
'client_id': request.client_id,
|
||||
'redirect_uri': request.redirect_uri,
|
||||
'response_type': request.response_type,
|
||||
'state': request.state,
|
||||
'request': request,
|
||||
}
|
116
lib/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
Normal file
116
lib/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
Normal file
|
@ -0,0 +1,116 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from .base import GrantTypeBase
|
||||
from .. import errors, utils
|
||||
from ..request_validator import RequestValidator
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RefreshTokenGrant(GrantTypeBase):
|
||||
|
||||
"""`Refresh token grant`_
|
||||
|
||||
.. _`Refresh token grant`: http://tools.ietf.org/html/rfc6749#section-6
|
||||
"""
|
||||
|
||||
def __init__(self, request_validator=None, issue_new_refresh_tokens=True):
|
||||
self.request_validator = request_validator or RequestValidator()
|
||||
self.issue_new_refresh_tokens = issue_new_refresh_tokens
|
||||
|
||||
def create_token_response(self, request, token_handler):
|
||||
"""Create a new access token from a refresh_token.
|
||||
|
||||
If valid and authorized, the authorization server issues an access
|
||||
token as described in `Section 5.1`_. If the request failed
|
||||
verification or is invalid, the authorization server returns an error
|
||||
response as described in `Section 5.2`_.
|
||||
|
||||
The authorization server MAY issue a new refresh token, in which case
|
||||
the client MUST discard the old refresh token and replace it with the
|
||||
new refresh token. The authorization server MAY revoke the old
|
||||
refresh token after issuing a new refresh token to the client. If a
|
||||
new refresh token is issued, the refresh token scope MUST be
|
||||
identical to that of the refresh token included by the client in the
|
||||
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
|
||||
"""
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'no-store',
|
||||
'Pragma': 'no-cache',
|
||||
}
|
||||
try:
|
||||
log.debug('Validating refresh token request, %r.', request)
|
||||
self.validate_token_request(request)
|
||||
except errors.OAuth2Error as e:
|
||||
return headers, e.json, e.status_code
|
||||
|
||||
token = token_handler.create_token(request,
|
||||
refresh_token=self.issue_new_refresh_tokens)
|
||||
log.debug('Issuing new token to client id %r (%r), %r.',
|
||||
request.client_id, request.client, token)
|
||||
return headers, json.dumps(token), 200
|
||||
|
||||
def validate_token_request(self, request):
|
||||
# REQUIRED. Value MUST be set to "refresh_token".
|
||||
if request.grant_type != 'refresh_token':
|
||||
raise errors.UnsupportedGrantTypeError(request=request)
|
||||
|
||||
if request.refresh_token is None:
|
||||
raise errors.InvalidRequestError(
|
||||
description='Missing refresh token parameter.',
|
||||
request=request)
|
||||
|
||||
# Because refresh tokens are typically long-lasting credentials used to
|
||||
# request additional access tokens, the refresh token is bound to the
|
||||
# client to which it was issued. If the client type is confidential or
|
||||
# the client was issued client credentials (or assigned other
|
||||
# authentication requirements), the client MUST authenticate with the
|
||||
# authorization server as described in Section 3.2.1.
|
||||
# http://tools.ietf.org/html/rfc6749#section-3.2.1
|
||||
if self.request_validator.client_authentication_required(request):
|
||||
log.debug('Authenticating client, %r.', request)
|
||||
if not self.request_validator.authenticate_client(request):
|
||||
log.debug('Invalid client (%r), denying access.', request)
|
||||
raise errors.InvalidClientError(request=request)
|
||||
elif not self.request_validator.authenticate_client_id(request.client_id, request):
|
||||
log.debug('Client authentication failed, %r.', request)
|
||||
raise errors.InvalidClientError(request=request)
|
||||
|
||||
# Ensure client is authorized use of this grant type
|
||||
self.validate_grant_type(request)
|
||||
|
||||
# REQUIRED. The refresh token issued to the client.
|
||||
log.debug('Validating refresh token %s for client %r.',
|
||||
request.refresh_token, request.client)
|
||||
if not self.request_validator.validate_refresh_token(
|
||||
request.refresh_token, request.client, request):
|
||||
log.debug('Invalid refresh token, %s, for client %r.',
|
||||
request.refresh_token, request.client)
|
||||
raise errors.InvalidGrantError(request=request)
|
||||
|
||||
original_scopes = utils.scope_to_list(
|
||||
self.request_validator.get_original_scopes(
|
||||
request.refresh_token, request))
|
||||
|
||||
if request.scope:
|
||||
request.scopes = utils.scope_to_list(request.scope)
|
||||
if (not all((s in original_scopes for s in request.scopes))
|
||||
and not self.request_validator.is_within_original_scope(
|
||||
request.scopes, request.refresh_token, request)):
|
||||
log.debug('Refresh token %s lack requested scopes, %r.',
|
||||
request.refresh_token, request.scopes)
|
||||
raise errors.InvalidScopeError(request=request)
|
||||
else:
|
||||
request.scopes = original_scopes
|
|
@ -0,0 +1,194 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from .base import GrantTypeBase
|
||||
from .. import errors
|
||||
from ..request_validator import RequestValidator
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase):
|
||||
|
||||
"""`Resource Owner Password Credentials Grant`_
|
||||
|
||||
The resource owner password credentials grant type is suitable in
|
||||
cases where the resource owner has a trust relationship with the
|
||||
client, such as the device operating system or a highly privileged
|
||||
application. The authorization server should take special care when
|
||||
enabling this grant type and only allow it when other flows are not
|
||||
viable.
|
||||
|
||||
This grant type is suitable for clients capable of obtaining the
|
||||
resource owner's credentials (username and password, typically using
|
||||
an interactive form). It is also used to migrate existing clients
|
||||
using direct authentication schemes such as HTTP Basic or Digest
|
||||
authentication to OAuth by converting the stored credentials to an
|
||||
access token::
|
||||
|
||||
+----------+
|
||||
| Resource |
|
||||
| Owner |
|
||||
| |
|
||||
+----------+
|
||||
v
|
||||
| Resource Owner
|
||||
(A) Password Credentials
|
||||
|
|
||||
v
|
||||
+---------+ +---------------+
|
||||
| |>--(B)---- Resource Owner ------->| |
|
||||
| | Password Credentials | Authorization |
|
||||
| Client | | Server |
|
||||
| |<--(C)---- Access Token ---------<| |
|
||||
| | (w/ Optional Refresh Token) | |
|
||||
+---------+ +---------------+
|
||||
|
||||
Figure 5: Resource Owner Password Credentials Flow
|
||||
|
||||
The flow illustrated in Figure 5 includes the following steps:
|
||||
|
||||
(A) The resource owner provides the client with its username and
|
||||
password.
|
||||
|
||||
(B) The client requests an access token from the authorization
|
||||
server's token endpoint by including the credentials received
|
||||
from the resource owner. When making the request, the client
|
||||
authenticates with the authorization server.
|
||||
|
||||
(C) The authorization server authenticates the client and validates
|
||||
the resource owner credentials, and if valid, issues an access
|
||||
token.
|
||||
|
||||
.. _`Resource Owner Password Credentials Grant`: http://tools.ietf.org/html/rfc6749#section-4.3
|
||||
"""
|
||||
|
||||
def __init__(self, request_validator=None, refresh_token=True):
|
||||
"""
|
||||
If the refresh_token keyword argument is False, do not return
|
||||
a refresh token in the response.
|
||||
"""
|
||||
self.request_validator = request_validator or RequestValidator()
|
||||
self.refresh_token = refresh_token
|
||||
|
||||
def create_token_response(self, request, token_handler):
|
||||
"""Return token or error in json format.
|
||||
|
||||
If the access token request is valid and authorized, the
|
||||
authorization server issues an access token and optional refresh
|
||||
token as described in `Section 5.1`_. If the request failed client
|
||||
authentication or is invalid, the authorization server returns an
|
||||
error response as described in `Section 5.2`_.
|
||||
|
||||
.. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
|
||||
.. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
|
||||
"""
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'no-store',
|
||||
'Pragma': 'no-cache',
|
||||
}
|
||||
try:
|
||||
if self.request_validator.client_authentication_required(request):
|
||||
log.debug('Authenticating client, %r.', request)
|
||||
if not self.request_validator.authenticate_client(request):
|
||||
log.debug('Client authentication failed, %r.', request)
|
||||
raise errors.InvalidClientError(request=request)
|
||||
elif not self.request_validator.authenticate_client_id(request.client_id, request):
|
||||
log.debug('Client authentication failed, %r.', request)
|
||||
raise errors.InvalidClientError(request=request)
|
||||
log.debug('Validating access token request, %r.', request)
|
||||
self.validate_token_request(request)
|
||||
except errors.OAuth2Error as e:
|
||||
log.debug('Client error in token request, %s.', e)
|
||||
return headers, e.json, e.status_code
|
||||
|
||||
token = token_handler.create_token(request, self.refresh_token)
|
||||
log.debug('Issuing token %r to client id %r (%r) and username %s.',
|
||||
token, request.client_id, request.client, request.username)
|
||||
return headers, json.dumps(token), 200
|
||||
|
||||
def validate_token_request(self, request):
|
||||
"""
|
||||
The client makes a request to the token endpoint by adding the
|
||||
following parameters using the "application/x-www-form-urlencoded"
|
||||
format per Appendix B with a character encoding of UTF-8 in the HTTP
|
||||
request entity-body:
|
||||
|
||||
grant_type
|
||||
REQUIRED. Value MUST be set to "password".
|
||||
|
||||
username
|
||||
REQUIRED. The resource owner username.
|
||||
|
||||
password
|
||||
REQUIRED. The resource owner password.
|
||||
|
||||
scope
|
||||
OPTIONAL. The scope of the access request as described by
|
||||
`Section 3.3`_.
|
||||
|
||||
If the client type is confidential or the client was issued client
|
||||
credentials (or assigned other authentication requirements), the
|
||||
client MUST authenticate with the authorization server as described
|
||||
in `Section 3.2.1`_.
|
||||
|
||||
The authorization server MUST:
|
||||
|
||||
o require client authentication for confidential clients or for any
|
||||
client that was issued client credentials (or with other
|
||||
authentication requirements),
|
||||
|
||||
o authenticate the client if client authentication is included, and
|
||||
|
||||
o validate the resource owner password credentials using its
|
||||
existing password validation algorithm.
|
||||
|
||||
Since this access token request utilizes the resource owner's
|
||||
password, the authorization server MUST protect the endpoint against
|
||||
brute force attacks (e.g., using rate-limitation or generating
|
||||
alerts).
|
||||
|
||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
.. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
|
||||
"""
|
||||
for param in ('grant_type', 'username', 'password'):
|
||||
if not getattr(request, param, None):
|
||||
raise errors.InvalidRequestError(
|
||||
'Request is missing %s parameter.' % param, request=request)
|
||||
|
||||
for param in ('grant_type', 'username', 'password', 'scope'):
|
||||
if param in request.duplicate_params:
|
||||
raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param, request=request)
|
||||
|
||||
# This error should rarely (if ever) occur if requests are routed to
|
||||
# grant type handlers based on the grant_type parameter.
|
||||
if not request.grant_type == 'password':
|
||||
raise errors.UnsupportedGrantTypeError(request=request)
|
||||
|
||||
log.debug('Validating username %s.', request.username)
|
||||
if not self.request_validator.validate_user(request.username,
|
||||
request.password, request.client, request):
|
||||
raise errors.InvalidGrantError(
|
||||
'Invalid credentials given.', request=request)
|
||||
else:
|
||||
if not hasattr(request.client, 'client_id'):
|
||||
raise NotImplementedError(
|
||||
'Validate user must set the '
|
||||
'request.client.client_id attribute '
|
||||
'in authenticate_client.')
|
||||
log.debug('Authorizing access to user %r.', request.user)
|
||||
|
||||
# Ensure client is authorized use of this grant type
|
||||
self.validate_grant_type(request)
|
||||
|
||||
if request.client:
|
||||
request.client_id = request.client_id or request.client.client_id
|
||||
self.validate_scopes(request)
|
406
lib/oauthlib/oauth2/rfc6749/parameters.py
Normal file
406
lib/oauthlib/oauth2/rfc6749/parameters.py
Normal file
|
@ -0,0 +1,406 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.parameters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module contains methods related to `Section 4`_ of the OAuth 2 RFC.
|
||||
|
||||
.. _`Section 4`: http://tools.ietf.org/html/rfc6749#section-4
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
from oauthlib.common import add_params_to_uri, add_params_to_qs, unicode_type
|
||||
from oauthlib.signals import scope_changed
|
||||
from .errors import raise_from_error, MissingTokenError, MissingTokenTypeError
|
||||
from .errors import MismatchingStateError, MissingCodeError
|
||||
from .errors import InsecureTransportError
|
||||
from .tokens import OAuth2Token
|
||||
from .utils import list_to_scope, scope_to_list, is_secure_transport
|
||||
|
||||
|
||||
def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
|
||||
scope=None, state=None, **kwargs):
|
||||
"""Prepare the authorization grant request URI.
|
||||
|
||||
The client constructs the request URI by adding the following
|
||||
parameters to the query component of the authorization endpoint URI
|
||||
using the ``application/x-www-form-urlencoded`` format as defined by
|
||||
[`W3C.REC-html401-19991224`_]:
|
||||
|
||||
:param response_type: To indicate which OAuth 2 grant/flow is required,
|
||||
"code" and "token".
|
||||
:param client_id: The client identifier as described in `Section 2.2`_.
|
||||
:param redirect_uri: The client provided URI to redirect back to after
|
||||
authorization as described in `Section 3.1.2`_.
|
||||
:param scope: The scope of the access request as described by
|
||||
`Section 3.3`_.
|
||||
|
||||
:param state: An opaque value used by the client to maintain
|
||||
state between the request and callback. The authorization
|
||||
server includes this value when redirecting the user-agent
|
||||
back to the client. The parameter SHOULD be used for
|
||||
preventing cross-site request forgery as described in
|
||||
`Section 10.12`_.
|
||||
:param kwargs: Extra arguments to embed in the grant/authorization URL.
|
||||
|
||||
An example of an authorization code grant authorization URL:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
|
||||
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
|
||||
Host: server.example.com
|
||||
|
||||
.. _`W3C.REC-html401-19991224`: http://tools.ietf.org/html/rfc6749#ref-W3C.REC-html401-19991224
|
||||
.. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
|
||||
.. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
.. _`section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
|
||||
"""
|
||||
if not is_secure_transport(uri):
|
||||
raise InsecureTransportError()
|
||||
|
||||
params = [(('response_type', response_type)),
|
||||
(('client_id', client_id))]
|
||||
|
||||
if redirect_uri:
|
||||
params.append(('redirect_uri', redirect_uri))
|
||||
if scope:
|
||||
params.append(('scope', list_to_scope(scope)))
|
||||
if state:
|
||||
params.append(('state', state))
|
||||
|
||||
for k in kwargs:
|
||||
if kwargs[k]:
|
||||
params.append((unicode_type(k), kwargs[k]))
|
||||
|
||||
return add_params_to_uri(uri, params)
|
||||
|
||||
|
||||
def prepare_token_request(grant_type, body='', **kwargs):
|
||||
"""Prepare the access token request.
|
||||
|
||||
The client makes a request to the token endpoint by adding the
|
||||
following parameters using the ``application/x-www-form-urlencoded``
|
||||
format in the HTTP request entity-body:
|
||||
|
||||
:param grant_type: To indicate grant type being used, i.e. "password",
|
||||
"authorization_code" or "client_credentials".
|
||||
:param body: Existing request body to embed parameters in.
|
||||
:param code: If using authorization code grant, pass the previously
|
||||
obtained authorization code as the ``code`` argument.
|
||||
:param redirect_uri: If the "redirect_uri" parameter was included in the
|
||||
authorization request as described in
|
||||
`Section 4.1.1`_, and their values MUST be identical.
|
||||
:param kwargs: Extra arguments to embed in the request body.
|
||||
|
||||
An example of an authorization code token request body:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
|
||||
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
|
||||
|
||||
.. _`Section 4.1.1`: http://tools.ietf.org/html/rfc6749#section-4.1.1
|
||||
"""
|
||||
params = [('grant_type', grant_type)]
|
||||
|
||||
if 'scope' in kwargs:
|
||||
kwargs['scope'] = list_to_scope(kwargs['scope'])
|
||||
|
||||
for k in kwargs:
|
||||
if kwargs[k]:
|
||||
params.append((unicode_type(k), kwargs[k]))
|
||||
|
||||
return add_params_to_qs(body, params)
|
||||
|
||||
|
||||
def prepare_token_revocation_request(url, token, token_type_hint="access_token",
|
||||
callback=None, body='', **kwargs):
|
||||
"""Prepare a token revocation request.
|
||||
|
||||
The client constructs the request by including the following parameters
|
||||
using the "application/x-www-form-urlencoded" format in the HTTP request
|
||||
entity-body:
|
||||
|
||||
token REQUIRED. The token that the client wants to get revoked.
|
||||
|
||||
token_type_hint OPTIONAL. A hint about the type of the token submitted
|
||||
for revocation. Clients MAY pass this parameter in order to help the
|
||||
authorization server to optimize the token lookup. If the server is unable
|
||||
to locate the token using the given hint, it MUST extend its search across
|
||||
all of its supported token types. An authorization server MAY ignore this
|
||||
parameter, particularly if it is able to detect the token type
|
||||
automatically. This specification defines two such values:
|
||||
|
||||
* access_token: An access token as defined in [RFC6749],
|
||||
`Section 1.4`_
|
||||
|
||||
* refresh_token: A refresh token as defined in [RFC6749],
|
||||
`Section 1.5`_
|
||||
|
||||
Specific implementations, profiles, and extensions of this
|
||||
specification MAY define other values for this parameter using the
|
||||
registry defined in `Section 4.1.2`_.
|
||||
|
||||
.. _`Section 1.4`: http://tools.ietf.org/html/rfc6749#section-1.4
|
||||
.. _`Section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5
|
||||
.. _`Section 4.1.2`: http://tools.ietf.org/html/rfc7009#section-4.1.2
|
||||
|
||||
"""
|
||||
if not is_secure_transport(url):
|
||||
raise InsecureTransportError()
|
||||
|
||||
params = [('token', token)]
|
||||
|
||||
if token_type_hint:
|
||||
params.append(('token_type_hint', token_type_hint))
|
||||
|
||||
for k in kwargs:
|
||||
if kwargs[k]:
|
||||
params.append((unicode_type(k), kwargs[k]))
|
||||
|
||||
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
|
||||
if callback:
|
||||
params.append(('callback', callback))
|
||||
return add_params_to_uri(url, params), headers, body
|
||||
else:
|
||||
return url, headers, add_params_to_qs(body, params)
|
||||
|
||||
|
||||
def parse_authorization_code_response(uri, state=None):
|
||||
"""Parse authorization grant response URI into a dict.
|
||||
|
||||
If the resource owner grants the access request, the authorization
|
||||
server issues an authorization code and delivers it to the client by
|
||||
adding the following parameters to the query component of the
|
||||
redirection URI using the ``application/x-www-form-urlencoded`` format:
|
||||
|
||||
**code**
|
||||
REQUIRED. The authorization code generated by the
|
||||
authorization server. The authorization code MUST expire
|
||||
shortly after it is issued to mitigate the risk of leaks. A
|
||||
maximum authorization code lifetime of 10 minutes is
|
||||
RECOMMENDED. The client MUST NOT use the authorization code
|
||||
more than once. If an authorization code is used more than
|
||||
once, the authorization server MUST deny the request and SHOULD
|
||||
revoke (when possible) all tokens previously issued based on
|
||||
that authorization code. The authorization code is bound to
|
||||
the client identifier and redirection URI.
|
||||
|
||||
**state**
|
||||
REQUIRED if the "state" parameter was present in the client
|
||||
authorization request. The exact value received from the
|
||||
client.
|
||||
|
||||
:param uri: The full redirect URL back to the client.
|
||||
:param state: The state parameter from the authorization request.
|
||||
|
||||
For example, the authorization server redirects the user-agent by
|
||||
sending the following HTTP response:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 302 Found
|
||||
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
|
||||
&state=xyz
|
||||
|
||||
"""
|
||||
if not is_secure_transport(uri):
|
||||
raise InsecureTransportError()
|
||||
|
||||
query = urlparse.urlparse(uri).query
|
||||
params = dict(urlparse.parse_qsl(query))
|
||||
|
||||
if not 'code' in params:
|
||||
raise MissingCodeError("Missing code parameter in response.")
|
||||
|
||||
if state and params.get('state', None) != state:
|
||||
raise MismatchingStateError()
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def parse_implicit_response(uri, state=None, scope=None):
|
||||
"""Parse the implicit token response URI into a dict.
|
||||
|
||||
If the resource owner grants the access request, the authorization
|
||||
server issues an access token and delivers it to the client by adding
|
||||
the following parameters to the fragment component of the redirection
|
||||
URI using the ``application/x-www-form-urlencoded`` format:
|
||||
|
||||
**access_token**
|
||||
REQUIRED. The access token issued by the authorization server.
|
||||
|
||||
**token_type**
|
||||
REQUIRED. The type of the token issued as described in
|
||||
Section 7.1. Value is case insensitive.
|
||||
|
||||
**expires_in**
|
||||
RECOMMENDED. 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**
|
||||
OPTIONAL, if identical to the scope requested by the client,
|
||||
otherwise REQUIRED. The scope of the access token as described
|
||||
by Section 3.3.
|
||||
|
||||
**state**
|
||||
REQUIRED if the "state" parameter was present in the client
|
||||
authorization request. The exact value received from the
|
||||
client.
|
||||
|
||||
Similar to the authorization code response, but with a full token provided
|
||||
in the URL fragment:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 302 Found
|
||||
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
|
||||
&state=xyz&token_type=example&expires_in=3600
|
||||
"""
|
||||
if not is_secure_transport(uri):
|
||||
raise InsecureTransportError()
|
||||
|
||||
fragment = urlparse.urlparse(uri).fragment
|
||||
params = dict(urlparse.parse_qsl(fragment, keep_blank_values=True))
|
||||
|
||||
if 'scope' in params:
|
||||
params['scope'] = scope_to_list(params['scope'])
|
||||
|
||||
if 'expires_in' in params:
|
||||
params['expires_at'] = time.time() + int(params['expires_in'])
|
||||
|
||||
if state and params.get('state', None) != state:
|
||||
raise ValueError("Mismatching or missing state in params.")
|
||||
|
||||
params = OAuth2Token(params, old_scope=scope)
|
||||
validate_token_parameters(params)
|
||||
return params
|
||||
|
||||
|
||||
def parse_token_response(body, scope=None):
|
||||
"""Parse the JSON token response body into a dict.
|
||||
|
||||
The authorization server issues an access token and optional refresh
|
||||
token, and constructs the response by adding the following parameters
|
||||
to the entity body of the HTTP response with a 200 (OK) status code:
|
||||
|
||||
access_token
|
||||
REQUIRED. The access token issued by the authorization server.
|
||||
token_type
|
||||
REQUIRED. The type of the token issued as described in
|
||||
`Section 7.1`_. Value is case insensitive.
|
||||
expires_in
|
||||
RECOMMENDED. 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.
|
||||
refresh_token
|
||||
OPTIONAL. The refresh token which can be used to obtain new
|
||||
access tokens using the same authorization grant as described
|
||||
in `Section 6`_.
|
||||
scope
|
||||
OPTIONAL, if identical to the scope requested by the client,
|
||||
otherwise REQUIRED. The scope of the access token as described
|
||||
by `Section 3.3`_.
|
||||
|
||||
The parameters are included in the entity body of the HTTP response
|
||||
using the "application/json" media type as defined by [`RFC4627`_]. The
|
||||
parameters are serialized into a JSON structure by adding each
|
||||
parameter at the highest structure level. Parameter names and string
|
||||
values are included as JSON strings. Numerical values are included
|
||||
as JSON numbers. The order of parameters does not matter and can
|
||||
vary.
|
||||
|
||||
:param body: The full json encoded response body.
|
||||
:param scope: The scope requested during authorization.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Cache-Control: no-store
|
||||
Pragma: no-cache
|
||||
|
||||
{
|
||||
"access_token":"2YotnFZFEjr1zCsicMWpAA",
|
||||
"token_type":"example",
|
||||
"expires_in":3600,
|
||||
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
|
||||
"example_parameter":"example_value"
|
||||
}
|
||||
|
||||
.. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
|
||||
.. _`Section 6`: http://tools.ietf.org/html/rfc6749#section-6
|
||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
.. _`RFC4627`: http://tools.ietf.org/html/rfc4627
|
||||
"""
|
||||
try:
|
||||
params = json.loads(body)
|
||||
except ValueError:
|
||||
|
||||
# Fall back to URL-encoded string, to support old implementations,
|
||||
# including (at time of writing) Facebook. See:
|
||||
# https://github.com/idan/oauthlib/issues/267
|
||||
|
||||
params = dict(urlparse.parse_qsl(body))
|
||||
for key in ('expires_in', 'expires'):
|
||||
if key in params: # cast a couple things to int
|
||||
params[key] = int(params[key])
|
||||
|
||||
if 'scope' in params:
|
||||
params['scope'] = scope_to_list(params['scope'])
|
||||
|
||||
if 'expires' in params:
|
||||
params['expires_in'] = params.pop('expires')
|
||||
|
||||
if 'expires_in' in params:
|
||||
params['expires_at'] = time.time() + int(params['expires_in'])
|
||||
|
||||
params = OAuth2Token(params, old_scope=scope)
|
||||
validate_token_parameters(params)
|
||||
return params
|
||||
|
||||
|
||||
def validate_token_parameters(params):
|
||||
"""Ensures token precence, token type, expiration and scope in params."""
|
||||
if 'error' in params:
|
||||
raise_from_error(params.get('error'), params)
|
||||
|
||||
if not 'access_token' in params:
|
||||
raise MissingTokenError(description="Missing access token parameter.")
|
||||
|
||||
if not 'token_type' in params:
|
||||
if os.environ.get('OAUTHLIB_STRICT_TOKEN_TYPE'):
|
||||
raise MissingTokenTypeError()
|
||||
|
||||
# If the issued access token scope is different from the one requested by
|
||||
# the client, the authorization server MUST include the "scope" response
|
||||
# parameter to inform the client of the actual scope granted.
|
||||
# http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
if params.scope_changed:
|
||||
message = 'Scope has changed from "{old}" to "{new}".'.format(
|
||||
old=params.old_scope, new=params.scope,
|
||||
)
|
||||
scope_changed.send(message=message, old=params.old_scopes, new=params.scopes)
|
||||
if not os.environ.get('OAUTHLIB_RELAX_TOKEN_SCOPE', None):
|
||||
w = Warning(message)
|
||||
w.token = params
|
||||
w.old_scope = params.old_scopes
|
||||
w.new_scope = params.scopes
|
||||
raise w
|
465
lib/oauthlib/oauth2/rfc6749/request_validator.py
Normal file
465
lib/oauthlib/oauth2/rfc6749/request_validator.py
Normal file
|
@ -0,0 +1,465 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RequestValidator(object):
|
||||
|
||||
def client_authentication_required(self, request, *args, **kwargs):
|
||||
"""Determine if client authentication is required for current request.
|
||||
|
||||
According to the rfc6749, client authentication is required in the following cases:
|
||||
- Resource Owner Password Credentials Grant, when Client type is Confidential or when
|
||||
Client was issued client credentials or whenever Client provided client
|
||||
authentication, see `Section 4.3.2`_.
|
||||
- Authorization Code Grant, when Client type is Confidential or when Client was issued
|
||||
client credentials or whenever Client provided client authentication,
|
||||
see `Section 4.1.3`_.
|
||||
- Refresh Token Grant, when Client type is Confidential or when Client was issued
|
||||
client credentials or whenever Client provided client authentication, see
|
||||
`Section 6`_
|
||||
|
||||
:param request: oauthlib.common.Request
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- Authorization Code Grant
|
||||
- Resource Owner Password Credentials Grant
|
||||
- Refresh Token Grant
|
||||
|
||||
.. _`Section 4.3.2`: http://tools.ietf.org/html/rfc6749#section-4.3.2
|
||||
.. _`Section 4.1.3`: http://tools.ietf.org/html/rfc6749#section-4.1.3
|
||||
.. _`Section 6`: http://tools.ietf.org/html/rfc6749#section-6
|
||||
"""
|
||||
return True
|
||||
|
||||
def authenticate_client(self, request, *args, **kwargs):
|
||||
"""Authenticate client through means outside the OAuth 2 spec.
|
||||
|
||||
Means of authentication is negotiated beforehand and may for example
|
||||
be `HTTP Basic Authentication Scheme`_ which utilizes the Authorization
|
||||
header.
|
||||
|
||||
Headers may be accesses through request.headers and parameters found in
|
||||
both body and query can be obtained by direct attribute access, i.e.
|
||||
request.client_id for client_id in the URL query.
|
||||
|
||||
:param request: oauthlib.common.Request
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- Authorization Code Grant
|
||||
- Resource Owner Password Credentials Grant (may be disabled)
|
||||
- Client Credentials Grant
|
||||
- Refresh Token Grant
|
||||
|
||||
.. _`HTTP Basic Authentication Scheme`: http://tools.ietf.org/html/rfc1945#section-11.1
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def authenticate_client_id(self, client_id, request, *args, **kwargs):
|
||||
"""Ensure client_id belong to a non-confidential client.
|
||||
|
||||
A non-confidential client is one that is not required to authenticate
|
||||
through other means, such as using HTTP Basic.
|
||||
|
||||
Note, while not strictly necessary it can often be very convenient
|
||||
to set request.client to the client object associated with the
|
||||
given client_id.
|
||||
|
||||
:param request: oauthlib.common.Request
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- Authorization Code Grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def confirm_redirect_uri(self, client_id, code, redirect_uri, client,
|
||||
*args, **kwargs):
|
||||
"""Ensure that the authorization process represented by this authorization
|
||||
code began with this 'redirect_uri'.
|
||||
|
||||
If the client specifies a redirect_uri when obtaining code then that
|
||||
redirect URI must be bound to the code and verified equal in this
|
||||
method, according to RFC 6749 section 4.1.3. Do not compare against
|
||||
the client's allowed redirect URIs, but against the URI used when the
|
||||
code was saved.
|
||||
|
||||
:param client_id: Unicode client identifier
|
||||
:param code: Unicode authorization_code.
|
||||
:param redirect_uri: Unicode absolute URI
|
||||
:param client: Client object set by you, see authenticate_client.
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- Authorization Code Grant (during token request)
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def get_default_redirect_uri(self, client_id, request, *args, **kwargs):
|
||||
"""Get the default redirect URI for the client.
|
||||
|
||||
:param client_id: Unicode client identifier
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
:rtype: The default redirect URI for the client
|
||||
|
||||
Method is used by:
|
||||
- Authorization Code Grant
|
||||
- Implicit Grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def get_default_scopes(self, client_id, request, *args, **kwargs):
|
||||
"""Get the default scopes for the client.
|
||||
|
||||
:param client_id: Unicode client identifier
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
:rtype: List of default scopes
|
||||
|
||||
Method is used by all core grant types:
|
||||
- Authorization Code Grant
|
||||
- Implicit Grant
|
||||
- Resource Owner Password Credentials Grant
|
||||
- Client Credentials grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def get_original_scopes(self, refresh_token, request, *args, **kwargs):
|
||||
"""Get the list of scopes associated with the refresh token.
|
||||
|
||||
:param refresh_token: Unicode refresh token
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
:rtype: List of scopes.
|
||||
|
||||
Method is used by:
|
||||
- Refresh token grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def is_within_original_scope(self, request_scopes, refresh_token, request, *args, **kwargs):
|
||||
"""Check if requested scopes are within a scope of the refresh token.
|
||||
|
||||
When access tokens are refreshed the scope of the new token
|
||||
needs to be within the scope of the original token. This is
|
||||
ensured by checking that all requested scopes strings are on
|
||||
the list returned by the get_original_scopes. If this check
|
||||
fails, is_within_original_scope is called. The method can be
|
||||
used in situations where returning all valid scopes from the
|
||||
get_original_scopes is not practical.
|
||||
|
||||
:param request_scopes: A list of scopes that were requested by client
|
||||
:param refresh_token: Unicode refresh_token
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- Refresh token grant
|
||||
"""
|
||||
return False
|
||||
|
||||
def invalidate_authorization_code(self, client_id, code, request, *args, **kwargs):
|
||||
"""Invalidate an authorization code after use.
|
||||
|
||||
:param client_id: Unicode client identifier
|
||||
:param code: The authorization code grant (request.code).
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
|
||||
Method is used by:
|
||||
- Authorization Code Grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def revoke_token(self, token, token_type_hint, request, *args, **kwargs):
|
||||
"""Revoke an access or refresh token.
|
||||
|
||||
:param token: The token string.
|
||||
:param token_type_hint: access_token or refresh_token.
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
|
||||
Method is used by:
|
||||
- Revocation Endpoint
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def rotate_refresh_token(self, request):
|
||||
"""Determine whether to rotate the refresh token. Default, yes.
|
||||
|
||||
When access tokens are refreshed the old refresh token can be kept
|
||||
or replaced with a new one (rotated). Return True to rotate and
|
||||
and False for keeping original.
|
||||
|
||||
:param request: oauthlib.common.Request
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- Refresh Token Grant
|
||||
"""
|
||||
return True
|
||||
|
||||
def save_authorization_code(self, client_id, code, request, *args, **kwargs):
|
||||
"""Persist the authorization_code.
|
||||
|
||||
The code should at minimum be stored with:
|
||||
- the client_id (client_id)
|
||||
- the redirect URI used (request.redirect_uri)
|
||||
- a resource owner / user (request.user)
|
||||
- the authorized scopes (request.scopes)
|
||||
- the client state, if given (code.get('state'))
|
||||
|
||||
The 'code' argument is actually a dictionary, containing at least a
|
||||
'code' key with the actual authorization code:
|
||||
|
||||
{'code': 'sdf345jsdf0934f'}
|
||||
|
||||
It may also have a 'state' key containing a nonce for the client, if it
|
||||
chose to send one. That value should be saved and used in
|
||||
'validate_code'.
|
||||
|
||||
:param client_id: Unicode client identifier
|
||||
:param code: A dict of the authorization code grant and, optionally, state.
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
|
||||
Method is used by:
|
||||
- Authorization Code Grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def save_bearer_token(self, token, request, *args, **kwargs):
|
||||
"""Persist the Bearer token.
|
||||
|
||||
The Bearer token should at minimum be associated with:
|
||||
- a client and it's client_id, if available
|
||||
- a resource owner / user (request.user)
|
||||
- authorized scopes (request.scopes)
|
||||
- an expiration time
|
||||
- a refresh token, if issued
|
||||
|
||||
The Bearer token dict may hold a number of items::
|
||||
|
||||
{
|
||||
'token_type': 'Bearer',
|
||||
'access_token': 'askfjh234as9sd8',
|
||||
'expires_in': 3600,
|
||||
'scope': 'string of space separated authorized scopes',
|
||||
'refresh_token': '23sdf876234', # if issued
|
||||
'state': 'given_by_client', # if supplied by client
|
||||
}
|
||||
|
||||
Note that while "scope" is a string-separated list of authorized scopes,
|
||||
the original list is still available in request.scopes
|
||||
|
||||
:param client_id: Unicode client identifier
|
||||
:param token: A Bearer token dict
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
:rtype: The default redirect URI for the client
|
||||
|
||||
Method is used by all core grant types issuing Bearer tokens:
|
||||
- Authorization Code Grant
|
||||
- Implicit Grant
|
||||
- Resource Owner Password Credentials Grant (might not associate a client)
|
||||
- Client Credentials grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def validate_bearer_token(self, token, scopes, request):
|
||||
"""Ensure the Bearer token is valid and authorized access to scopes.
|
||||
|
||||
:param token: A string of random characters.
|
||||
:param scopes: A list of scopes associated with the protected resource.
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
|
||||
A key to OAuth 2 security and restricting impact of leaked tokens is
|
||||
the short expiration time of tokens, *always ensure the token has not
|
||||
expired!*.
|
||||
|
||||
Two different approaches to scope validation:
|
||||
|
||||
1) all(scopes). The token must be authorized access to all scopes
|
||||
associated with the resource. For example, the
|
||||
token has access to ``read-only`` and ``images``,
|
||||
thus the client can view images but not upload new.
|
||||
Allows for fine grained access control through
|
||||
combining various scopes.
|
||||
|
||||
2) any(scopes). The token must be authorized access to one of the
|
||||
scopes associated with the resource. For example,
|
||||
token has access to ``read-only-images``.
|
||||
Allows for fine grained, although arguably less
|
||||
convenient, access control.
|
||||
|
||||
A powerful way to use scopes would mimic UNIX ACLs and see a scope
|
||||
as a group with certain privileges. For a restful API these might
|
||||
map to HTTP verbs instead of read, write and execute.
|
||||
|
||||
Note, the request.user attribute can be set to the resource owner
|
||||
associated with this token. Similarly the request.client and
|
||||
request.scopes attribute can be set to associated client object
|
||||
and authorized scopes. If you then use a decorator such as the
|
||||
one provided for django these attributes will be made available
|
||||
in all protected views as keyword arguments.
|
||||
|
||||
:param token: Unicode Bearer token
|
||||
:param scopes: List of scopes (defined by you)
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
:rtype: True or False
|
||||
|
||||
Method is indirectly used by all core Bearer token issuing grant types:
|
||||
- Authorization Code Grant
|
||||
- Implicit Grant
|
||||
- Resource Owner Password Credentials Grant
|
||||
- Client Credentials Grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def validate_client_id(self, client_id, request, *args, **kwargs):
|
||||
"""Ensure client_id belong to a valid and active client.
|
||||
|
||||
Note, while not strictly necessary it can often be very convenient
|
||||
to set request.client to the client object associated with the
|
||||
given client_id.
|
||||
|
||||
:param request: oauthlib.common.Request
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- Authorization Code Grant
|
||||
- Implicit Grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def validate_code(self, client_id, code, client, request, *args, **kwargs):
|
||||
"""Verify that the authorization_code is valid and assigned to the given
|
||||
client.
|
||||
|
||||
Before returning true, set the following based on the information stored
|
||||
with the code in 'save_authorization_code':
|
||||
|
||||
- request.user
|
||||
- request.state (if given)
|
||||
- request.scopes
|
||||
OBS! The request.user attribute should be set to the resource owner
|
||||
associated with this authorization code. Similarly request.scopes
|
||||
must also be set.
|
||||
|
||||
:param client_id: Unicode client identifier
|
||||
:param code: Unicode authorization code
|
||||
:param client: Client object set by you, see authenticate_client.
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- Authorization Code Grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def validate_grant_type(self, client_id, grant_type, client, request, *args, **kwargs):
|
||||
"""Ensure client is authorized to use the grant_type requested.
|
||||
|
||||
:param client_id: Unicode client identifier
|
||||
:param grant_type: Unicode grant type, i.e. authorization_code, password.
|
||||
:param client: Client object set by you, see authenticate_client.
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- Authorization Code Grant
|
||||
- Resource Owner Password Credentials Grant
|
||||
- Client Credentials Grant
|
||||
- Refresh Token Grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def validate_redirect_uri(self, client_id, redirect_uri, request, *args, **kwargs):
|
||||
"""Ensure client is authorized to redirect to the redirect_uri requested.
|
||||
|
||||
All clients should register the absolute URIs of all URIs they intend
|
||||
to redirect to. The registration is outside of the scope of oauthlib.
|
||||
|
||||
:param client_id: Unicode client identifier
|
||||
:param redirect_uri: Unicode absolute URI
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- Authorization Code Grant
|
||||
- Implicit Grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def validate_refresh_token(self, refresh_token, client, request, *args, **kwargs):
|
||||
"""Ensure the Bearer token is valid and authorized access to scopes.
|
||||
|
||||
OBS! The request.user attribute should be set to the resource owner
|
||||
associated with this refresh token.
|
||||
|
||||
:param refresh_token: Unicode refresh token
|
||||
:param client: Client object set by you, see authenticate_client.
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- Authorization Code Grant (indirectly by issuing refresh tokens)
|
||||
- Resource Owner Password Credentials Grant (also indirectly)
|
||||
- Refresh Token Grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def validate_response_type(self, client_id, response_type, client, request, *args, **kwargs):
|
||||
"""Ensure client is authorized to use the response_type requested.
|
||||
|
||||
:param client_id: Unicode client identifier
|
||||
:param response_type: Unicode response type, i.e. code, token.
|
||||
:param client: Client object set by you, see authenticate_client.
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- Authorization Code Grant
|
||||
- Implicit Grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
|
||||
"""Ensure the client is authorized access to requested scopes.
|
||||
|
||||
:param client_id: Unicode client identifier
|
||||
:param scopes: List of scopes (defined by you)
|
||||
:param client: Client object set by you, see authenticate_client.
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by all core grant types:
|
||||
- Authorization Code Grant
|
||||
- Implicit Grant
|
||||
- Resource Owner Password Credentials Grant
|
||||
- Client Credentials Grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def validate_user(self, username, password, client, request, *args, **kwargs):
|
||||
"""Ensure the username and password is valid.
|
||||
|
||||
OBS! The validation should also set the user attribute of the request
|
||||
to a valid resource owner, i.e. request.user = username or similar. If
|
||||
not set you will be unable to associate a token with a user in the
|
||||
persistance method used (commonly, save_bearer_token).
|
||||
|
||||
:param username: Unicode username
|
||||
:param password: Unicode password
|
||||
:param client: Client object set by you, see authenticate_client.
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- Resource Owner Password Credentials Grant
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
298
lib/oauthlib/oauth2/rfc6749/tokens.py
Normal file
298
lib/oauthlib/oauth2/rfc6749/tokens.py
Normal file
|
@ -0,0 +1,298 @@
|
|||
"""
|
||||
oauthlib.oauth2.rfc6749.tokens
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module contains methods for adding two types of access tokens to requests.
|
||||
|
||||
- Bearer http://tools.ietf.org/html/rfc6750
|
||||
- MAC http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from binascii import b2a_base64
|
||||
import hashlib
|
||||
import hmac
|
||||
try:
|
||||
from urlparse import urlparse
|
||||
except ImportError:
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from oauthlib.common import add_params_to_uri, add_params_to_qs, unicode_type
|
||||
from oauthlib import common
|
||||
|
||||
from . import utils
|
||||
|
||||
|
||||
class OAuth2Token(dict):
|
||||
|
||||
def __init__(self, params, old_scope=None):
|
||||
super(OAuth2Token, self).__init__(params)
|
||||
self._new_scope = None
|
||||
if 'scope' in params and params['scope']:
|
||||
self._new_scope = set(utils.scope_to_list(params['scope']))
|
||||
if old_scope is not None:
|
||||
self._old_scope = set(utils.scope_to_list(old_scope))
|
||||
if self._new_scope is None:
|
||||
# the rfc says that if the scope hasn't changed, it's optional
|
||||
# in params so set the new scope to the old scope
|
||||
self._new_scope = self._old_scope
|
||||
else:
|
||||
self._old_scope = self._new_scope
|
||||
|
||||
@property
|
||||
def scope_changed(self):
|
||||
return self._new_scope != self._old_scope
|
||||
|
||||
@property
|
||||
def old_scope(self):
|
||||
return utils.list_to_scope(self._old_scope)
|
||||
|
||||
@property
|
||||
def old_scopes(self):
|
||||
return list(self._old_scope)
|
||||
|
||||
@property
|
||||
def scope(self):
|
||||
return utils.list_to_scope(self._new_scope)
|
||||
|
||||
@property
|
||||
def scopes(self):
|
||||
return list(self._new_scope)
|
||||
|
||||
@property
|
||||
def missing_scopes(self):
|
||||
return list(self._old_scope - self._new_scope)
|
||||
|
||||
@property
|
||||
def additional_scopes(self):
|
||||
return list(self._new_scope - self._old_scope)
|
||||
|
||||
|
||||
def prepare_mac_header(token, uri, key, http_method,
|
||||
nonce=None,
|
||||
headers=None,
|
||||
body=None,
|
||||
ext='',
|
||||
hash_algorithm='hmac-sha-1',
|
||||
issue_time=None,
|
||||
draft=0):
|
||||
"""Add an `MAC Access Authentication`_ signature to headers.
|
||||
|
||||
Unlike OAuth 1, this HMAC signature does not require inclusion of the
|
||||
request payload/body, neither does it use a combination of client_secret
|
||||
and token_secret but rather a mac_key provided together with the access
|
||||
token.
|
||||
|
||||
Currently two algorithms are supported, "hmac-sha-1" and "hmac-sha-256",
|
||||
`extension algorithms`_ are not supported.
|
||||
|
||||
Example MAC Authorization header, linebreaks added for clarity
|
||||
|
||||
Authorization: MAC id="h480djs93hd8",
|
||||
nonce="1336363200:dj83hs9s",
|
||||
mac="bhCQXTVyfj5cmA9uKkPFx1zeOXM="
|
||||
|
||||
.. _`MAC Access Authentication`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
|
||||
.. _`extension algorithms`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-7.1
|
||||
|
||||
:param uri: Request URI.
|
||||
:param headers: Request headers as a dictionary.
|
||||
:param http_method: HTTP Request method.
|
||||
:param key: MAC given provided by token endpoint.
|
||||
:param hash_algorithm: HMAC algorithm provided by token endpoint.
|
||||
:param issue_time: Time when the MAC credentials were issued (datetime).
|
||||
:param draft: MAC authentication specification version.
|
||||
:return: headers dictionary with the authorization field added.
|
||||
"""
|
||||
http_method = http_method.upper()
|
||||
host, port = utils.host_from_uri(uri)
|
||||
|
||||
if hash_algorithm.lower() == 'hmac-sha-1':
|
||||
h = hashlib.sha1
|
||||
elif hash_algorithm.lower() == 'hmac-sha-256':
|
||||
h = hashlib.sha256
|
||||
else:
|
||||
raise ValueError('unknown hash algorithm')
|
||||
|
||||
if draft == 0:
|
||||
nonce = nonce or '{0}:{1}'.format(utils.generate_age(issue_time),
|
||||
common.generate_nonce())
|
||||
else:
|
||||
ts = common.generate_timestamp()
|
||||
nonce = common.generate_nonce()
|
||||
|
||||
sch, net, path, par, query, fra = urlparse(uri)
|
||||
|
||||
if query:
|
||||
request_uri = path + '?' + query
|
||||
else:
|
||||
request_uri = path
|
||||
|
||||
# Hash the body/payload
|
||||
if body is not None and draft == 0:
|
||||
body = body.encode('utf-8')
|
||||
bodyhash = b2a_base64(h(body).digest())[:-1].decode('utf-8')
|
||||
else:
|
||||
bodyhash = ''
|
||||
|
||||
# Create the normalized base string
|
||||
base = []
|
||||
if draft == 0:
|
||||
base.append(nonce)
|
||||
else:
|
||||
base.append(ts)
|
||||
base.append(nonce)
|
||||
base.append(http_method.upper())
|
||||
base.append(request_uri)
|
||||
base.append(host)
|
||||
base.append(port)
|
||||
if draft == 0:
|
||||
base.append(bodyhash)
|
||||
base.append(ext or '')
|
||||
base_string = '\n'.join(base) + '\n'
|
||||
|
||||
# hmac struggles with unicode strings - http://bugs.python.org/issue5285
|
||||
if isinstance(key, unicode_type):
|
||||
key = key.encode('utf-8')
|
||||
sign = hmac.new(key, base_string.encode('utf-8'), h)
|
||||
sign = b2a_base64(sign.digest())[:-1].decode('utf-8')
|
||||
|
||||
header = []
|
||||
header.append('MAC id="%s"' % token)
|
||||
if draft != 0:
|
||||
header.append('ts="%s"' % ts)
|
||||
header.append('nonce="%s"' % nonce)
|
||||
if bodyhash:
|
||||
header.append('bodyhash="%s"' % bodyhash)
|
||||
if ext:
|
||||
header.append('ext="%s"' % ext)
|
||||
header.append('mac="%s"' % sign)
|
||||
|
||||
headers = headers or {}
|
||||
headers['Authorization'] = ', '.join(header)
|
||||
return headers
|
||||
|
||||
|
||||
def prepare_bearer_uri(token, uri):
|
||||
"""Add a `Bearer Token`_ to the request URI.
|
||||
Not recommended, use only if client can't use authorization header or body.
|
||||
|
||||
http://www.example.com/path?access_token=h480djs93hd8
|
||||
|
||||
.. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
|
||||
"""
|
||||
return add_params_to_uri(uri, [(('access_token', token))])
|
||||
|
||||
|
||||
def prepare_bearer_headers(token, headers=None):
|
||||
"""Add a `Bearer Token`_ to the request URI.
|
||||
Recommended method of passing bearer tokens.
|
||||
|
||||
Authorization: Bearer h480djs93hd8
|
||||
|
||||
.. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
|
||||
"""
|
||||
headers = headers or {}
|
||||
headers['Authorization'] = 'Bearer %s' % token
|
||||
return headers
|
||||
|
||||
|
||||
def prepare_bearer_body(token, body=''):
|
||||
"""Add a `Bearer Token`_ to the request body.
|
||||
|
||||
access_token=h480djs93hd8
|
||||
|
||||
.. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
|
||||
"""
|
||||
return add_params_to_qs(body, [(('access_token', token))])
|
||||
|
||||
|
||||
def random_token_generator(request, refresh_token=False):
|
||||
return common.generate_token()
|
||||
|
||||
|
||||
def signed_token_generator(private_pem, **kwargs):
|
||||
def signed_token_generator(request):
|
||||
request.claims = kwargs
|
||||
return common.generate_signed_token(private_pem, request)
|
||||
|
||||
return signed_token_generator
|
||||
|
||||
|
||||
class TokenBase(object):
|
||||
|
||||
def __call__(self, request, refresh_token=False):
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def validate_request(self, request):
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def estimate_type(self, request):
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
|
||||
class BearerToken(TokenBase):
|
||||
__slots__ = (
|
||||
'request_validator', 'token_generator',
|
||||
'refresh_token_generator', 'expires_in'
|
||||
)
|
||||
|
||||
def __init__(self, request_validator=None, token_generator=None,
|
||||
expires_in=None, refresh_token_generator=None):
|
||||
self.request_validator = request_validator
|
||||
self.token_generator = token_generator or random_token_generator
|
||||
self.refresh_token_generator = (
|
||||
refresh_token_generator or self.token_generator
|
||||
)
|
||||
self.expires_in = expires_in or 3600
|
||||
|
||||
def create_token(self, request, refresh_token=False):
|
||||
"""Create a BearerToken, by default without refresh token."""
|
||||
|
||||
if callable(self.expires_in):
|
||||
expires_in = self.expires_in(request)
|
||||
else:
|
||||
expires_in = self.expires_in
|
||||
|
||||
request.expires_in = expires_in
|
||||
|
||||
token = {
|
||||
'access_token': self.token_generator(request),
|
||||
'expires_in': expires_in,
|
||||
'token_type': 'Bearer',
|
||||
}
|
||||
|
||||
if request.scopes is not None:
|
||||
token['scope'] = ' '.join(request.scopes)
|
||||
|
||||
if request.state is not None:
|
||||
token['state'] = request.state
|
||||
|
||||
if refresh_token:
|
||||
if (request.refresh_token and
|
||||
not self.request_validator.rotate_refresh_token(request)):
|
||||
token['refresh_token'] = request.refresh_token
|
||||
else:
|
||||
token['refresh_token'] = self.refresh_token_generator(request)
|
||||
|
||||
token.update(request.extra_credentials or {})
|
||||
token = OAuth2Token(token)
|
||||
self.request_validator.save_bearer_token(token, request)
|
||||
return token
|
||||
|
||||
def validate_request(self, request):
|
||||
token = None
|
||||
if 'Authorization' in request.headers:
|
||||
token = request.headers.get('Authorization')[7:]
|
||||
else:
|
||||
token = request.access_token
|
||||
return self.request_validator.validate_bearer_token(
|
||||
token, request.scopes, request)
|
||||
|
||||
def estimate_type(self, request):
|
||||
if request.headers.get('Authorization', '').startswith('Bearer'):
|
||||
return 9
|
||||
elif request.access_token is not None:
|
||||
return 5
|
||||
else:
|
||||
return 0
|
92
lib/oauthlib/oauth2/rfc6749/utils.py
Normal file
92
lib/oauthlib/oauth2/rfc6749/utils.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.utils
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module contains utility methods used by various parts of the OAuth 2 spec.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import os
|
||||
import datetime
|
||||
try:
|
||||
from urllib import quote
|
||||
except ImportError:
|
||||
from urllib.parse import quote
|
||||
try:
|
||||
from urlparse import urlparse
|
||||
except ImportError:
|
||||
from urllib.parse import urlparse
|
||||
from oauthlib.common import unicode_type, urldecode
|
||||
|
||||
|
||||
def list_to_scope(scope):
|
||||
"""Convert a list of scopes to a space separated string."""
|
||||
if isinstance(scope, unicode_type) or scope is None:
|
||||
return scope
|
||||
elif isinstance(scope, (set, tuple, list)):
|
||||
return " ".join([unicode_type(s) for s in scope])
|
||||
else:
|
||||
raise ValueError("Invalid scope (%s), must be string, tuple, set, or list." % scope)
|
||||
|
||||
|
||||
def scope_to_list(scope):
|
||||
"""Convert a space separated string to a list of scopes."""
|
||||
if isinstance(scope, (tuple, list, set)):
|
||||
return [unicode_type(s) for s in scope]
|
||||
elif scope is None:
|
||||
return None
|
||||
else:
|
||||
return scope.strip().split(" ")
|
||||
|
||||
|
||||
def params_from_uri(uri):
|
||||
params = dict(urldecode(urlparse(uri).query))
|
||||
if 'scope' in params:
|
||||
params['scope'] = scope_to_list(params['scope'])
|
||||
return params
|
||||
|
||||
|
||||
def host_from_uri(uri):
|
||||
"""Extract hostname and port from URI.
|
||||
|
||||
Will use default port for HTTP and HTTPS if none is present in the URI.
|
||||
"""
|
||||
default_ports = {
|
||||
'HTTP': '80',
|
||||
'HTTPS': '443',
|
||||
}
|
||||
|
||||
sch, netloc, path, par, query, fra = urlparse(uri)
|
||||
if ':' in netloc:
|
||||
netloc, port = netloc.split(':', 1)
|
||||
else:
|
||||
port = default_ports.get(sch.upper())
|
||||
|
||||
return netloc, port
|
||||
|
||||
|
||||
def escape(u):
|
||||
"""Escape a string in an OAuth-compatible fashion.
|
||||
|
||||
TODO: verify whether this can in fact be used for OAuth 2
|
||||
|
||||
"""
|
||||
if not isinstance(u, unicode_type):
|
||||
raise ValueError('Only unicode objects are escapable.')
|
||||
return quote(u.encode('utf-8'), safe=b'~')
|
||||
|
||||
|
||||
def generate_age(issue_time):
|
||||
"""Generate a age parameter for MAC authentication draft 00."""
|
||||
td = datetime.datetime.now() - issue_time
|
||||
age = (td.microseconds + (td.seconds + td.days * 24 * 3600)
|
||||
* 10 ** 6) / 10 ** 6
|
||||
return unicode_type(age)
|
||||
|
||||
|
||||
def is_secure_transport(uri):
|
||||
"""Check if the uri is over ssl."""
|
||||
if os.environ.get('OAUTHLIB_INSECURE_TRANSPORT'):
|
||||
return True
|
||||
return uri.lower().startswith('https://')
|
Loading…
Add table
Add a link
Reference in a new issue