Esempio n. 1
0
 def test_verify_iat(self, payload, leeway, raises, mocktimegm):
     mocktimegm.return_value = self.iat
     if raises:
         with self.assertRaises(JWTClaimsError) as ctx:
             utils.verify_iat(payload, leeway)
         self.assertEqual('Invalid Issued At(iat) Time', str(ctx.exception))
     else:
         self.assertIsNone(utils.verify_iat(payload, leeway))
Esempio n. 2
0
def verify_claims(payload, issuer, audience, cid_list):
    """ Validates Issuer, Client IDs, Audience
    Issued At time and Expiration in the Payload
    """
    verify_iss(payload, issuer)
    verify_cid(payload, cid_list)
    verify_aud(payload, audience)
    verify_exp(payload)
    verify_iat(payload)
Esempio n. 3
0
async def authenticate_user_by_alb_auth(request):
    aws_alb_auth_header_name = config.get(
        "get_user_by_aws_alb_auth_settings.aws_alb_auth_header_name",
        "X-Amzn-Oidc-Data")
    aws_alb_claims_header_name = config.get(
        "get_user_by_aws_alb_auth_settings.aws_alb_claims_header_name",
        "X-Amzn-Oidc-Accesstoken",
    )
    function = f"{__name__}.{sys._getframe().f_code.co_name}"
    log_data = {"function": function}
    encoded_auth_jwt = request.request.headers.get(aws_alb_auth_header_name)
    access_token = request.request.headers.get(aws_alb_claims_header_name)
    if not encoded_auth_jwt:
        raise Exception(f"Missing header: {aws_alb_auth_header_name}")
    if not access_token:
        raise Exception(f"Missing header: {aws_alb_claims_header_name}")

    jwt_headers = encoded_auth_jwt.split(".")[0]
    decoded_jwt_headers = base64.b64decode(jwt_headers)
    decoded_jwt_headers = decoded_jwt_headers.decode("utf-8")
    decoded_json = json.loads(decoded_jwt_headers)
    kid = decoded_json["kid"]
    # Step 2: Get the public key from regional endpoint
    url = "https://public-keys.auth.elb." + config.region + ".amazonaws.com/" + kid
    req = requests.get(url)
    pub_key = req.text
    # Step 3: Get the payload
    payload = jwt.decode(encoded_auth_jwt, pub_key, algorithms=["ES256"])
    email = payload.get(
        config.get("get_user_by_aws_alb_auth_settings.jwt_email_key", "email"))

    if not email:
        raise UnableToAuthenticate("Unable to determine user from ID Token")

    # Step 4: Parse the Access Token
    # User has already passed ALB auth and successfully authenticated
    access_token_pub_key = None
    jwt_verify = config.get("get_user_by_aws_alb_auth_settings.jwt_verify",
                            True)
    access_token_verify_options = {"verify_signature": jwt_verify}
    oidc_config = {}
    algorithm = None
    try:
        if jwt_verify:
            oidc_config = await populate_oidc_config()
            header = jwt.get_unverified_header(access_token)
            key_id = header["kid"]
            algorithm = header["alg"]
            if not algorithm:
                raise UnableToAuthenticate(
                    "Access Token header does not specify a signing algorithm."
                )
            access_token_pub_key = oidc_config["jwt_keys"][key_id]

        decoded_access_token = jwt.decode(
            access_token,
            access_token_pub_key,
            algorithms=[algorithm],
            options=access_token_verify_options,
            audience=oidc_config.get("aud"),
            issuer=oidc_config.get("issuer"),
        )
        # Step 5: Verify the access token.
        if not jwt_verify:
            verify_exp(access_token)
            verify_iat(access_token)

        # Extract groups from tokens, checking both because IdPs aren't consistent here
        for token in [decoded_access_token, payload]:
            groups = token.get(
                config.get("get_user_by_aws_alb_auth_settings.jwt_groups_key",
                           "groups"))
            if groups:
                break

    except jwt.exceptions.DecodeError as e:
        # This exception occurs when the access token is not JWT-parsable. It is expected with some IdPs.
        log.debug({
            **log_data,
            "message":
            ("Unable to decode claims token. This is expected for some Identity Providers."
             ),
            "error":
            e,
            "user":
            email,
        })
        log.debug(log_data, exc_info=True)
        groups = []
    except (
            ExpiredSignatureError,
            ImmatureSignatureError,
            InvalidAudienceError,
            InvalidIssuedAtError,
            InvalidIssuerError,
    ) as e:
        # JWT Validation failed, log an error and revoke the ALB auth cookie
        log.debug({
            **log_data,
            "message": (str(e)),
            "error": e,
            "user": email,
        })
        log.debug(log_data, exc_info=True)
        request.clear_cookie("AWSELBAuthSessionCookie-0")
        request.redirect(request.request.uri)

        groups = []

    return {"user": email, "groups": groups}
