Esempio n. 1
0
def test_blacklist(app):
    """
    Test blacklisting a ``jti`` directly.
    """
    _, exp = utils.iat_and_exp()
    jti = utils.new_jti()
    blacklist_token(jti, exp)
    assert is_blacklisted(jti)
Esempio n. 2
0
def validate_jwt(encoded_token=None,
                 aud=None,
                 scope={"openid"},
                 require_purpose=True,
                 purpose=None,
                 public_key=None,
                 attempt_refresh=False,
                 issuers=None,
                 pkey_cache=None,
                 **kwargs):
    """
    Validate a JWT and return the claims.

    This wraps the authutils functions to work correctly for fence and
    correctly validate the token. Other functions in fence should call this
    function and not use any functions from authutils.

    Args:
        encoded_token (str): the base64 encoding of the token
        aud (Optional[str]):
            audience as which the app identifies, which the JWT will be
            expected to include in its ``aud`` claim.
            Optional; will default to issuer (config["BASE_URL"]).
            To skip aud validation, pass the following as a kwarg:
              options={"verify_aud": False}
        scope (Optional[Iterable[str]]):
            list of scopes each of which the token must satisfy; defaults
            to ``{'openid'}`` (minimum expected by OpenID provider).
            Explicitly set this to None to skip scope validation.
        purpose (Optional[str]):
            which purpose the token is supposed to be used for (access,
            refresh, or id)
        public_key (Optional[str]): public key to vaidate JWT with

    Return:
        dict: dictionary of claims from the validated JWT

    Raises:
        JWTError:
            if auth header is missing, decoding fails, or the JWT fails to
            satisfy any expectation
    """

    if encoded_token is None:
        try:
            encoded_token = get_jwt_header()
        except Unauthorized as e:
            raise JWTError(e.message)

    assert (isinstance(scope, set) or isinstance(scope, list)
            or scope is None), "scope argument must be set or list or None"

    # Can't set arg default to config[x] in fn def, so doing it this way.
    if aud is None:
        aud = config["BASE_URL"]

    iss = config["BASE_URL"]
    if issuers is None:
        issuers = [iss]
        oidc_iss = (config.get("OPENID_CONNECT",
                               {}).get("fence", {}).get("api_base_url", None))
        if oidc_iss:
            issuers.append(oidc_iss)
    try:
        token_iss = jwt.decode(encoded_token, verify=False).get("iss")
    except jwt.InvalidTokenError as e:
        raise JWTError(e)
    attempt_refresh = attempt_refresh and (token_iss != iss)
    public_key = public_key or authutils.token.keys.get_public_key_for_token(
        encoded_token, attempt_refresh=attempt_refresh, pkey_cache=pkey_cache)

    try:
        claims = authutils.token.validate.validate_jwt(
            encoded_token=encoded_token,
            aud=aud,
            scope=scope,
            purpose=purpose,
            issuers=issuers,
            public_key=public_key,
            attempt_refresh=attempt_refresh,
            **kwargs)
    except authutils.errors.JWTError as e:

        ##### begin refresh token and API key patch block #####
        # TODO: In the next release, remove this if/elif block and take the else block
        # back out of the else.
        # Old refresh tokens and API keys are not compatible with new validation, so to smooth
        # the transition, allow old style refresh tokens/API keys with this patch;
        # remove patch in next tag. Refresh tokens and API keys have default TTL of 30 days.
        from authutils.errors import JWTAudienceError

        unverified_claims = jwt.decode(encoded_token, verify=False)
        if unverified_claims.get("pur") == "refresh" and isinstance(
                e, JWTAudienceError):
            # Check everything else is fine minus the audience
            try:
                claims = authutils.token.validate.validate_jwt(
                    encoded_token=encoded_token,
                    aud="openid",
                    scope=None,
                    purpose="refresh",
                    issuers=issuers,
                    public_key=public_key,
                    attempt_refresh=attempt_refresh,
                    **kwargs)
            except Error as e:
                raise JWTError("Invalid refresh token: {}".format(e))
        elif unverified_claims.get("pur") == "api_key" and isinstance(
                e, JWTAudienceError):
            # Check everything else is fine minus the audience
            try:
                claims = authutils.token.validate.validate_jwt(
                    encoded_token=encoded_token,
                    aud="fence",
                    scope=None,
                    purpose="api_key",
                    issuers=issuers,
                    public_key=public_key,
                    attempt_refresh=attempt_refresh,
                    **kwargs)
            except Error as e:
                raise JWTError("Invalid API key: {}".format(e))
        else:
            ##### end refresh token, API key patch block #####
            msg = "Invalid token : {}".format(str(e))
            unverified_claims = jwt.decode(encoded_token, verify=False)
            if not unverified_claims.get(
                    "scope") or "" in unverified_claims["scope"]:
                msg += "; was OIDC client configured with scopes?"
            raise JWTError(msg)
    if purpose:
        validate_purpose(claims, purpose)
    if require_purpose and "pur" not in claims:
        raise JWTError("token {} missing purpose (`pur`) claim".format(
            claims["jti"]))

    # For refresh tokens and API keys specifically, check that they are not
    # blacklisted.
    if require_purpose and (claims["pur"] == "refresh"
                            or claims["pur"] == "api_key"):
        if is_blacklisted(claims["jti"]):
            raise JWTError("token is blacklisted")

    return claims
