venv added, updated
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
authlib.oauth2.rfc6749.grants
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Implementation for `Section 4`_ of "Obtaining Authorization".
|
||||
|
||||
To request an access token, the client obtains authorization from the
|
||||
resource owner. The authorization is expressed in the form of an
|
||||
authorization grant, which the client uses to request the access
|
||||
token. OAuth defines four grant types:
|
||||
|
||||
1. authorization code
|
||||
2. implicit
|
||||
3. resource owner password credentials
|
||||
4. client credentials.
|
||||
|
||||
It also provides an extension mechanism for defining additional grant
|
||||
types. Authlib defines refresh_token as a grant type too.
|
||||
|
||||
.. _`Section 4`: https://tools.ietf.org/html/rfc6749#section-4
|
||||
"""
|
||||
|
||||
# flake8: noqa
|
||||
|
||||
from .base import BaseGrant, AuthorizationEndpointMixin, TokenEndpointMixin
|
||||
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
|
||||
|
||||
__all__ = [
|
||||
'BaseGrant', 'AuthorizationEndpointMixin', 'TokenEndpointMixin',
|
||||
'AuthorizationCodeGrant', 'ImplicitGrant',
|
||||
'ResourceOwnerPasswordCredentialsGrant',
|
||||
'ClientCredentialsGrant', 'RefreshTokenGrant',
|
||||
]
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+378
@@ -0,0 +1,378 @@
|
||||
import logging
|
||||
from authlib.common.urls import add_params_to_uri
|
||||
from authlib.common.security import generate_token
|
||||
from .base import BaseGrant, AuthorizationEndpointMixin, TokenEndpointMixin
|
||||
from ..errors import (
|
||||
OAuth2Error,
|
||||
UnauthorizedClientError,
|
||||
InvalidClientError,
|
||||
InvalidGrantError,
|
||||
InvalidRequestError,
|
||||
AccessDeniedError,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AuthorizationCodeGrant(BaseGrant, AuthorizationEndpointMixin, TokenEndpointMixin):
|
||||
"""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)
|
||||
"""
|
||||
#: Allowed client auth methods for token endpoint
|
||||
TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_basic', 'client_secret_post']
|
||||
|
||||
#: Generated "code" length
|
||||
AUTHORIZATION_CODE_LENGTH = 48
|
||||
|
||||
RESPONSE_TYPES = {'code'}
|
||||
GRANT_TYPE = 'authorization_code'
|
||||
|
||||
def validate_authorization_request(self):
|
||||
"""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 `Section 4.1.1`_.
|
||||
|
||||
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.
|
||||
|
||||
For example, the client directs the user-agent to make the following
|
||||
HTTP request using TLS (with extra line breaks for display purposes
|
||||
only):
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
|
||||
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
|
||||
Host: server.example.com
|
||||
|
||||
The authorization server validates the request to ensure that all
|
||||
required parameters are present and valid. If the request is valid,
|
||||
the authorization server authenticates the resource owner and obtains
|
||||
an authorization decision (by asking the resource owner or by
|
||||
establishing approval via other means).
|
||||
|
||||
.. _`Section 4.1.1`: https://tools.ietf.org/html/rfc6749#section-4.1.1
|
||||
"""
|
||||
return validate_code_authorization_request(self)
|
||||
|
||||
def create_authorization_response(self, redirect_uri: str, grant_user):
|
||||
"""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.
|
||||
Per `Section 4.1.2`_.
|
||||
|
||||
code
|
||||
REQUIRED. The authorization code generated by the
|
||||
authorization server. The authorization code MUST expire
|
||||
shortly after it is issued to mitigate the risk of leaks. A
|
||||
maximum authorization code lifetime of 10 minutes is
|
||||
RECOMMENDED. The client MUST NOT use the authorization code
|
||||
more than once. If an authorization code is used more than
|
||||
once, the authorization server MUST deny the request and SHOULD
|
||||
revoke (when possible) all tokens previously issued based on
|
||||
that authorization code. The authorization code is bound to
|
||||
the client identifier and redirection URI.
|
||||
state
|
||||
REQUIRED if the "state" parameter was present in the client
|
||||
authorization request. The exact value received from the
|
||||
client.
|
||||
|
||||
For example, the authorization server redirects the user-agent by
|
||||
sending the following HTTP response.
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 302 Found
|
||||
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
|
||||
&state=xyz
|
||||
|
||||
.. _`Section 4.1.2`: https://tools.ietf.org/html/rfc6749#section-4.1.2
|
||||
|
||||
:param redirect_uri: Redirect to the given URI for the authorization
|
||||
:param grant_user: if resource owner granted the request, pass this
|
||||
resource owner, otherwise pass None.
|
||||
:returns: (status_code, body, headers)
|
||||
"""
|
||||
if not grant_user:
|
||||
raise AccessDeniedError(state=self.request.state, redirect_uri=redirect_uri)
|
||||
|
||||
self.request.user = grant_user
|
||||
|
||||
code = self.generate_authorization_code()
|
||||
self.save_authorization_code(code, self.request)
|
||||
|
||||
params = [('code', code)]
|
||||
if self.request.state:
|
||||
params.append(('state', self.request.state))
|
||||
uri = add_params_to_uri(redirect_uri, params)
|
||||
headers = [('Location', uri)]
|
||||
return 302, '', headers
|
||||
|
||||
def validate_token_request(self):
|
||||
"""The client makes a request to the token endpoint by sending the
|
||||
following parameters using the "application/x-www-form-urlencoded"
|
||||
format per `Section 4.1.3`_:
|
||||
|
||||
grant_type
|
||||
REQUIRED. Value MUST be set to "authorization_code".
|
||||
|
||||
code
|
||||
REQUIRED. The authorization code received from the
|
||||
authorization server.
|
||||
|
||||
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.
|
||||
|
||||
client_id
|
||||
REQUIRED, if the client is not authenticating with the
|
||||
authorization server as described in Section 3.2.1.
|
||||
|
||||
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.
|
||||
|
||||
For example, the client makes the following HTTP request using TLS:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
POST /token HTTP/1.1
|
||||
Host: server.example.com
|
||||
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
|
||||
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
|
||||
|
||||
.. _`Section 4.1.3`: https://tools.ietf.org/html/rfc6749#section-4.1.3
|
||||
"""
|
||||
# ignore validate for grant_type, since it is validated by
|
||||
# check_token_endpoint
|
||||
|
||||
# authenticate the client if client authentication is included
|
||||
client = self.authenticate_token_endpoint_client()
|
||||
|
||||
log.debug('Validate token request of %r', client)
|
||||
if not client.check_grant_type(self.GRANT_TYPE):
|
||||
raise UnauthorizedClientError(
|
||||
f'The client is not authorized to use "grant_type={self.GRANT_TYPE}"')
|
||||
|
||||
code = self.request.form.get('code')
|
||||
if code is None:
|
||||
raise InvalidRequestError('Missing "code" in request.')
|
||||
|
||||
# ensure that the authorization code was issued to the authenticated
|
||||
# confidential client, or if the client is public, ensure that the
|
||||
# code was issued to "client_id" in the request
|
||||
authorization_code = self.query_authorization_code(code, client)
|
||||
if not authorization_code:
|
||||
raise InvalidGrantError('Invalid "code" in request.')
|
||||
|
||||
# validate redirect_uri parameter
|
||||
log.debug('Validate token redirect_uri of %r', client)
|
||||
redirect_uri = self.request.redirect_uri
|
||||
original_redirect_uri = authorization_code.get_redirect_uri()
|
||||
if original_redirect_uri and redirect_uri != original_redirect_uri:
|
||||
raise InvalidGrantError('Invalid "redirect_uri" in request.')
|
||||
|
||||
# save for create_token_response
|
||||
self.request.client = client
|
||||
self.request.authorization_code = authorization_code
|
||||
self.execute_hook('after_validate_token_request')
|
||||
|
||||
def create_token_response(self):
|
||||
"""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. Per `Section 4.1.4`_.
|
||||
|
||||
An example successful response:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Cache-Control: no-store
|
||||
Pragma: no-cache
|
||||
|
||||
{
|
||||
"access_token":"2YotnFZFEjr1zCsicMWpAA",
|
||||
"token_type":"example",
|
||||
"expires_in":3600,
|
||||
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
|
||||
"example_parameter":"example_value"
|
||||
}
|
||||
|
||||
:returns: (status_code, body, headers)
|
||||
|
||||
.. _`Section 4.1.4`: https://tools.ietf.org/html/rfc6749#section-4.1.4
|
||||
"""
|
||||
client = self.request.client
|
||||
authorization_code = self.request.authorization_code
|
||||
|
||||
user = self.authenticate_user(authorization_code)
|
||||
if not user:
|
||||
raise InvalidGrantError('There is no "user" for this code.')
|
||||
self.request.user = user
|
||||
|
||||
scope = authorization_code.get_scope()
|
||||
token = self.generate_token(
|
||||
user=user,
|
||||
scope=scope,
|
||||
include_refresh_token=client.check_grant_type('refresh_token'),
|
||||
)
|
||||
log.debug('Issue token %r to %r', token, client)
|
||||
|
||||
self.save_token(token)
|
||||
self.execute_hook('process_token', token=token)
|
||||
self.delete_authorization_code(authorization_code)
|
||||
return 200, token, self.TOKEN_RESPONSE_HEADER
|
||||
|
||||
def generate_authorization_code(self):
|
||||
""""The method to generate "code" value for authorization code data.
|
||||
Developers may rewrite this method, or customize the code length with::
|
||||
|
||||
class MyAuthorizationCodeGrant(AuthorizationCodeGrant):
|
||||
AUTHORIZATION_CODE_LENGTH = 32 # default is 48
|
||||
"""
|
||||
return generate_token(self.AUTHORIZATION_CODE_LENGTH)
|
||||
|
||||
def save_authorization_code(self, code, request):
|
||||
"""Save authorization_code for later use. Developers MUST implement
|
||||
it in subclass. Here is an example::
|
||||
|
||||
def save_authorization_code(self, code, request):
|
||||
client = request.client
|
||||
item = AuthorizationCode(
|
||||
code=code,
|
||||
client_id=client.client_id,
|
||||
redirect_uri=request.redirect_uri,
|
||||
scope=request.scope,
|
||||
user_id=request.user.id,
|
||||
)
|
||||
item.save()
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def query_authorization_code(self, code, client): # pragma: no cover
|
||||
"""Get authorization_code from previously savings. Developers MUST
|
||||
implement it in subclass::
|
||||
|
||||
def query_authorization_code(self, code, client):
|
||||
return Authorization.get(code=code, client_id=client.client_id)
|
||||
|
||||
:param code: a string represent the code.
|
||||
:param client: client related to this code.
|
||||
:return: authorization_code object
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def delete_authorization_code(self, authorization_code):
|
||||
"""Delete authorization code from database or cache. Developers MUST
|
||||
implement it in subclass, e.g.::
|
||||
|
||||
def delete_authorization_code(self, authorization_code):
|
||||
authorization_code.delete()
|
||||
|
||||
:param authorization_code: the instance of authorization_code
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def authenticate_user(self, authorization_code):
|
||||
"""Authenticate the user related to this authorization_code. Developers
|
||||
MUST implement this method in subclass, e.g.::
|
||||
|
||||
def authenticate_user(self, authorization_code):
|
||||
return User.get(authorization_code.user_id)
|
||||
|
||||
:param authorization_code: AuthorizationCode object
|
||||
:return: user
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def validate_code_authorization_request(grant):
|
||||
request = grant.request
|
||||
client_id = request.client_id
|
||||
log.debug('Validate authorization request of %r', client_id)
|
||||
|
||||
if client_id is None:
|
||||
raise InvalidClientError(state=request.state)
|
||||
|
||||
client = grant.server.query_client(client_id)
|
||||
if not client:
|
||||
raise InvalidClientError(state=request.state)
|
||||
|
||||
redirect_uri = grant.validate_authorization_redirect_uri(request, client)
|
||||
response_type = request.response_type
|
||||
if not client.check_response_type(response_type):
|
||||
raise UnauthorizedClientError(
|
||||
f'The client is not authorized to use "response_type={response_type}"',
|
||||
state=grant.request.state,
|
||||
redirect_uri=redirect_uri,
|
||||
)
|
||||
|
||||
try:
|
||||
grant.request.client = client
|
||||
grant.validate_requested_scope()
|
||||
grant.execute_hook('after_validate_authorization_request')
|
||||
except OAuth2Error as error:
|
||||
error.redirect_uri = redirect_uri
|
||||
raise error
|
||||
return redirect_uri
|
||||
@@ -0,0 +1,162 @@
|
||||
from authlib.consts import default_json_headers
|
||||
from authlib.common.urls import urlparse
|
||||
from ..requests import OAuth2Request
|
||||
from ..errors import InvalidRequestError
|
||||
|
||||
|
||||
class BaseGrant:
|
||||
#: Allowed client auth methods for token endpoint
|
||||
TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_basic']
|
||||
|
||||
#: Designed for which "grant_type"
|
||||
GRANT_TYPE = None
|
||||
|
||||
# NOTE: there is no charset for application/json, since
|
||||
# application/json should always in UTF-8.
|
||||
# The example on RFC is incorrect.
|
||||
# https://tools.ietf.org/html/rfc4627
|
||||
TOKEN_RESPONSE_HEADER = default_json_headers
|
||||
|
||||
def __init__(self, request: OAuth2Request, server):
|
||||
self.prompt = None
|
||||
self.redirect_uri = None
|
||||
self.request = request
|
||||
self.server = server
|
||||
self._hooks = {
|
||||
'after_validate_authorization_request': set(),
|
||||
'after_validate_consent_request': set(),
|
||||
'after_validate_token_request': set(),
|
||||
'process_token': set(),
|
||||
}
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
return self.request.client
|
||||
|
||||
def generate_token(self, user=None, scope=None, grant_type=None,
|
||||
expires_in=None, include_refresh_token=True):
|
||||
if grant_type is None:
|
||||
grant_type = self.GRANT_TYPE
|
||||
return self.server.generate_token(
|
||||
client=self.request.client,
|
||||
grant_type=grant_type,
|
||||
user=user,
|
||||
scope=scope,
|
||||
expires_in=expires_in,
|
||||
include_refresh_token=include_refresh_token,
|
||||
)
|
||||
|
||||
def authenticate_token_endpoint_client(self):
|
||||
"""Authenticate client with the given methods for token endpoint.
|
||||
|
||||
For example, the client makes the following HTTP request using TLS:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
POST /token HTTP/1.1
|
||||
Host: server.example.com
|
||||
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
|
||||
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
|
||||
|
||||
Default available methods are: "none", "client_secret_basic" and
|
||||
"client_secret_post".
|
||||
|
||||
:return: client
|
||||
"""
|
||||
client = self.server.authenticate_client(
|
||||
self.request, self.TOKEN_ENDPOINT_AUTH_METHODS)
|
||||
self.server.send_signal(
|
||||
'after_authenticate_client',
|
||||
client=client, grant=self)
|
||||
return client
|
||||
|
||||
def save_token(self, token):
|
||||
"""A method to save token into database."""
|
||||
return self.server.save_token(token, self.request)
|
||||
|
||||
def validate_requested_scope(self):
|
||||
"""Validate if requested scope is supported by Authorization Server."""
|
||||
scope = self.request.scope
|
||||
state = self.request.state
|
||||
return self.server.validate_requested_scope(scope, state)
|
||||
|
||||
def register_hook(self, hook_type, hook):
|
||||
if hook_type not in self._hooks:
|
||||
raise ValueError('Hook type %s is not in %s.',
|
||||
hook_type, self._hooks)
|
||||
self._hooks[hook_type].add(hook)
|
||||
|
||||
def execute_hook(self, hook_type, *args, **kwargs):
|
||||
for hook in self._hooks[hook_type]:
|
||||
hook(self, *args, **kwargs)
|
||||
|
||||
|
||||
class TokenEndpointMixin:
|
||||
#: Allowed HTTP methods of this token endpoint
|
||||
TOKEN_ENDPOINT_HTTP_METHODS = ['POST']
|
||||
|
||||
#: Designed for which "grant_type"
|
||||
GRANT_TYPE = None
|
||||
|
||||
@classmethod
|
||||
def check_token_endpoint(cls, request: OAuth2Request):
|
||||
return request.grant_type == cls.GRANT_TYPE and \
|
||||
request.method in cls.TOKEN_ENDPOINT_HTTP_METHODS
|
||||
|
||||
def validate_token_request(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_token_response(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class AuthorizationEndpointMixin:
|
||||
RESPONSE_TYPES = set()
|
||||
ERROR_RESPONSE_FRAGMENT = False
|
||||
|
||||
@classmethod
|
||||
def check_authorization_endpoint(cls, request: OAuth2Request):
|
||||
return request.response_type in cls.RESPONSE_TYPES
|
||||
|
||||
@staticmethod
|
||||
def validate_authorization_redirect_uri(request: OAuth2Request, client):
|
||||
if request.redirect_uri:
|
||||
if not client.check_redirect_uri(request.redirect_uri):
|
||||
raise InvalidRequestError(
|
||||
f'Redirect URI {request.redirect_uri} is not supported by client.',
|
||||
state=request.state)
|
||||
return request.redirect_uri
|
||||
else:
|
||||
redirect_uri = client.get_default_redirect_uri()
|
||||
if not redirect_uri:
|
||||
raise InvalidRequestError(
|
||||
'Missing "redirect_uri" in request.',
|
||||
state=request.state)
|
||||
return redirect_uri
|
||||
|
||||
@staticmethod
|
||||
def validate_no_multiple_request_parameter(request: OAuth2Request):
|
||||
"""For the Authorization Endpoint, request and response parameters MUST NOT be included
|
||||
more than once. Per `Section 3.1`_.
|
||||
|
||||
.. _`Section 3.1`: https://tools.ietf.org/html/rfc6749#section-3.1
|
||||
"""
|
||||
datalist = request.datalist
|
||||
parameters = ["response_type", "client_id", "redirect_uri", "scope", "state"]
|
||||
for param in parameters:
|
||||
if len(datalist.get(param, [])) > 1:
|
||||
raise InvalidRequestError(f'Multiple "{param}" in request.', state=request.state)
|
||||
|
||||
def validate_consent_request(self):
|
||||
redirect_uri = self.validate_authorization_request()
|
||||
self.execute_hook('after_validate_consent_request', redirect_uri)
|
||||
self.redirect_uri = redirect_uri
|
||||
|
||||
def validate_authorization_request(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_authorization_response(self, redirect_uri: str, grant_user):
|
||||
raise NotImplementedError()
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
import logging
|
||||
from .base import BaseGrant, TokenEndpointMixin
|
||||
from ..errors import UnauthorizedClientError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClientCredentialsGrant(BaseGrant, TokenEndpointMixin):
|
||||
"""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 client credentials grant type MUST only be used by confidential
|
||||
clients::
|
||||
|
||||
+---------+ +---------------+
|
||||
| | | |
|
||||
| |>--(A)- Client Authentication --->| Authorization |
|
||||
| Client | | Server |
|
||||
| |<--(B)---- Access Token ---------<| |
|
||||
| | | |
|
||||
+---------+ +---------------+
|
||||
|
||||
https://tools.ietf.org/html/rfc6749#section-4.4
|
||||
"""
|
||||
GRANT_TYPE = 'client_credentials'
|
||||
|
||||
def validate_token_request(self):
|
||||
"""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 "client_credentials".
|
||||
|
||||
scope
|
||||
OPTIONAL. The scope of the access request as described by
|
||||
Section 3.3.
|
||||
|
||||
The client MUST authenticate with the authorization server as
|
||||
described in Section 3.2.1.
|
||||
|
||||
For example, the client makes the following HTTP request using
|
||||
transport-layer security (with extra line breaks for display purposes
|
||||
only):
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
POST /token HTTP/1.1
|
||||
Host: server.example.com
|
||||
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=client_credentials
|
||||
|
||||
The authorization server MUST authenticate the client.
|
||||
"""
|
||||
|
||||
# ignore validate for grant_type, since it is validated by
|
||||
# check_token_endpoint
|
||||
client = self.authenticate_token_endpoint_client()
|
||||
log.debug('Validate token request of %r', client)
|
||||
|
||||
if not client.check_grant_type(self.GRANT_TYPE):
|
||||
raise UnauthorizedClientError()
|
||||
|
||||
self.request.client = client
|
||||
self.validate_requested_scope()
|
||||
|
||||
def create_token_response(self):
|
||||
"""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.
|
||||
|
||||
An example successful response:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Cache-Control: no-store
|
||||
Pragma: no-cache
|
||||
|
||||
{
|
||||
"access_token":"2YotnFZFEjr1zCsicMWpAA",
|
||||
"token_type":"example",
|
||||
"expires_in":3600,
|
||||
"example_parameter":"example_value"
|
||||
}
|
||||
|
||||
:returns: (status_code, body, headers)
|
||||
"""
|
||||
token = self.generate_token(scope=self.request.scope, include_refresh_token=False)
|
||||
log.debug('Issue token %r to %r', token, self.client)
|
||||
self.save_token(token)
|
||||
self.execute_hook('process_token', self, token=token)
|
||||
return 200, token, self.TOKEN_RESPONSE_HEADER
|
||||
@@ -0,0 +1,229 @@
|
||||
import logging
|
||||
from authlib.common.urls import add_params_to_uri
|
||||
from .base import BaseGrant, AuthorizationEndpointMixin
|
||||
from ..errors import (
|
||||
OAuth2Error,
|
||||
UnauthorizedClientError,
|
||||
AccessDeniedError,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ImplicitGrant(BaseGrant, AuthorizationEndpointMixin):
|
||||
"""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.
|
||||
|
||||
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.
|
||||
|
||||
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 |
|
||||
| |
|
||||
+---------+
|
||||
"""
|
||||
#: authorization_code grant type has authorization endpoint
|
||||
AUTHORIZATION_ENDPOINT = True
|
||||
#: Allowed client auth methods for token endpoint
|
||||
TOKEN_ENDPOINT_AUTH_METHODS = ['none']
|
||||
|
||||
RESPONSE_TYPES = {'token'}
|
||||
GRANT_TYPE = 'implicit'
|
||||
ERROR_RESPONSE_FRAGMENT = True
|
||||
|
||||
def validate_authorization_request(self):
|
||||
"""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 `Section 4.2.1`_.
|
||||
|
||||
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 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.
|
||||
|
||||
For example, the client directs the user-agent to make the following
|
||||
HTTP request using TLS:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz
|
||||
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
|
||||
Host: server.example.com
|
||||
|
||||
.. _`Section 4.2.1`: https://tools.ietf.org/html/rfc6749#section-4.2.1
|
||||
"""
|
||||
# ignore validate for response_type, since it is validated by
|
||||
# check_authorization_endpoint
|
||||
|
||||
# The implicit grant type is optimized for public clients
|
||||
client = self.authenticate_token_endpoint_client()
|
||||
log.debug('Validate authorization request of %r', client)
|
||||
|
||||
redirect_uri = self.validate_authorization_redirect_uri(
|
||||
self.request, client)
|
||||
|
||||
response_type = self.request.response_type
|
||||
if not client.check_response_type(response_type):
|
||||
raise UnauthorizedClientError(
|
||||
'The client is not authorized to use '
|
||||
'"response_type={}"'.format(response_type),
|
||||
state=self.request.state,
|
||||
redirect_uri=redirect_uri,
|
||||
redirect_fragment=True,
|
||||
)
|
||||
|
||||
try:
|
||||
self.request.client = client
|
||||
self.validate_requested_scope()
|
||||
self.execute_hook('after_validate_authorization_request')
|
||||
except OAuth2Error as error:
|
||||
error.redirect_uri = redirect_uri
|
||||
error.redirect_fragment = True
|
||||
raise error
|
||||
return redirect_uri
|
||||
|
||||
def create_authorization_response(self, redirect_uri, grant_user):
|
||||
"""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 `Section 4.2.2`_.
|
||||
|
||||
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.
|
||||
|
||||
For example, the authorization server redirects the user-agent by
|
||||
sending the following HTTP response:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 302 Found
|
||||
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
|
||||
&state=xyz&token_type=example&expires_in=3600
|
||||
|
||||
Developers should note that some user-agents do not support the
|
||||
inclusion of a fragment component in the HTTP "Location" response
|
||||
header field. Such clients will require using other methods for
|
||||
redirecting the client than a 3xx redirection response -- for
|
||||
example, returning an HTML page that includes a 'continue' button
|
||||
with an action linked to the redirection URI.
|
||||
|
||||
.. _`Section 4.2.2`: https://tools.ietf.org/html/rfc6749#section-4.2.2
|
||||
|
||||
:param redirect_uri: Redirect to the given URI for the authorization
|
||||
:param grant_user: if resource owner granted the request, pass this
|
||||
resource owner, otherwise pass None.
|
||||
:returns: (status_code, body, headers)
|
||||
"""
|
||||
state = self.request.state
|
||||
if grant_user:
|
||||
self.request.user = grant_user
|
||||
token = self.generate_token(
|
||||
user=grant_user,
|
||||
scope=self.request.scope,
|
||||
include_refresh_token=False,
|
||||
)
|
||||
log.debug('Grant token %r to %r', token, self.request.client)
|
||||
|
||||
self.save_token(token)
|
||||
self.execute_hook('process_token', token=token)
|
||||
params = [(k, token[k]) for k in token]
|
||||
if state:
|
||||
params.append(('state', state))
|
||||
|
||||
uri = add_params_to_uri(redirect_uri, params, fragment=True)
|
||||
headers = [('Location', uri)]
|
||||
return 302, '', headers
|
||||
else:
|
||||
raise AccessDeniedError(
|
||||
state=state,
|
||||
redirect_uri=redirect_uri,
|
||||
redirect_fragment=True
|
||||
)
|
||||
@@ -0,0 +1,179 @@
|
||||
"""
|
||||
authlib.oauth2.rfc6749.grants.refresh_token
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A special grant endpoint for refresh_token grant_type. Refreshing an
|
||||
Access Token per `Section 6`_.
|
||||
|
||||
.. _`Section 6`: https://tools.ietf.org/html/rfc6749#section-6
|
||||
"""
|
||||
|
||||
import logging
|
||||
from .base import BaseGrant, TokenEndpointMixin
|
||||
from ..util import scope_to_list
|
||||
from ..errors import (
|
||||
InvalidRequestError,
|
||||
InvalidScopeError,
|
||||
InvalidGrantError,
|
||||
UnauthorizedClientError,
|
||||
)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RefreshTokenGrant(BaseGrant, TokenEndpointMixin):
|
||||
"""A special grant endpoint for refresh_token grant_type. Refreshing an
|
||||
Access Token per `Section 6`_.
|
||||
|
||||
.. _`Section 6`: https://tools.ietf.org/html/rfc6749#section-6
|
||||
"""
|
||||
GRANT_TYPE = 'refresh_token'
|
||||
|
||||
#: The authorization server MAY issue a new refresh token
|
||||
INCLUDE_NEW_REFRESH_TOKEN = False
|
||||
|
||||
def _validate_request_client(self):
|
||||
# require client authentication for confidential clients or for any
|
||||
# client that was issued client credentials (or with other
|
||||
# authentication requirements)
|
||||
client = self.authenticate_token_endpoint_client()
|
||||
log.debug('Validate token request of %r', client)
|
||||
|
||||
if not client.check_grant_type(self.GRANT_TYPE):
|
||||
raise UnauthorizedClientError()
|
||||
|
||||
return client
|
||||
|
||||
def _validate_request_token(self, client):
|
||||
refresh_token = self.request.form.get('refresh_token')
|
||||
if refresh_token is None:
|
||||
raise InvalidRequestError('Missing "refresh_token" in request.')
|
||||
|
||||
token = self.authenticate_refresh_token(refresh_token)
|
||||
if not token or not token.check_client(client):
|
||||
raise InvalidGrantError()
|
||||
return token
|
||||
|
||||
def _validate_token_scope(self, token):
|
||||
scope = self.request.scope
|
||||
if not scope:
|
||||
return
|
||||
|
||||
original_scope = token.get_scope()
|
||||
if not original_scope:
|
||||
raise InvalidScopeError()
|
||||
|
||||
original_scope = set(scope_to_list(original_scope))
|
||||
if not original_scope.issuperset(set(scope_to_list(scope))):
|
||||
raise InvalidScopeError()
|
||||
|
||||
def validate_token_request(self):
|
||||
"""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 per Appendix B with a character encoding of UTF-8 in the HTTP
|
||||
request entity-body, per Section 6:
|
||||
|
||||
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.
|
||||
|
||||
|
||||
For example, the client makes the following HTTP request using
|
||||
transport-layer security (with extra line breaks for display purposes
|
||||
only):
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
POST /token HTTP/1.1
|
||||
Host: server.example.com
|
||||
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
|
||||
"""
|
||||
client = self._validate_request_client()
|
||||
self.request.client = client
|
||||
refresh_token = self._validate_request_token(client)
|
||||
self._validate_token_scope(refresh_token)
|
||||
self.request.refresh_token = refresh_token
|
||||
|
||||
def create_token_response(self):
|
||||
"""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.
|
||||
"""
|
||||
refresh_token = self.request.refresh_token
|
||||
user = self.authenticate_user(refresh_token)
|
||||
if not user:
|
||||
raise InvalidRequestError('There is no "user" for this token.')
|
||||
|
||||
client = self.request.client
|
||||
token = self.issue_token(user, refresh_token)
|
||||
log.debug('Issue token %r to %r', token, client)
|
||||
|
||||
self.request.user = user
|
||||
self.save_token(token)
|
||||
self.execute_hook('process_token', token=token)
|
||||
self.revoke_old_credential(refresh_token)
|
||||
return 200, token, self.TOKEN_RESPONSE_HEADER
|
||||
|
||||
def issue_token(self, user, refresh_token):
|
||||
scope = self.request.scope
|
||||
if not scope:
|
||||
scope = refresh_token.get_scope()
|
||||
|
||||
token = self.generate_token(
|
||||
user=user,
|
||||
scope=scope,
|
||||
include_refresh_token=self.INCLUDE_NEW_REFRESH_TOKEN,
|
||||
)
|
||||
return token
|
||||
|
||||
def authenticate_refresh_token(self, refresh_token):
|
||||
"""Get token information with refresh_token string. Developers MUST
|
||||
implement this method in subclass::
|
||||
|
||||
def authenticate_refresh_token(self, refresh_token):
|
||||
token = Token.get(refresh_token=refresh_token)
|
||||
if token and not token.refresh_token_revoked:
|
||||
return token
|
||||
|
||||
:param refresh_token: The refresh token issued to the client
|
||||
:return: token
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def authenticate_user(self, refresh_token):
|
||||
"""Authenticate the user related to this credential. Developers MUST
|
||||
implement this method in subclass::
|
||||
|
||||
def authenticate_user(self, credential):
|
||||
return User.get(credential.user_id)
|
||||
|
||||
:param refresh_token: Token object
|
||||
:return: user
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def revoke_old_credential(self, refresh_token):
|
||||
"""The authorization server MAY revoke the old refresh token after
|
||||
issuing a new refresh token to the client. Developers MUST implement
|
||||
this method in subclass::
|
||||
|
||||
def revoke_old_credential(self, refresh_token):
|
||||
credential.revoked = True
|
||||
credential.save()
|
||||
|
||||
:param refresh_token: Token object
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
import logging
|
||||
from .base import BaseGrant, TokenEndpointMixin
|
||||
from ..errors import (
|
||||
UnauthorizedClientError,
|
||||
InvalidRequestError,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResourceOwnerPasswordCredentialsGrant(BaseGrant, TokenEndpointMixin):
|
||||
"""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) | |
|
||||
+---------+ +---------------+
|
||||
"""
|
||||
GRANT_TYPE = 'password'
|
||||
|
||||
def validate_token_request(self):
|
||||
"""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.
|
||||
|
||||
For example, the client makes the following HTTP request using
|
||||
transport-layer security (with extra line breaks for display purposes
|
||||
only):
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
POST /token HTTP/1.1
|
||||
Host: server.example.com
|
||||
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=password&username=johndoe&password=A3ddj3w
|
||||
"""
|
||||
# ignore validate for grant_type, since it is validated by
|
||||
# check_token_endpoint
|
||||
client = self.authenticate_token_endpoint_client()
|
||||
log.debug('Validate token request of %r', client)
|
||||
|
||||
if not client.check_grant_type(self.GRANT_TYPE):
|
||||
raise UnauthorizedClientError()
|
||||
|
||||
params = self.request.form
|
||||
if 'username' not in params:
|
||||
raise InvalidRequestError('Missing "username" in request.')
|
||||
if 'password' not in params:
|
||||
raise InvalidRequestError('Missing "password" in request.')
|
||||
|
||||
log.debug('Authenticate user of %r', params['username'])
|
||||
user = self.authenticate_user(
|
||||
params['username'],
|
||||
params['password']
|
||||
)
|
||||
if not user:
|
||||
raise InvalidRequestError(
|
||||
'Invalid "username" or "password" in request.',
|
||||
)
|
||||
self.request.client = client
|
||||
self.request.user = user
|
||||
self.validate_requested_scope()
|
||||
|
||||
def create_token_response(self):
|
||||
"""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.
|
||||
|
||||
An example successful response:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Cache-Control: no-store
|
||||
Pragma: no-cache
|
||||
|
||||
{
|
||||
"access_token":"2YotnFZFEjr1zCsicMWpAA",
|
||||
"token_type":"example",
|
||||
"expires_in":3600,
|
||||
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
|
||||
"example_parameter":"example_value"
|
||||
}
|
||||
|
||||
:returns: (status_code, body, headers)
|
||||
"""
|
||||
user = self.request.user
|
||||
scope = self.request.scope
|
||||
token = self.generate_token(user=user, scope=scope)
|
||||
log.debug('Issue token %r to %r', token, self.client)
|
||||
self.save_token(token)
|
||||
self.execute_hook('process_token', token=token)
|
||||
return 200, token, self.TOKEN_RESPONSE_HEADER
|
||||
|
||||
def authenticate_user(self, username, password):
|
||||
"""validate the resource owner password credentials using its
|
||||
existing password validation algorithm::
|
||||
|
||||
def authenticate_user(self, username, password):
|
||||
user = get_user_by_username(username)
|
||||
if user.check_password(password):
|
||||
return user
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
Reference in New Issue
Block a user