Esempio n. 4
0
def _validate_token(access_token: str) -> Mapping[str, str]:
    # Decoding Header & Payload from token
    header = jwt.get_unverified_header(access_token)
    payload = jwt.get_unverified_claims(access_token)

    # Verifying Claims
    try:
        verify_iss(payload, _TOKEN_ISSUER)
    except Exception:
        raise HTTPException(HTTPStatus.UNAUTHORIZED, "Wrong 'iss' claim.")

    try:
        verify_aud(payload, _SERVER_AUDIENCE)
    except Exception:
        raise HTTPException(HTTPStatus.UNAUTHORIZED, "Wrong 'aud' claim.")

    try:
        verify_exp(payload)
    except Exception:
        raise HTTPException(HTTPStatus.UNAUTHORIZED, "Wrong 'exp' claim.")

    try:
        verify_iat(payload)
    except Exception:
        raise HTTPException(HTTPStatus.UNAUTHORIZED, "Wrong 'iat' claim.")

    global _JWKS_CACHE

    kid = header['kid']
    if kid not in _JWKS_CACHE:
        # retrieving key if not in cache

        # first metadata document
        try:
            metadata_response = requests.get(_OIDC_METADATA_DOCUMENT)

            # Consider any status other than 2xx an error
            if not metadata_response.status_code // 100 == 2:
                raise Exception(metadata_response.text,
                                metadata_response.status_code)

        except requests.exceptions.RequestException as e:
            # A serious problem happened, like an SSLError or InvalidURL
            raise Exception("Error: {}".format(str(e)))

        # then key store
        jwks_url = metadata_response.json()["jwks_uri"]
        try:
            jwks_response = requests.get(jwks_url)

            # Consider any status other than 2xx an error
            if not jwks_response.status_code // 100 == 2:
                raise Exception(jwks_response.text, jwks_response.status_code)
        except requests.exceptions.RequestException as e:
            # A serious problem happened, like an SSLError or InvalidURL
            raise Exception("Error: {}".format(str(e)))

        # search for key by given 'kid' header
        jwks = list(
            filter(lambda x: x['kid'] == kid,
                   jwks_response.json()['keys']))

        if not len(jwks):
            raise HTTPException(
                HTTPStatus.UNAUTHORIZED,
                "Error: Could not find jwk for kid: {}".format(kid))

        # put that in the cache
        _JWKS_CACHE[kid] = jwks[0]

    jwks_key = _JWKS_CACHE[kid]
    key = jwk.construct(jwks_key, header['alg'])
    message, encoded_sig = access_token.rsplit('.', 1)
    decoded_sig = base64url_decode(encoded_sig.encode('utf-8'))

    valid = key.verify(message.encode(), decoded_sig)

    # If the token is valid, it returns the payload
    if valid:
        return payload
    else:
        raise HTTPException(HTTPStatus.UNAUTHORIZED,
                            "Token signature invalid.")