# Path: jose/jws.py
# def sign(payload, key, headers=None, algorithm=ALGORITHMS.HS256):
# def verify(token, key, algorithms, verify=True):
# def get_unverified_header(token):
# def get_unverified_headers(token):
# def get_unverified_claims(token):
# def _encode_header(algorithm, additional_headers=None):
# def _encode_payload(payload):
# def _sign_header_and_claims(encoded_header, encoded_claims, algorithm, key):
# def _load(jwt):
# def _sig_matches_keys(keys, signing_input, signature, alg):
# def _get_keys(key):
# def _verify_signature(signing_input, header, signature, key="", algorithms=None):
# 
# Path: jose/constants.py
# ALGORITHMS = Algorithms()
# 
# Path: jose/exceptions.py
# class ExpiredSignatureError(JWTError):
#     pass
# 
# class JWSError(JOSEError):
#     pass
# 
# class JWTClaimsError(JWTError):
#     pass
# 
# class JWTError(JOSEError):
#     pass
# 
# Path: jose/utils.py
# def calculate_at_hash(access_token, hash_alg):
#     """Helper method for calculating an access token
#     hash, as described in http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
# 
#     Its value is the base64url encoding of the left-most half of the hash of the octets
#     of the ASCII representation of the access_token value, where the hash algorithm
#     used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE
#     Header. For instance, if the alg is RS256, hash the access_token value with SHA-256,
#     then take the left-most 128 bits and base64url encode them. The at_hash value is a
#     case sensitive string.
# 
#     Args:
#         access_token (str): An access token string.
#         hash_alg (callable): A callable returning a hash object, e.g. hashlib.sha256
# 
#     """
#     hash_digest = hash_alg(access_token.encode("utf-8")).digest()
#     cut_at = int(len(hash_digest) / 2)
#     truncated = hash_digest[:cut_at]
#     at_hash = base64url_encode(truncated)
#     return at_hash.decode("utf-8")
# 
# def timedelta_total_seconds(delta):
#     """Helper method to determine the total number of seconds
#     from a timedelta.
# 
#     Args:
#         delta (timedelta): A timedelta to convert to seconds.
#     """
#     return delta.days * 24 * 60 * 60 + delta.seconds


import json
from calendar import timegm
from collections.abc import Mapping
from datetime import datetime, timedelta
from jose import jws
from .constants import ALGORITHMS
from .exceptions import ExpiredSignatureError, JWSError, JWTClaimsError, JWTError
from .utils import calculate_at_hash, timedelta_total_seconds
    if not isinstance(claims["jti"], str):
        raise JWTClaimsError("JWT ID must be a string.")


def _validate_at_hash(claims, access_token, algorithm):
    """
    Validates that the 'at_hash' is valid.

    Its value is the base64url encoding of the left-most half of the hash
    of the octets of the ASCII representation of the access_token value,
    where the hash algorithm used is the hash algorithm used in the alg
    Header Parameter of the ID Token's JOSE Header. For instance, if the
    alg is RS256, hash the access_token value with SHA-256, then take the
    left-most 128 bits and base64url encode them. The at_hash value is a
    case sensitive string.  Use of this claim is OPTIONAL.

    Args:
      claims (dict): The claims dictionary to validate.
      access_token (str): The access token returned by the OpenID Provider.
      algorithm (str): The algorithm used to sign the JWT, as specified by
          the token headers.
    """
    if "at_hash" not in claims:
        return

    if not access_token:
        msg = "No access_token provided to compare against at_hash claim."
        raise JWTClaimsError(msg)

    try:

# Path: jose/jws.py
# def sign(payload, key, headers=None, algorithm=ALGORITHMS.HS256):
# def verify(token, key, algorithms, verify=True):
# def get_unverified_header(token):
# def get_unverified_headers(token):
# def get_unverified_claims(token):
# def _encode_header(algorithm, additional_headers=None):
# def _encode_payload(payload):
# def _sign_header_and_claims(encoded_header, encoded_claims, algorithm, key):
# def _load(jwt):
# def _sig_matches_keys(keys, signing_input, signature, alg):
# def _get_keys(key):
# def _verify_signature(signing_input, header, signature, key="", algorithms=None):
# 
# Path: jose/constants.py
# ALGORITHMS = Algorithms()
# 
# Path: jose/exceptions.py
# class ExpiredSignatureError(JWTError):
#     pass
# 
# class JWSError(JOSEError):
#     pass
# 
# class JWTClaimsError(JWTError):
#     pass
# 
# class JWTError(JOSEError):
#     pass
# 
# Path: jose/utils.py
# def calculate_at_hash(access_token, hash_alg):
#     """Helper method for calculating an access token
#     hash, as described in http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
# 
#     Its value is the base64url encoding of the left-most half of the hash of the octets
#     of the ASCII representation of the access_token value, where the hash algorithm
#     used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE
#     Header. For instance, if the alg is RS256, hash the access_token value with SHA-256,
#     then take the left-most 128 bits and base64url encode them. The at_hash value is a
#     case sensitive string.
# 
#     Args:
#         access_token (str): An access token string.
#         hash_alg (callable): A callable returning a hash object, e.g. hashlib.sha256
# 
#     """
#     hash_digest = hash_alg(access_token.encode("utf-8")).digest()
#     cut_at = int(len(hash_digest) / 2)
#     truncated = hash_digest[:cut_at]
#     at_hash = base64url_encode(truncated)
#     return at_hash.decode("utf-8")
# 
# def timedelta_total_seconds(delta):
#     """Helper method to determine the total number of seconds
#     from a timedelta.
# 
#     Args:
#         delta (timedelta): A timedelta to convert to seconds.
#     """
#     return delta.days * 24 * 60 * 60 + delta.seconds


# Path: jose/jwt.py
import json
from calendar import timegm
from collections.abc import Mapping
from datetime import datetime, timedelta
from jose import jws
from .constants import ALGORITHMS
from .exceptions import ExpiredSignatureError, JWSError, JWTClaimsError, JWTError
from .utils import calculate_at_hash, timedelta_total_seconds

    if not isinstance(claims["jti"], str):
        raise JWTClaimsError("JWT ID must be a string.")


def _validate_at_hash(claims, access_token, algorithm):
    """
    Validates that the 'at_hash' is valid.

    Its value is the base64url encoding of the left-most half of the hash
    of the octets of the ASCII representation of the access_token value,
    where the hash algorithm used is the hash algorithm used in the alg
    Header Parameter of the ID Token's JOSE Header. For instance, if the
    alg is RS256, hash the access_token value with SHA-256, then take the
    left-most 128 bits and base64url encode them. The at_hash value is a
    case sensitive string.  Use of this claim is OPTIONAL.

    Args:
      claims (dict): The claims dictionary to validate.
      access_token (str): The access token returned by the OpenID Provider.
      algorithm (str): The algorithm used to sign the JWT, as specified by
          the token headers.
    """
    if "at_hash" not in claims:
        return

    if not access_token:
        msg = "No access_token provided to compare against at_hash claim."
        raise JWTClaimsError(msg)

    try:

        expected_hash = calculate_at_hash(access_token, ALGORITHMS.HASHES[algorithm])