summaryrefslogtreecommitdiff
path: root/oauthlib/oauth2
diff options
context:
space:
mode:
authorIb Lundgren <ib.lundgren@gmail.com>2013-05-30 12:13:12 +0100
committerIb Lundgren <ib.lundgren@gmail.com>2013-05-30 12:13:12 +0100
commit07b2c2bca9b7c0deb24a801841b373198b8a99bf (patch)
tree3dda951d7bdb7c8327d352b3a956c301accbf420 /oauthlib/oauth2
parentcd6da5ab2522e283d2fd7f89c74d77cab83b5eb8 (diff)
downloadoauthlib-07b2c2bca9b7c0deb24a801841b373198b8a99bf.tar.gz
Split OAuth2 large modules into smaller ones. #168.
Diffstat (limited to 'oauthlib/oauth2')
-rw-r--r--oauthlib/oauth2/__init__.py29
-rw-r--r--oauthlib/oauth2/rfc6749/__init__.py1301
-rw-r--r--oauthlib/oauth2/rfc6749/clients/__init__.py16
-rw-r--r--oauthlib/oauth2/rfc6749/clients/backend_application.py158
-rw-r--r--oauthlib/oauth2/rfc6749/clients/base.py210
-rw-r--r--oauthlib/oauth2/rfc6749/clients/legacy_application.py170
-rw-r--r--oauthlib/oauth2/rfc6749/clients/mobile_application.py176
-rw-r--r--oauthlib/oauth2/rfc6749/clients/web_application.py273
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/__init__.py19
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/authorization.py108
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/base.py62
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/pre_configured.py128
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/resource.py80
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/token.py92
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/__init__.py12
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/authorization_code.py387
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/base.py37
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/client_credentials.py204
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/implicit.py334
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/refresh_token.py113
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py192
-rw-r--r--oauthlib/oauth2/rfc6749/request_validator.py (renamed from oauthlib/oauth2/rfc6749/grant_types.py)0
22 files changed, 2792 insertions, 1309 deletions
diff --git a/oauthlib/oauth2/__init__.py b/oauthlib/oauth2/__init__.py
index b1eb97a..c02a11f 100644
--- a/oauthlib/oauth2/__init__.py
+++ b/oauthlib/oauth2/__init__.py
@@ -9,13 +9,24 @@ This module is a wrapper for the most recent implementation of OAuth 2.0 Client
and Server classes.
"""
-from .rfc6749 import Client, Server, WebApplicationClient
-from .rfc6749 import UserAgentClient as MobileApplicationClient
-from .rfc6749 import PasswordCredentialsClient as LegacyApplicationClient
-from .rfc6749 import ClientCredentialsClient as BackendApplicationClient
-from .rfc6749 import AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint
-from .rfc6749 import WebApplicationServer, MobileApplicationServer
-from .rfc6749 import LegacyApplicationServer, BackendApplicationServer
-from .rfc6749.grant_types import *
-from .rfc6749.tokens import BearerToken
+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.endpoints import AuthorizationEndpoint
+from .rfc6749.endpoints import TokenEndpoint
+from .rfc6749.endpoints import ResourceEndpoint
+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
diff --git a/oauthlib/oauth2/rfc6749/__init__.py b/oauthlib/oauth2/rfc6749/__init__.py
index f8ee488..c82c06a 100644
--- a/oauthlib/oauth2/rfc6749/__init__.py
+++ b/oauthlib/oauth2/rfc6749/__init__.py
@@ -8,962 +8,11 @@ oauthlib.oauth2.rfc6749
This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 RFC6749.
"""
-import datetime
import functools
-import logging
-from oauthlib.common import Request
-from . import tokens, grant_types
-from .errors import TokenExpiredError, InsecureTransportError
+from oauthlib.common import log
from .errors import TemporarilyUnavailableError, ServerError
from .errors import FatalClientError, OAuth2Error
-from .parameters import prepare_grant_uri, prepare_token_request
-from .parameters import parse_authorization_code_response
-from .parameters import parse_implicit_response, parse_token_response
-
-
-AUTH_HEADER = 'auth_header'
-URI_QUERY = 'query'
-BODY = 'body'
-
-log = logging.getLogger('oauthlib')
-
-# Add a NullHandler to prevent warnings for users who don't wish
-# to configure logging.
-try:
- log.addHandler(logging.NullHandler())
-# NullHandler gracefully backported to 2.6
-except AttributeError:
- class NullHandler(logging.Handler):
- def emit(self, record):
- pass
- log.addHandler(NullHandler())
-
-
-class Client(object):
- """Base OAuth2 client responsible for access tokens.
-
- While this class can be used to simply append tokens onto requests
- it is often more useful to use a client targeted at a specific workflow.
- """
-
- 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,
- **kwargs):
- """Initialize a client with commonly used attributes."""
-
- 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._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 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 uri.lower().startswith('https://'):
- 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 < datetime.datetime.now():
- raise TokenExpiredError()
-
- return case_insensitive_token_types[self.token_type.lower()](uri, http_method, body,
- headers, token_placement, **kwargs)
-
- 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 = datetime.datetime.now() + datetime.timedelta(
- seconds=int(self.expires_in))
-
- 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')
-
- 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 parse_request_body_response(self, *args, **kwargs):
- """Abstract method used to parse JSON responses."""
-
-
-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
-
- def parse_request_body_response(self, body, scope=None):
- """Parse the JSON response body.
-
- 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 client
- authentication failed 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.
-
- A normal response might look like::
-
- >>> json.loads(response_body)
- {
- 'access_token': 'sdfkjh345',
- 'token_type': 'Bearer',
- 'expires_in': '3600',
- 'refresh_token': 'x345dgasd',
- 'scope': 'hello world',
- }
- >>> from oauthlib.oauth2 import WebApplicationClient
- >>> client = WebApplicationClient('your_id')
- >>> client.parse_request_body_response(response_body)
- {
- 'access_token': 'sdfkjh345',
- 'token_type': 'Bearer',
- 'expires_in': '3600',
- 'refresh_token': 'x345dgasd',
- 'scope': ['hello', 'world'], # note the list
- }
-
- If there was a scope change you will be notified with a warning::
-
- >>> client.parse_request_body_response(response_body, scope=['images'])
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
- .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
- File "oauthlib/oauth2/rfc6749/parameters.py", line 263, in parse_token_response
- validate_token_parameters(params, scope)
- File "oauthlib/oauth2/rfc6749/parameters.py", line 285, in validate_token_parameters
- raise Warning("Scope has changed to %s." % new_scope)
- Warning: Scope has changed to [u'hello', u'world'].
-
- If there was an error on the providers side you will be notified with
- an error. For example, if there was no ``token_type`` provided::
-
- >>> client.parse_request_body_response(response_body)
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
- File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
- File "oauthlib/oauth2/rfc6749/parameters.py", line 263, in parse_token_response
- validate_token_parameters(params, scope)
- File "oauthlib/oauth2/rfc6749/parameters.py", line 276, in validate_token_parameters
- raise MissingTokenTypeError()
- oauthlib.oauth2.rfc6749.errors.MissingTokenTypeError
-
- .. _`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
-
-
-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: Warning if scope has changed. 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.
- >>> client.parse_request_uri_response(response_uri, scope=['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 199, in parse_implicit_response
- validate_token_parameters(params, scope)
- File "oauthlib/oauth2/rfc6749/parameters.py", line 285, in validate_token_parameters
- raise Warning("Scope has changed to %s." % new_scope)
- Warning: Scope has changed to [u'hello', u'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
-
-
-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)
-
- def parse_request_body_response(self, body, scope=None):
- """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.
-
- A normal response might look like::
-
- >>> json.loads(response_body)
- {
- 'access_token': 'sdfkjh345',
- 'token_type': 'Bearer',
- 'expires_in': '3600',
- 'refresh_token': 'x345dgasd',
- 'scope': 'hello world',
- }
- >>> from oauthlib.oauth2 import BackendApplicationClient
- >>> client = BackendApplicationClient('your_id')
- >>> client.parse_request_body_response(response_body)
- {
- 'access_token': 'sdfkjh345',
- 'token_type': 'Bearer',
- 'expires_in': '3600',
- 'refresh_token': 'x345dgasd',
- 'scope': ['hello', 'world'], # note the list
- }
-
- If there was a scope change you will be notified with a warning::
-
- >>> client.parse_request_body_response(response_body, scope=['images'])
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
- .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
- File "oauthlib/oauth2/rfc6749/parameters.py", line 263, in parse_token_response
- validate_token_parameters(params, scope)
- File "oauthlib/oauth2/rfc6749/parameters.py", line 285, in validate_token_parameters
- raise Warning("Scope has changed to %s." % new_scope)
- Warning: Scope has changed to [u'hello', u'world'].
-
- If there was an error on the providers side you will be notified with
- an error. For example, if there was no ``token_type`` provided::
-
- >>> client.parse_request_body_response(response_body)
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
- File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
- File "oauthlib/oauth2/rfc6749/parameters.py", line 263, in parse_token_response
- validate_token_parameters(params, scope)
- File "oauthlib/oauth2/rfc6749/parameters.py", line 276, in validate_token_parameters
- raise MissingTokenTypeError()
- oauthlib.oauth2.rfc6749.errors.MissingTokenTypeError
-
- .. _`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
-
-
-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)
-
- def parse_request_body_response(self, body, scope=None):
- """Parse the JSON response body.
-
- 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`_.
-
- :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.
-
- A normal response might look like::
-
- >>> json.loads(response_body)
- {
- 'access_token': 'sdfkjh345',
- 'token_type': 'Bearer',
- 'expires_in': '3600',
- 'refresh_token': 'x345dgasd',
- 'scope': 'hello world',
- }
- >>> from oauthlib.oauth2 import LegacyApplicationClient
- >>> client = LegacyApplicationClient('your_id')
- >>> client.parse_request_body_response(response_body)
- {
- 'access_token': 'sdfkjh345',
- 'token_type': 'Bearer',
- 'expires_in': '3600',
- 'refresh_token': 'x345dgasd',
- 'scope': ['hello', 'world'], # note the list
- }
-
- If there was a scope change you will be notified with a warning::
-
- >>> client.parse_request_body_response(response_body, scope=['images'])
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
- .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
- File "oauthlib/oauth2/rfc6749/parameters.py", line 263, in parse_token_response
- validate_token_parameters(params, scope)
- File "oauthlib/oauth2/rfc6749/parameters.py", line 285, in validate_token_parameters
- raise Warning("Scope has changed to %s." % new_scope)
- Warning: Scope has changed to [u'hello', u'world'].
-
- If there was an error on the providers side you will be notified with
- an error. For example, if there was no ``token_type`` provided::
-
- >>> client.parse_request_body_response(response_body)
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
- File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
- File "oauthlib/oauth2/rfc6749/parameters.py", line 263, in parse_token_response
- validate_token_parameters(params, scope)
- File "oauthlib/oauth2/rfc6749/parameters.py", line 276, in validate_token_parameters
- raise MissingTokenTypeError()
- oauthlib.oauth2.rfc6749.errors.MissingTokenTypeError
-
- .. _`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
-
-
-# TODO(ib-lundgren): Deprecate these names
-class UserAgentClient(MobileApplicationClient):
- pass
-
-
-class PasswordCredentialsClient(LegacyApplicationClient):
- pass
-
-
-class ClientCredentialsClient(BackendApplicationClient):
- pass
class BaseEndpoint(object):
@@ -1010,351 +59,3 @@ def catch_errors_and_unavailability(f):
else:
return f(endpoint, uri, *args, **kwargs)
return wrapper
-
-
-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)
-
-
-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.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)
-
-
-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
-
-
-class Server(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint):
- """An all-in-one endpoint featuring all four major grant types."""
-
- def __init__(self, request_validator, token_expires_in=None,
- *args, **kwargs):
- auth_grant = grant_types.AuthorizationCodeGrant(request_validator)
- implicit_grant = grant_types.ImplicitGrant(request_validator)
- password_grant = grant_types.ResourceOwnerPasswordCredentialsGrant(request_validator)
- credentials_grant = grant_types.ClientCredentialsGrant(request_validator)
- refresh_grant = grant_types.RefreshTokenGrant(request_validator)
- bearer = tokens.BearerToken(request_validator,
- expires_in=token_expires_in)
- 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})
-
-
-class WebApplicationServer(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint):
- """An all-in-one endpoint featuring Authorization code grant and Bearer tokens."""
-
- def __init__(self, request_validator, token_generator=None,
- token_expires_in=None, **kwargs):
- """Construct a new web application server.
-
- :param request_validator: An implementation of oauthlib.oauth2.RequestValidator.
- :param token_generator: A function to generate a token from a request.
- :param kwargs: Extra parameters to pass to authorization endpoint,
- token endpoint and resource endpoint constructors.
- """
- auth_grant = grant_types.AuthorizationCodeGrant(request_validator)
- refresh_grant = grant_types.RefreshTokenGrant(request_validator)
- bearer = tokens.BearerToken(request_validator, token_generator,
- expires_in=token_expires_in)
- 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})
-
-
-class MobileApplicationServer(AuthorizationEndpoint, ResourceEndpoint):
- """An all-in-one endpoint featuring Implicit code grant and Bearer tokens."""
-
- def __init__(self, request_validator, token_generator=None,
- token_expires_in=None, **kwargs):
- implicit_grant = grant_types.ImplicitGrant(request_validator)
- bearer = tokens.BearerToken(request_validator, token_generator,
- expires_in=token_expires_in)
- 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})
-
-
-class LegacyApplicationServer(TokenEndpoint, ResourceEndpoint):
- """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, **kwargs):
- password_grant = grant_types.ResourceOwnerPasswordCredentialsGrant(request_validator)
- refresh_grant = grant_types.RefreshTokenGrant(request_validator)
- bearer = tokens.BearerToken(request_validator, token_generator,
- expires_in=token_expires_in)
- 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})
-
-
-class BackendApplicationServer(TokenEndpoint, ResourceEndpoint):
- """An all-in-one endpoint featuring Client Credentials grant and Bearer tokens."""
-
- def __init__(self, request_validator, token_generator=None,
- token_expires_in=None, **kwargs):
- credentials_grant = grant_types.ClientCredentialsGrant(request_validator)
- bearer = tokens.BearerToken(request_validator, token_generator,
- expires_in=token_expires_in)
- 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})
diff --git a/oauthlib/oauth2/rfc6749/clients/__init__.py b/oauthlib/oauth2/rfc6749/clients/__init__.py
new file mode 100644
index 0000000..b9f633f
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/clients/__init__.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, unicode_literals
+
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming OAuth 2.0 RFC6749.
+"""
+
+from .base import *
+from .web_application import WebApplicationClient
+from .mobile_application import MobileApplicationClient
+from .legacy_application import LegacyApplicationClient
+from .backend_application import BackendApplicationClient
diff --git a/oauthlib/oauth2/rfc6749/clients/backend_application.py b/oauthlib/oauth2/rfc6749/clients/backend_application.py
new file mode 100644
index 0000000..e281128
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/clients/backend_application.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, unicode_literals
+
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+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)
+
+ def parse_request_body_response(self, body, scope=None):
+ """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.
+
+ A normal response might look like::
+
+ >>> json.loads(response_body)
+ {
+ 'access_token': 'sdfkjh345',
+ 'token_type': 'Bearer',
+ 'expires_in': '3600',
+ 'refresh_token': 'x345dgasd',
+ 'scope': 'hello world',
+ }
+ >>> from oauthlib.oauth2 import BackendApplicationClient
+ >>> client = BackendApplicationClient('your_id')
+ >>> client.parse_request_body_response(response_body)
+ {
+ 'access_token': 'sdfkjh345',
+ 'token_type': 'Bearer',
+ 'expires_in': '3600',
+ 'refresh_token': 'x345dgasd',
+ 'scope': ['hello', 'world'], # note the list
+ }
+
+ If there was a scope change you will be notified with a warning::
+
+ >>> client.parse_request_body_response(response_body, scope=['images'])
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
+ .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
+ File "oauthlib/oauth2/rfc6749/parameters.py", line 263, in parse_token_response
+ validate_token_parameters(params, scope)
+ File "oauthlib/oauth2/rfc6749/parameters.py", line 285, in validate_token_parameters
+ raise Warning("Scope has changed to %s." % new_scope)
+ Warning: Scope has changed to [u'hello', u'world'].
+
+ If there was an error on the providers side you will be notified with
+ an error. For example, if there was no ``token_type`` provided::
+
+ >>> client.parse_request_body_response(response_body)
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
+ File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
+ File "oauthlib/oauth2/rfc6749/parameters.py", line 263, in parse_token_response
+ validate_token_parameters(params, scope)
+ File "oauthlib/oauth2/rfc6749/parameters.py", line 276, in validate_token_parameters
+ raise MissingTokenTypeError()
+ oauthlib.oauth2.rfc6749.errors.MissingTokenTypeError
+
+ .. _`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
diff --git a/oauthlib/oauth2/rfc6749/clients/base.py b/oauthlib/oauth2/rfc6749/clients/base.py
new file mode 100644
index 0000000..ac78ab3
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/clients/base.py
@@ -0,0 +1,210 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, unicode_literals
+
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming OAuth 2.0 RFC6749.
+"""
+import datetime
+
+from oauthlib.oauth2.rfc6749 import tokens
+from oauthlib.oauth2.rfc6749.parameters import prepare_token_request
+from oauthlib.oauth2.rfc6749.errors import TokenExpiredError
+from oauthlib.oauth2.rfc6749.errors import InsecureTransportError
+
+
+AUTH_HEADER = 'auth_header'
+URI_QUERY = 'query'
+BODY = 'body'
+
+
+class Client(object):
+ """Base OAuth2 client responsible for access tokens.
+
+ While this class can be used to simply append tokens onto requests
+ it is often more useful to use a client targeted at a specific workflow.
+ """
+
+ 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,
+ **kwargs):
+ """Initialize a client with commonly used attributes."""
+
+ 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._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 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 uri.lower().startswith('https://'):
+ 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 < datetime.datetime.now():
+ raise TokenExpiredError()
+
+ return case_insensitive_token_types[self.token_type.lower()](uri, http_method, body,
+ headers, token_placement, **kwargs)
+
+ 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 = datetime.datetime.now() + datetime.timedelta(
+ seconds=int(self.expires_in))
+
+ 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')
+
+ 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 parse_request_body_response(self, *args, **kwargs):
+ """Abstract method used to parse JSON responses."""
diff --git a/oauthlib/oauth2/rfc6749/clients/legacy_application.py b/oauthlib/oauth2/rfc6749/clients/legacy_application.py
new file mode 100644
index 0000000..7164d23
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/clients/legacy_application.py
@@ -0,0 +1,170 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, unicode_literals
+
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+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)
+
+ def parse_request_body_response(self, body, scope=None):
+ """Parse the JSON response body.
+
+ 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`_.
+
+ :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.
+
+ A normal response might look like::
+
+ >>> json.loads(response_body)
+ {
+ 'access_token': 'sdfkjh345',
+ 'token_type': 'Bearer',
+ 'expires_in': '3600',
+ 'refresh_token': 'x345dgasd',
+ 'scope': 'hello world',
+ }
+ >>> from oauthlib.oauth2 import LegacyApplicationClient
+ >>> client = LegacyApplicationClient('your_id')
+ >>> client.parse_request_body_response(response_body)
+ {
+ 'access_token': 'sdfkjh345',
+ 'token_type': 'Bearer',
+ 'expires_in': '3600',
+ 'refresh_token': 'x345dgasd',
+ 'scope': ['hello', 'world'], # note the list
+ }
+
+ If there was a scope change you will be notified with a warning::
+
+ >>> client.parse_request_body_response(response_body, scope=['images'])
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
+ .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
+ File "oauthlib/oauth2/rfc6749/parameters.py", line 263, in parse_token_response
+ validate_token_parameters(params, scope)
+ File "oauthlib/oauth2/rfc6749/parameters.py", line 285, in validate_token_parameters
+ raise Warning("Scope has changed to %s." % new_scope)
+ Warning: Scope has changed to [u'hello', u'world'].
+
+ If there was an error on the providers side you will be notified with
+ an error. For example, if there was no ``token_type`` provided::
+
+ >>> client.parse_request_body_response(response_body)
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
+ File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
+ File "oauthlib/oauth2/rfc6749/parameters.py", line 263, in parse_token_response
+ validate_token_parameters(params, scope)
+ File "oauthlib/oauth2/rfc6749/parameters.py", line 276, in validate_token_parameters
+ raise MissingTokenTypeError()
+ oauthlib.oauth2.rfc6749.errors.MissingTokenTypeError
+
+ .. _`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
diff --git a/oauthlib/oauth2/rfc6749/clients/mobile_application.py b/oauthlib/oauth2/rfc6749/clients/mobile_application.py
new file mode 100644
index 0000000..e6c23c8
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/clients/mobile_application.py
@@ -0,0 +1,176 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, unicode_literals
+
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+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: Warning if scope has changed. 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.
+ >>> client.parse_request_uri_response(response_uri, scope=['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 199, in parse_implicit_response
+ validate_token_parameters(params, scope)
+ File "oauthlib/oauth2/rfc6749/parameters.py", line 285, in validate_token_parameters
+ raise Warning("Scope has changed to %s." % new_scope)
+ Warning: Scope has changed to [u'hello', u'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
diff --git a/oauthlib/oauth2/rfc6749/clients/web_application.py b/oauthlib/oauth2/rfc6749/clients/web_application.py
new file mode 100644
index 0000000..b2e4417
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/clients/web_application.py
@@ -0,0 +1,273 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, unicode_literals
+
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+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
+
+ def parse_request_body_response(self, body, scope=None):
+ """Parse the JSON response body.
+
+ 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 client
+ authentication failed 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.
+
+ A normal response might look like::
+
+ >>> json.loads(response_body)
+ {
+ 'access_token': 'sdfkjh345',
+ 'token_type': 'Bearer',
+ 'expires_in': '3600',
+ 'refresh_token': 'x345dgasd',
+ 'scope': 'hello world',
+ }
+ >>> from oauthlib.oauth2 import WebApplicationClient
+ >>> client = WebApplicationClient('your_id')
+ >>> client.parse_request_body_response(response_body)
+ {
+ 'access_token': 'sdfkjh345',
+ 'token_type': 'Bearer',
+ 'expires_in': '3600',
+ 'refresh_token': 'x345dgasd',
+ 'scope': ['hello', 'world'], # note the list
+ }
+
+ If there was a scope change you will be notified with a warning::
+
+ >>> client.parse_request_body_response(response_body, scope=['images'])
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
+ .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
+ File "oauthlib/oauth2/rfc6749/parameters.py", line 263, in parse_token_response
+ validate_token_parameters(params, scope)
+ File "oauthlib/oauth2/rfc6749/parameters.py", line 285, in validate_token_parameters
+ raise Warning("Scope has changed to %s." % new_scope)
+ Warning: Scope has changed to [u'hello', u'world'].
+
+ If there was an error on the providers side you will be notified with
+ an error. For example, if there was no ``token_type`` provided::
+
+ >>> client.parse_request_body_response(response_body)
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
+ File "oauthlib/oauth2/rfc6749/__init__.py", line 421, in parse_request_body_response
+ File "oauthlib/oauth2/rfc6749/parameters.py", line 263, in parse_token_response
+ validate_token_parameters(params, scope)
+ File "oauthlib/oauth2/rfc6749/parameters.py", line 276, in validate_token_parameters
+ raise MissingTokenTypeError()
+ oauthlib.oauth2.rfc6749.errors.MissingTokenTypeError
+
+ .. _`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
diff --git a/oauthlib/oauth2/rfc6749/endpoints/__init__.py b/oauthlib/oauth2/rfc6749/endpoints/__init__.py
new file mode 100644
index 0000000..f0a6f7c
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/endpoints/__init__.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, unicode_literals
+
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+
+from .authorization import AuthorizationEndpoint
+from .token import TokenEndpoint
+from .resource import ResourceEndpoint
+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
diff --git a/oauthlib/oauth2/rfc6749/endpoints/authorization.py b/oauthlib/oauth2/rfc6749/endpoints/authorization.py
new file mode 100644
index 0000000..d8606ea
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/endpoints/authorization.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, unicode_literals
+
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+
+from oauthlib.common import Request, log
+
+from .base import BaseEndpoint, catch_errors_and_unavailability
+
+
+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)
diff --git a/oauthlib/oauth2/rfc6749/endpoints/base.py b/oauthlib/oauth2/rfc6749/endpoints/base.py
new file mode 100644
index 0000000..41b3aef
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/endpoints/base.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, unicode_literals
+
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+import functools
+
+from oauthlib.common import log
+
+from ..errors import TemporarilyUnavailableError, ServerError
+from ..errors import FatalClientError, OAuth2Error
+
+
+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 None, {}, 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 None, {}, error.json, 500
+ else:
+ return f(endpoint, uri, *args, **kwargs)
+ return wrapper
diff --git a/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py b/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
new file mode 100644
index 0000000..539879d
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, unicode_literals
+
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+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
+
+
+class Server(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint):
+ """An all-in-one endpoint featuring all four major grant types."""
+
+ def __init__(self, request_validator, token_expires_in=None,
+ *args, **kwargs):
+ 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,
+ expires_in=token_expires_in)
+ 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})
+
+
+class WebApplicationServer(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint):
+ """An all-in-one endpoint featuring Authorization code grant and Bearer tokens."""
+
+ def __init__(self, request_validator, token_generator=None,
+ token_expires_in=None, **kwargs):
+ """Construct a new web application server.
+
+ :param request_validator: An implementation of oauthlib.oauth2.RequestValidator.
+ :param token_generator: A function to generate a token from a request.
+ :param kwargs: Extra parameters to pass to authorization endpoint,
+ token endpoint and resource endpoint constructors.
+ """
+ auth_grant = AuthorizationCodeGrant(request_validator)
+ refresh_grant = RefreshTokenGrant(request_validator)
+ bearer = BearerToken(request_validator, token_generator,
+ expires_in=token_expires_in)
+ 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})
+
+
+class MobileApplicationServer(AuthorizationEndpoint, ResourceEndpoint):
+ """An all-in-one endpoint featuring Implicit code grant and Bearer tokens."""
+
+ def __init__(self, request_validator, token_generator=None,
+ token_expires_in=None, **kwargs):
+ implicit_grant = ImplicitGrant(request_validator)
+ bearer = BearerToken(request_validator, token_generator,
+ expires_in=token_expires_in)
+ 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})
+
+
+class LegacyApplicationServer(TokenEndpoint, ResourceEndpoint):
+ """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, **kwargs):
+ password_grant = ResourceOwnerPasswordCredentialsGrant(request_validator)
+ refresh_grant = RefreshTokenGrant(request_validator)
+ bearer = BearerToken(request_validator, token_generator,
+ expires_in=token_expires_in)
+ 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})
+
+
+class BackendApplicationServer(TokenEndpoint, ResourceEndpoint):
+ """An all-in-one endpoint featuring Client Credentials grant and Bearer tokens."""
+
+ def __init__(self, request_validator, token_generator=None,
+ token_expires_in=None, **kwargs):
+ credentials_grant = ClientCredentialsGrant(request_validator)
+ bearer = BearerToken(request_validator, token_generator,
+ expires_in=token_expires_in)
+ 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})
diff --git a/oauthlib/oauth2/rfc6749/endpoints/resource.py b/oauthlib/oauth2/rfc6749/endpoints/resource.py
new file mode 100644
index 0000000..c1f6b18
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/endpoints/resource.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, unicode_literals
+
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+from oauthlib.common import Request, log
+
+from .base import BaseEndpoint, catch_errors_and_unavailability
+
+
+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
diff --git a/oauthlib/oauth2/rfc6749/endpoints/token.py b/oauthlib/oauth2/rfc6749/endpoints/token.py
new file mode 100644
index 0000000..9fac254
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/endpoints/token.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, unicode_literals
+
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+from oauthlib.common import Request, log
+
+from .base import BaseEndpoint, catch_errors_and_unavailability
+
+
+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.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)
diff --git a/oauthlib/oauth2/rfc6749/grant_types/__init__.py b/oauthlib/oauth2/rfc6749/grant_types/__init__.py
new file mode 100644
index 0000000..2ec8e4f
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/grant_types/__init__.py
@@ -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
diff --git a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
new file mode 100644
index 0000000..55f70f3
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
@@ -0,0 +1,387 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth2.rfc6749.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+from __future__ import unicode_literals, absolute_import
+import json
+
+from oauthlib import common
+from oauthlib.common import log
+from oauthlib.uri_validate import is_absolute_uri
+
+from .base import GrantTypeBase
+from .. import errors
+from ..request_validator import RequestValidator
+
+
+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):
+ self.request_validator = request_validator or RequestValidator()
+
+ 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: uri, 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 common.add_params_to_uri(request.redirect_uri, e.twotuples), None, None, e.status_code
+
+ 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 common.add_params_to_uri(request.redirect_uri, grant.items()), None, 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;charset=UTF-8',
+ '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 None, headers, e.json, e.status_code
+
+ token = token_handler.create_token(request, refresh_token=True)
+ self.request_validator.invalidate_authorization_code(
+ request.client_id, request.code, request)
+ return None, 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.
+
+ # 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(state=request.state, request=request)
+
+ if not self.request_validator.validate_client_id(request.client_id, request):
+ raise errors.InvalidClientIdError(state=request.state, 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(state=request.state, request=request)
+
+ if not self.request_validator.validate_redirect_uri(
+ request.client_id, request.redirect_uri, request):
+ raise errors.MismatchingRedirectURIError(state=request.state, 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(state=request.state, 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.
+ if request.response_type is None:
+ raise errors.InvalidRequestError(state=request.state,
+ description='Missing response_type parameter.', request=request)
+
+ for param in ('client_id', 'response_type', 'redirect_uri', 'scope', 'state'):
+ if param in request.duplicate_params:
+ raise errors.InvalidRequestError(state=request.state,
+ description='Duplicate %s parameter.' % param, 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)
+
+ # REQUIRED. Value MUST be set to "code".
+ if request.response_type != 'code':
+ raise errors.UnsupportedResponseTypeError(state=request.state, 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,
+ }
+
+ 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(state=request.state,
+ description='Duplicate %s parameter.' % param,
+ request=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):
+ # 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
+ if not self.request_validator.authenticate_client_id(
+ request.client_id, 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)
+
+ # 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', 'state', 'scopes'):
+ if getattr(request, attr) 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)
diff --git a/oauthlib/oauth2/rfc6749/grant_types/base.py b/oauthlib/oauth2/rfc6749/grant_types/base.py
new file mode 100644
index 0000000..a3dd87c
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/grant_types/base.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth2.rfc6749.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+from __future__ import unicode_literals, absolute_import
+
+from oauthlib.common import log
+from oauthlib.oauth2.rfc6749 import errors, utils
+
+
+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):
+ if not self.request_validator.validate_grant_type(request.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(state=request.state, request=request)
diff --git a/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py b/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
new file mode 100644
index 0000000..146eac8
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
@@ -0,0 +1,204 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth2.rfc6749.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+from __future__ import unicode_literals, absolute_import
+import json
+from oauthlib.common import log
+
+from .base import GrantTypeBase
+from .. import errors
+from ..request_validator import RequestValidator
+
+
+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
+ """
+ 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 None, {}, 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 None, {}, json.dumps(token), 200
+
+ def validate_token_request(self, request):
+ if not getattr(request, 'grant_type'):
+ 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(state=request.state,
+ 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)
+
+
+class RefreshTokenGrant(GrantTypeBase):
+ """`Refresh token grant`_
+
+ .. _`Refresh token grant`: http://tools.ietf.org/html/rfc6749#section-6
+ """
+
+ @property
+ def issue_new_refresh_tokens(self):
+ return True
+
+ def __init__(self, request_validator=None, issue_new_refresh_tokens=True):
+ self.request_validator = request_validator or RequestValidator()
+
+ 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;charset=UTF-8',
+ '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 None, headers, e.json, 400
+
+ 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 None, 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
+ log.debug('Authenticating client, %r.', request)
+ if not self.request_validator.authenticate_client(request):
+ log.debug('Invalid client (%r), denying access.', request)
+ raise errors.AccessDeniedError(request=request)
+
+ # Ensure client is authorized use of this grant type
+ self.validate_grant_type(request)
+
+ # 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.
+ if request.scopes:
+ log.debug('Ensuring refresh token %s has access to scopes %r.',
+ request.refresh_token, request.scopes)
+ else:
+ log.debug('Reusing scopes from previous access token.')
+ if not self.request_validator.confirm_scopes(request.refresh_token,
+ request.scopes, request):
+ log.debug('Refresh token %s lack requested scopes, %r.',
+ request.refresh_token, request.scopes)
+ raise errors.InvalidScopeError(state=request.state, request=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.InvalidRequestError(request=request)
diff --git a/oauthlib/oauth2/rfc6749/grant_types/implicit.py b/oauthlib/oauth2/rfc6749/grant_types/implicit.py
new file mode 100644
index 0000000..7e2cc54
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/grant_types/implicit.py
@@ -0,0 +1,334 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth2.rfc6749.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+from __future__ import unicode_literals, absolute_import
+from oauthlib import common
+from oauthlib.common import log
+from oauthlib.uri_validate import is_absolute_uri
+
+from .base import GrantTypeBase
+from .. import errors
+from ..request_validator import RequestValidator
+
+
+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 common.add_params_to_uri(request.redirect_uri, e.twotuples,
+ fragment=True), {}, None, e.status_code
+
+ token = token_handler.create_token(request, refresh_token=False)
+ return 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.
+
+ # 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(state=request.state, request=request)
+
+ if not self.request_validator.validate_client_id(request.client_id, request):
+ raise errors.InvalidClientIdError(state=request.state, 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(state=request.state, 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(state=request.state, 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(state=request.state, request=request)
+ if not is_absolute_uri(request.redirect_uri):
+ raise errors.InvalidRedirectURIError(state=request.state, 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.
+ if request.response_type is None:
+ raise errors.InvalidRequestError(state=request.state,
+ description='Missing response_type parameter.',
+ request=request)
+
+ for param in ('client_id', 'response_type', 'redirect_uri', 'scope', 'state'):
+ if param in request.duplicate_params:
+ raise errors.InvalidRequestError(state=request.state,
+ description='Duplicate %s parameter.' % param, request=request)
+
+ # REQUIRED. Value MUST be set to "token".
+ if request.response_type != 'token':
+ raise errors.UnsupportedResponseTypeError(state=request.state, 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,
+ }
diff --git a/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py b/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
new file mode 100644
index 0000000..b63013b
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth2.rfc6749.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+from __future__ import unicode_literals, absolute_import
+import json
+
+from oauthlib.common import log
+
+from .base import GrantTypeBase
+from .. import errors
+from ..request_validator import RequestValidator
+
+
+class RefreshTokenGrant(GrantTypeBase):
+ """`Refresh token grant`_
+
+ .. _`Refresh token grant`: http://tools.ietf.org/html/rfc6749#section-6
+ """
+
+ @property
+ def issue_new_refresh_tokens(self):
+ return True
+
+ def __init__(self, request_validator=None, issue_new_refresh_tokens=True):
+ self.request_validator = request_validator or RequestValidator()
+
+ 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;charset=UTF-8',
+ '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 None, headers, e.json, 400
+
+ 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 None, 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
+ log.debug('Authenticating client, %r.', request)
+ if not self.request_validator.authenticate_client(request):
+ log.debug('Invalid client (%r), denying access.', request)
+ raise errors.AccessDeniedError(request=request)
+
+ # Ensure client is authorized use of this grant type
+ self.validate_grant_type(request)
+
+ # 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.
+ if request.scopes:
+ log.debug('Ensuring refresh token %s has access to scopes %r.',
+ request.refresh_token, request.scopes)
+ else:
+ log.debug('Reusing scopes from previous access token.')
+ if not self.request_validator.confirm_scopes(request.refresh_token,
+ request.scopes, request):
+ log.debug('Refresh token %s lack requested scopes, %r.',
+ request.refresh_token, request.scopes)
+ raise errors.InvalidScopeError(state=request.state, request=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.InvalidRequestError(request=request)
diff --git a/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py b/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py
new file mode 100644
index 0000000..5b991c6
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py
@@ -0,0 +1,192 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth2.rfc6749.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+from __future__ import unicode_literals, absolute_import
+import json
+from oauthlib.common import log
+
+from .base import GrantTypeBase
+from .. import errors
+from ..request_validator import RequestValidator
+
+
+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):
+ self.request_validator = request_validator or RequestValidator()
+
+ def create_token_response(self, request, token_handler,
+ require_authentication=True):
+ """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;charset=UTF-8',
+ 'Cache-Control': 'no-store',
+ 'Pragma': 'no-cache',
+ }
+ try:
+ if require_authentication:
+ 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.')
+ else:
+ log.debug('Client authentication disabled, %r.', 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 None, headers, e.json, e.status_code
+
+ token = token_handler.create_token(request, refresh_token=True)
+ log.debug('Issuing token %r to client id %r (%r) and username %s.',
+ token, request.client_id, request.client, request.username)
+ return None, 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):
+ 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(state=request.state,
+ 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 and password %s.',
+ request.username, request.password)
+ 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)
diff --git a/oauthlib/oauth2/rfc6749/grant_types.py b/oauthlib/oauth2/rfc6749/request_validator.py
index 25edcc8..25edcc8 100644
--- a/oauthlib/oauth2/rfc6749/grant_types.py
+++ b/oauthlib/oauth2/rfc6749/request_validator.py