Esempio n. 3
0
def validate_jwt(
    encoded_token=None,
    aud=None,
    purpose=None,
    public_key=None,
    attempt_refresh=False,
    **kwargs
):
    """
    Validate a JWT and return the claims.

    This wraps the authutils functions to work correctly for fence and
    correctly validate the token. Other functions in fence should call this
    function and not use any functions from authutils.

    Args:
        encoded_token (str): the base64 encoding of the token
        aud (Optional[Iterable[str]]):
            list of audiences that the token must satisfy; defaults to
            ``{'openid'}`` (minimum expected by OpenID provider)
        purpose (Optional[str]):
            which purpose the token is supposed to be used for (access,
            refresh, or id)
        public_key (Optional[str]): public key to vaidate JWT with

    Return:
        dict: dictionary of claims from the validated JWT

    Raises:
        JWTError:
            if auth header is missing, decoding fails, or the JWT fails to
            satisfy any expectation
    """
    if encoded_token is None:
        try:
            encoded_token = flask.request.headers["Authorization"].split(" ")[1]
        except IndexError:
            raise JWTError("could not parse authorization header")
        except KeyError:
            raise JWTError("no authorization header provided")
    aud = aud or {"openid"}
    aud = set(aud)
    iss = flask.current_app.config["BASE_URL"]
    issuers = [iss]
    oidc_iss = flask.current_app.config.get("OIDC_ISSUER")
    if oidc_iss:
        issuers.append(oidc_iss)
    try:
        token_iss = jwt.decode(encoded_token, verify=False).get("iss")
    except jwt.InvalidTokenError as e:
        raise JWTError(e.message)
    attempt_refresh = attempt_refresh and (token_iss != iss)
    public_key = authutils.token.keys.get_public_key_for_token(
        encoded_token, attempt_refresh=attempt_refresh
    )
    try:
        claims = authutils.token.validate.validate_jwt(
            encoded_token=encoded_token,
            aud=aud,
            purpose=purpose,
            issuers=issuers,
            public_key=public_key,
            attempt_refresh=attempt_refresh,
            **kwargs
        )
    except authutils.errors.JWTError as e:
        msg = "Invalid token : {}".format(str(e))
        unverified_claims = jwt.decode(encoded_token, verify=False)
        if "" in unverified_claims["aud"]:
            msg += "; was OIDC client configured with scopes?"
        raise JWTError(msg)
    if purpose:
        validate_purpose(claims, purpose)
    if "pur" not in claims:
        raise JWTError("token {} missing purpose (`pur`) claim".format(claims["jti"]))

    # For refresh tokens and API keys specifically, check that they are not
    # blacklisted.
    if claims["pur"] == "refresh" or claims["pur"] == "api_key":
        if is_blacklisted(claims["jti"]):
            raise JWTError("token is blacklisted")

    return claims
Esempio n. 4
0
def test_jti_not_blacklisted(app):
    """
    Test checking a ``jti`` which has not been blacklisted.
    """
    assert not is_blacklisted(utils.new_jti())