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',
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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