venv added, updated
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
authlib.oauth2.rfc7523
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module represents a direct implementation of
|
||||
JSON Web Token (JWT) Profile for OAuth 2.0 Client
|
||||
Authentication and Authorization Grants.
|
||||
|
||||
https://tools.ietf.org/html/rfc7523
|
||||
"""
|
||||
|
||||
from .jwt_bearer import JWTBearerGrant
|
||||
from .client import (
|
||||
JWTBearerClientAssertion,
|
||||
)
|
||||
from .assertion import (
|
||||
client_secret_jwt_sign,
|
||||
private_key_jwt_sign,
|
||||
)
|
||||
from .auth import (
|
||||
ClientSecretJWT, PrivateKeyJWT,
|
||||
)
|
||||
from .token import JWTBearerTokenGenerator
|
||||
from .validator import JWTBearerToken, JWTBearerTokenValidator
|
||||
|
||||
__all__ = [
|
||||
'JWTBearerGrant',
|
||||
'JWTBearerClientAssertion',
|
||||
'client_secret_jwt_sign',
|
||||
'private_key_jwt_sign',
|
||||
'ClientSecretJWT',
|
||||
'PrivateKeyJWT',
|
||||
|
||||
'JWTBearerToken',
|
||||
'JWTBearerTokenGenerator',
|
||||
'JWTBearerTokenValidator',
|
||||
]
|
||||
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.
@@ -0,0 +1,66 @@
|
||||
import time
|
||||
from authlib.jose import jwt
|
||||
from authlib.common.security import generate_token
|
||||
|
||||
|
||||
def sign_jwt_bearer_assertion(
|
||||
key, issuer, audience, subject=None, issued_at=None,
|
||||
expires_at=None, claims=None, header=None, **kwargs):
|
||||
|
||||
if header is None:
|
||||
header = {}
|
||||
alg = kwargs.pop('alg', None)
|
||||
if alg:
|
||||
header['alg'] = alg
|
||||
if 'alg' not in header:
|
||||
raise ValueError('Missing "alg" in header')
|
||||
|
||||
payload = {'iss': issuer, 'aud': audience}
|
||||
|
||||
# subject is not required in Google service
|
||||
if subject:
|
||||
payload['sub'] = subject
|
||||
|
||||
if not issued_at:
|
||||
issued_at = int(time.time())
|
||||
|
||||
expires_in = kwargs.pop('expires_in', 3600)
|
||||
if not expires_at:
|
||||
expires_at = issued_at + expires_in
|
||||
|
||||
payload['iat'] = issued_at
|
||||
payload['exp'] = expires_at
|
||||
|
||||
if claims:
|
||||
payload.update(claims)
|
||||
|
||||
return jwt.encode(header, payload, key)
|
||||
|
||||
|
||||
def client_secret_jwt_sign(client_secret, client_id, token_endpoint, alg='HS256',
|
||||
claims=None, **kwargs):
|
||||
return _sign(client_secret, client_id, token_endpoint, alg, claims, **kwargs)
|
||||
|
||||
|
||||
def private_key_jwt_sign(private_key, client_id, token_endpoint, alg='RS256',
|
||||
claims=None, **kwargs):
|
||||
return _sign(private_key, client_id, token_endpoint, alg, claims, **kwargs)
|
||||
|
||||
|
||||
def _sign(key, client_id, token_endpoint, alg, claims=None, **kwargs):
|
||||
# REQUIRED. Issuer. This MUST contain the client_id of the OAuth Client.
|
||||
issuer = client_id
|
||||
# REQUIRED. Subject. This MUST contain the client_id of the OAuth Client.
|
||||
subject = client_id
|
||||
# The Audience SHOULD be the URL of the Authorization Server's Token Endpoint.
|
||||
audience = token_endpoint
|
||||
|
||||
# jti is required
|
||||
if claims is None:
|
||||
claims = {}
|
||||
if 'jti' not in claims:
|
||||
claims['jti'] = generate_token(36)
|
||||
|
||||
return sign_jwt_bearer_assertion(
|
||||
key=key, issuer=issuer, audience=audience, subject=subject,
|
||||
claims=claims, alg=alg, **kwargs)
|
||||
@@ -0,0 +1,94 @@
|
||||
from authlib.common.urls import add_params_to_qs
|
||||
from .assertion import client_secret_jwt_sign, private_key_jwt_sign
|
||||
from .client import ASSERTION_TYPE
|
||||
|
||||
|
||||
class ClientSecretJWT:
|
||||
"""Authentication method for OAuth 2.0 Client. This authentication
|
||||
method is called ``client_secret_jwt``, which is using ``client_id``
|
||||
and ``client_secret`` constructed with JWT to identify a client.
|
||||
|
||||
Here is an example of use ``client_secret_jwt`` with Requests Session::
|
||||
|
||||
from authlib.integrations.requests_client import OAuth2Session
|
||||
|
||||
token_endpoint = 'https://example.com/oauth/token'
|
||||
session = OAuth2Session(
|
||||
'your-client-id', 'your-client-secret',
|
||||
token_endpoint_auth_method='client_secret_jwt'
|
||||
)
|
||||
session.register_client_auth_method(ClientSecretJWT(token_endpoint))
|
||||
session.fetch_token(token_endpoint)
|
||||
|
||||
:param token_endpoint: A string URL of the token endpoint
|
||||
:param claims: Extra JWT claims
|
||||
:param headers: Extra JWT headers
|
||||
:param alg: ``alg`` value, default is HS256
|
||||
"""
|
||||
name = 'client_secret_jwt'
|
||||
alg = 'HS256'
|
||||
|
||||
def __init__(self, token_endpoint=None, claims=None, headers=None, alg=None):
|
||||
self.token_endpoint = token_endpoint
|
||||
self.claims = claims
|
||||
self.headers = headers
|
||||
if alg is not None:
|
||||
self.alg = alg
|
||||
|
||||
def sign(self, auth, token_endpoint):
|
||||
return client_secret_jwt_sign(
|
||||
auth.client_secret,
|
||||
client_id=auth.client_id,
|
||||
token_endpoint=token_endpoint,
|
||||
claims=self.claims,
|
||||
header=self.headers,
|
||||
alg=self.alg,
|
||||
)
|
||||
|
||||
def __call__(self, auth, method, uri, headers, body):
|
||||
token_endpoint = self.token_endpoint
|
||||
if not token_endpoint:
|
||||
token_endpoint = uri
|
||||
|
||||
client_assertion = self.sign(auth, token_endpoint)
|
||||
body = add_params_to_qs(body or '', [
|
||||
('client_assertion_type', ASSERTION_TYPE),
|
||||
('client_assertion', client_assertion)
|
||||
])
|
||||
return uri, headers, body
|
||||
|
||||
|
||||
class PrivateKeyJWT(ClientSecretJWT):
|
||||
"""Authentication method for OAuth 2.0 Client. This authentication
|
||||
method is called ``private_key_jwt``, which is using ``client_id``
|
||||
and ``private_key`` constructed with JWT to identify a client.
|
||||
|
||||
Here is an example of use ``private_key_jwt`` with Requests Session::
|
||||
|
||||
from authlib.integrations.requests_client import OAuth2Session
|
||||
|
||||
token_endpoint = 'https://example.com/oauth/token'
|
||||
session = OAuth2Session(
|
||||
'your-client-id', 'your-client-private-key',
|
||||
token_endpoint_auth_method='private_key_jwt'
|
||||
)
|
||||
session.register_client_auth_method(PrivateKeyJWT(token_endpoint))
|
||||
session.fetch_token(token_endpoint)
|
||||
|
||||
:param token_endpoint: A string URL of the token endpoint
|
||||
:param claims: Extra JWT claims
|
||||
:param headers: Extra JWT headers
|
||||
:param alg: ``alg`` value, default is RS256
|
||||
"""
|
||||
name = 'private_key_jwt'
|
||||
alg = 'RS256'
|
||||
|
||||
def sign(self, auth, token_endpoint):
|
||||
return private_key_jwt_sign(
|
||||
auth.client_secret,
|
||||
client_id=auth.client_id,
|
||||
token_endpoint=token_endpoint,
|
||||
claims=self.claims,
|
||||
header=self.headers,
|
||||
alg=self.alg,
|
||||
)
|
||||
@@ -0,0 +1,113 @@
|
||||
import logging
|
||||
from authlib.jose import jwt
|
||||
from authlib.jose.errors import JoseError
|
||||
from ..rfc6749 import InvalidClientError
|
||||
|
||||
ASSERTION_TYPE = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JWTBearerClientAssertion:
|
||||
"""Implementation of Using JWTs for Client Authentication, which is
|
||||
defined by RFC7523.
|
||||
"""
|
||||
#: Value of ``client_assertion_type`` of JWTs
|
||||
CLIENT_ASSERTION_TYPE = ASSERTION_TYPE
|
||||
#: Name of the client authentication method
|
||||
CLIENT_AUTH_METHOD = 'client_assertion_jwt'
|
||||
|
||||
def __init__(self, token_url, validate_jti=True):
|
||||
self.token_url = token_url
|
||||
self._validate_jti = validate_jti
|
||||
|
||||
def __call__(self, query_client, request):
|
||||
data = request.form
|
||||
assertion_type = data.get('client_assertion_type')
|
||||
assertion = data.get('client_assertion')
|
||||
if assertion_type == ASSERTION_TYPE and assertion:
|
||||
resolve_key = self.create_resolve_key_func(query_client, request)
|
||||
self.process_assertion_claims(assertion, resolve_key)
|
||||
return self.authenticate_client(request.client)
|
||||
log.debug('Authenticate via %r failed', self.CLIENT_AUTH_METHOD)
|
||||
|
||||
def create_claims_options(self):
|
||||
"""Create a claims_options for verify JWT payload claims. Developers
|
||||
MAY overwrite this method to create a more strict options."""
|
||||
# https://tools.ietf.org/html/rfc7523#section-3
|
||||
# The Audience SHOULD be the URL of the Authorization Server's Token Endpoint
|
||||
options = {
|
||||
'iss': {'essential': True, 'validate': _validate_iss},
|
||||
'sub': {'essential': True},
|
||||
'aud': {'essential': True, 'value': self.token_url},
|
||||
'exp': {'essential': True},
|
||||
}
|
||||
if self._validate_jti:
|
||||
options['jti'] = {'essential': True, 'validate': self.validate_jti}
|
||||
return options
|
||||
|
||||
def process_assertion_claims(self, assertion, resolve_key):
|
||||
"""Extract JWT payload claims from request "assertion", per
|
||||
`Section 3.1`_.
|
||||
|
||||
:param assertion: assertion string value in the request
|
||||
:param resolve_key: function to resolve the sign key
|
||||
:return: JWTClaims
|
||||
:raise: InvalidClientError
|
||||
|
||||
.. _`Section 3.1`: https://tools.ietf.org/html/rfc7523#section-3.1
|
||||
"""
|
||||
try:
|
||||
claims = jwt.decode(
|
||||
assertion, resolve_key,
|
||||
claims_options=self.create_claims_options()
|
||||
)
|
||||
claims.validate()
|
||||
except JoseError as e:
|
||||
log.debug('Assertion Error: %r', e)
|
||||
raise InvalidClientError()
|
||||
return claims
|
||||
|
||||
def authenticate_client(self, client):
|
||||
if client.check_endpoint_auth_method(self.CLIENT_AUTH_METHOD, 'token'):
|
||||
return client
|
||||
raise InvalidClientError()
|
||||
|
||||
def create_resolve_key_func(self, query_client, request):
|
||||
def resolve_key(headers, payload):
|
||||
# https://tools.ietf.org/html/rfc7523#section-3
|
||||
# For client authentication, the subject MUST be the
|
||||
# "client_id" of the OAuth client
|
||||
client_id = payload['sub']
|
||||
client = query_client(client_id)
|
||||
if not client:
|
||||
raise InvalidClientError()
|
||||
request.client = client
|
||||
return self.resolve_client_public_key(client, headers)
|
||||
return resolve_key
|
||||
|
||||
def validate_jti(self, claims, jti):
|
||||
"""Validate if the given ``jti`` value is used before. Developers
|
||||
MUST implement this method::
|
||||
|
||||
def validate_jti(self, claims, jti):
|
||||
key = 'jti:{}-{}'.format(claims['sub'], jti)
|
||||
if redis.get(key):
|
||||
return False
|
||||
redis.set(key, 1, ex=3600)
|
||||
return True
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def resolve_client_public_key(self, client, headers):
|
||||
"""Resolve the client public key for verifying the JWT signature.
|
||||
A client may have many public keys, in this case, we can retrieve it
|
||||
via ``kid`` value in headers. Developers MUST implement this method::
|
||||
|
||||
def resolve_client_public_key(self, client, headers):
|
||||
return client.public_key
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def _validate_iss(claims, iss):
|
||||
return claims['sub'] == iss
|
||||
@@ -0,0 +1,182 @@
|
||||
import logging
|
||||
from authlib.jose import jwt, JoseError
|
||||
from ..rfc6749 import BaseGrant, TokenEndpointMixin
|
||||
from ..rfc6749 import (
|
||||
UnauthorizedClientError,
|
||||
InvalidRequestError,
|
||||
InvalidGrantError,
|
||||
InvalidClientError,
|
||||
)
|
||||
from .assertion import sign_jwt_bearer_assertion
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
JWT_BEARER_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
|
||||
|
||||
|
||||
class JWTBearerGrant(BaseGrant, TokenEndpointMixin):
|
||||
GRANT_TYPE = JWT_BEARER_GRANT_TYPE
|
||||
|
||||
#: Options for verifying JWT payload claims. Developers MAY
|
||||
#: overwrite this constant to create a more strict options.
|
||||
CLAIMS_OPTIONS = {
|
||||
'iss': {'essential': True},
|
||||
'aud': {'essential': True},
|
||||
'exp': {'essential': True},
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def sign(key, issuer, audience, subject=None,
|
||||
issued_at=None, expires_at=None, claims=None, **kwargs):
|
||||
return sign_jwt_bearer_assertion(
|
||||
key, issuer, audience, subject, issued_at,
|
||||
expires_at, claims, **kwargs)
|
||||
|
||||
def process_assertion_claims(self, assertion):
|
||||
"""Extract JWT payload claims from request "assertion", per
|
||||
`Section 3.1`_.
|
||||
|
||||
:param assertion: assertion string value in the request
|
||||
:return: JWTClaims
|
||||
:raise: InvalidGrantError
|
||||
|
||||
.. _`Section 3.1`: https://tools.ietf.org/html/rfc7523#section-3.1
|
||||
"""
|
||||
try:
|
||||
claims = jwt.decode(
|
||||
assertion, self.resolve_public_key,
|
||||
claims_options=self.CLAIMS_OPTIONS)
|
||||
claims.validate()
|
||||
except JoseError as e:
|
||||
log.debug('Assertion Error: %r', e)
|
||||
raise InvalidGrantError(description=e.description)
|
||||
return claims
|
||||
|
||||
def resolve_public_key(self, headers, payload):
|
||||
client = self.resolve_issuer_client(payload['iss'])
|
||||
return self.resolve_client_key(client, headers, payload)
|
||||
|
||||
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 2.1`_:
|
||||
|
||||
grant_type
|
||||
REQUIRED. Value MUST be set to
|
||||
"urn:ietf:params:oauth:grant-type:jwt-bearer".
|
||||
|
||||
assertion
|
||||
REQUIRED. Value MUST contain a single JWT.
|
||||
|
||||
scope
|
||||
OPTIONAL.
|
||||
|
||||
The following example demonstrates an access token request with a JWT
|
||||
as an authorization grant:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
POST /token.oauth2 HTTP/1.1
|
||||
Host: as.example.com
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
|
||||
&assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6IjE2In0.
|
||||
eyJpc3Mi[...omitted for brevity...].
|
||||
J9l-ZhwP[...omitted for brevity...]
|
||||
|
||||
.. _`Section 2.1`: https://tools.ietf.org/html/rfc7523#section-2.1
|
||||
"""
|
||||
assertion = self.request.form.get('assertion')
|
||||
if not assertion:
|
||||
raise InvalidRequestError('Missing "assertion" in request')
|
||||
|
||||
claims = self.process_assertion_claims(assertion)
|
||||
client = self.resolve_issuer_client(claims['iss'])
|
||||
log.debug('Validate token request of %s', client)
|
||||
|
||||
if not client.check_grant_type(self.GRANT_TYPE):
|
||||
raise UnauthorizedClientError()
|
||||
|
||||
self.request.client = client
|
||||
self.validate_requested_scope()
|
||||
|
||||
subject = claims.get('sub')
|
||||
if subject:
|
||||
user = self.authenticate_user(subject)
|
||||
if not user:
|
||||
raise InvalidGrantError(description='Invalid "sub" value in assertion')
|
||||
|
||||
log.debug('Check client(%s) permission to User(%s)', client, user)
|
||||
if not self.has_granted_permission(client, user):
|
||||
raise InvalidClientError(
|
||||
description='Client has no permission to access user data')
|
||||
self.request.user = user
|
||||
|
||||
def create_token_response(self):
|
||||
"""If valid and authorized, the authorization server issues an access
|
||||
token.
|
||||
"""
|
||||
token = self.generate_token(
|
||||
scope=self.request.scope,
|
||||
user=self.request.user,
|
||||
include_refresh_token=False,
|
||||
)
|
||||
log.debug('Issue token %r to %r', token, self.request.client)
|
||||
self.save_token(token)
|
||||
return 200, token, self.TOKEN_RESPONSE_HEADER
|
||||
|
||||
def resolve_issuer_client(self, issuer):
|
||||
"""Fetch client via "iss" in assertion claims. Developers MUST
|
||||
implement this method in subclass, e.g.::
|
||||
|
||||
def resolve_issuer_client(self, issuer):
|
||||
return Client.query_by_iss(issuer)
|
||||
|
||||
:param issuer: "iss" value in assertion
|
||||
:return: Client instance
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def resolve_client_key(self, client, headers, payload):
|
||||
"""Resolve client key to decode assertion data. Developers MUST
|
||||
implement this method in subclass. For instance, there is a
|
||||
"jwks" column on client table, e.g.::
|
||||
|
||||
def resolve_client_key(self, client, headers, payload):
|
||||
# from authlib.jose import JsonWebKey
|
||||
|
||||
key_set = JsonWebKey.import_key_set(client.jwks)
|
||||
return key_set.find_by_kid(headers['kid'])
|
||||
|
||||
:param client: instance of OAuth client model
|
||||
:param headers: headers part of the JWT
|
||||
:param payload: payload part of the JWT
|
||||
:return: ``authlib.jose.Key`` instance
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def authenticate_user(self, subject):
|
||||
"""Authenticate user with the given assertion claims. Developers MUST
|
||||
implement it in subclass, e.g.::
|
||||
|
||||
def authenticate_user(self, subject):
|
||||
return User.get_by_sub(subject)
|
||||
|
||||
:param subject: "sub" value in claims
|
||||
:return: User instance
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def has_granted_permission(self, client, user):
|
||||
"""Check if the client has permission to access the given user's resource.
|
||||
Developers MUST implement it in subclass, e.g.::
|
||||
|
||||
def has_granted_permission(self, client, user):
|
||||
permission = ClientUserGrant.query(client=client, user=user)
|
||||
return permission.granted
|
||||
|
||||
:param client: instance of OAuth client model
|
||||
:param user: instance of User model
|
||||
:return: bool
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
@@ -0,0 +1,93 @@
|
||||
import time
|
||||
from authlib.common.encoding import to_native
|
||||
from authlib.jose import jwt
|
||||
|
||||
|
||||
class JWTBearerTokenGenerator:
|
||||
"""A JSON Web Token formatted bearer token generator for jwt-bearer grant type.
|
||||
This token generator can be registered into authorization server::
|
||||
|
||||
authorization_server.register_token_generator(
|
||||
'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||
JWTBearerTokenGenerator(private_rsa_key),
|
||||
)
|
||||
|
||||
In this way, we can generate the token into JWT format. And we don't have to
|
||||
save this token into database, since it will be short time valid. Consider to
|
||||
rewrite ``JWTBearerGrant.save_token``::
|
||||
|
||||
class MyJWTBearerGrant(JWTBearerGrant):
|
||||
def save_token(self, token):
|
||||
pass
|
||||
|
||||
:param secret_key: private RSA key in bytes, JWK or JWK Set.
|
||||
:param issuer: a string or URI of the issuer
|
||||
:param alg: ``alg`` to use in JWT
|
||||
"""
|
||||
DEFAULT_EXPIRES_IN = 3600
|
||||
|
||||
def __init__(self, secret_key, issuer=None, alg='RS256'):
|
||||
self.secret_key = secret_key
|
||||
self.issuer = issuer
|
||||
self.alg = alg
|
||||
|
||||
@staticmethod
|
||||
def get_allowed_scope(client, scope):
|
||||
if scope:
|
||||
scope = client.get_allowed_scope(scope)
|
||||
return scope
|
||||
|
||||
@staticmethod
|
||||
def get_sub_value(user):
|
||||
"""Return user's ID as ``sub`` value in token payload. For instance::
|
||||
|
||||
@staticmethod
|
||||
def get_sub_value(user):
|
||||
return str(user.id)
|
||||
"""
|
||||
return user.get_user_id()
|
||||
|
||||
def get_token_data(self, grant_type, client, expires_in, user=None, scope=None):
|
||||
scope = self.get_allowed_scope(client, scope)
|
||||
issued_at = int(time.time())
|
||||
data = {
|
||||
'scope': scope,
|
||||
'grant_type': grant_type,
|
||||
'iat': issued_at,
|
||||
'exp': issued_at + expires_in,
|
||||
'client_id': client.get_client_id(),
|
||||
}
|
||||
if self.issuer:
|
||||
data['iss'] = self.issuer
|
||||
if user:
|
||||
data['sub'] = self.get_sub_value(user)
|
||||
return data
|
||||
|
||||
def generate(self, grant_type, client, user=None, scope=None, expires_in=None):
|
||||
"""Generate a bearer token for OAuth 2.0 authorization token endpoint.
|
||||
|
||||
:param client: the client that making the request.
|
||||
:param grant_type: current requested grant_type.
|
||||
:param user: current authorized user.
|
||||
:param expires_in: if provided, use this value as expires_in.
|
||||
:param scope: current requested scope.
|
||||
:return: Token dict
|
||||
"""
|
||||
if expires_in is None:
|
||||
expires_in = self.DEFAULT_EXPIRES_IN
|
||||
|
||||
token_data = self.get_token_data(grant_type, client, expires_in, user, scope)
|
||||
access_token = jwt.encode({'alg': self.alg}, token_data, key=self.secret_key, check=False)
|
||||
token = {
|
||||
'token_type': 'Bearer',
|
||||
'access_token': to_native(access_token),
|
||||
'expires_in': expires_in
|
||||
}
|
||||
if scope:
|
||||
token['scope'] = scope
|
||||
return token
|
||||
|
||||
def __call__(self, grant_type, client, user=None, scope=None,
|
||||
expires_in=None, include_refresh_token=True):
|
||||
# there is absolutely no refresh token in JWT format
|
||||
return self.generate(grant_type, client, user, scope, expires_in)
|
||||
@@ -0,0 +1,54 @@
|
||||
import time
|
||||
import logging
|
||||
from authlib.jose import jwt, JoseError, JWTClaims
|
||||
from ..rfc6749 import TokenMixin
|
||||
from ..rfc6750 import BearerTokenValidator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JWTBearerToken(TokenMixin, JWTClaims):
|
||||
def check_client(self, client):
|
||||
return self['client_id'] == client.get_client_id()
|
||||
|
||||
def get_scope(self):
|
||||
return self.get('scope')
|
||||
|
||||
def get_expires_in(self):
|
||||
return self['exp'] - self['iat']
|
||||
|
||||
def is_expired(self):
|
||||
return self['exp'] < time.time()
|
||||
|
||||
def is_revoked(self):
|
||||
return False
|
||||
|
||||
|
||||
class JWTBearerTokenValidator(BearerTokenValidator):
|
||||
TOKEN_TYPE = 'bearer'
|
||||
token_cls = JWTBearerToken
|
||||
|
||||
def __init__(self, public_key, issuer=None, realm=None, **extra_attributes):
|
||||
super().__init__(realm, **extra_attributes)
|
||||
self.public_key = public_key
|
||||
claims_options = {
|
||||
'exp': {'essential': True},
|
||||
'client_id': {'essential': True},
|
||||
'grant_type': {'essential': True},
|
||||
}
|
||||
if issuer:
|
||||
claims_options['iss'] = {'essential': True, 'value': issuer}
|
||||
self.claims_options = claims_options
|
||||
|
||||
def authenticate_token(self, token_string):
|
||||
try:
|
||||
claims = jwt.decode(
|
||||
token_string, self.public_key,
|
||||
claims_options=self.claims_options,
|
||||
claims_cls=self.token_cls,
|
||||
)
|
||||
claims.validate()
|
||||
return claims
|
||||
except JoseError as error:
|
||||
logger.debug('Authenticate token failed. %r', error)
|
||||
return None
|
||||
Reference in New Issue
Block a user