Esempio n. 1
0
 def wrapper(*f_args, **f_kwargs):
     if not hasattr(flask.current_app, "arborist"):
         raise Forbidden(
             "this fence instance is not configured with arborist;"
             " this endpoint is unavailable")
     if not flask.current_app.arborist.auth_request(
             jwt=get_jwt_header(),
             service="fence",
             methods=method,
             resources=resource,
     ):
         raise Forbidden(
             "user does not have privileges to access this endpoint")
     return f(*f_args, **f_kwargs)
Esempio n. 2
0
        def wrapper(*f_args, **f_kwargs):
            if not hasattr(flask.current_app, "arborist"):
                raise Forbidden(
                    "this fence instance is not configured for role-based access"
                    " control; this endpoint is unavailable")

            jwt = get_jwt_header()
            data = {
                "user": {
                    "token": jwt
                },
                "request": {
                    "resource": resource,
                    "action": {
                        "service": "fence",
                        "method": method
                    },
                },
            }
            if not flask.current_app.arborist.auth_request(data=data):
                raise Forbidden(
                    "user does not have privileges to access this endpoint")
            return f(*f_args, **f_kwargs)
Esempio n. 3
0
def get_user_info(current_session, username):
    user = get_user(current_session, username)
    if user.is_admin:
        role = "admin"
    else:
        role = "user"

    groups = udm.get_user_groups(current_session, username)["groups"]
    info = {
        "user_id": user.id,  # TODO deprecated, use 'sub'
        "sub": str(user.id),
        # getattr b/c the identity_provider sqlalchemy relationship could not exists (be None)
        "idp": getattr(user.identity_provider, "name", ""),
        "username": user.username,  # TODO deprecated, use 'name'
        "name": user.username,
        "display_name":
        user.display_name,  # TODO deprecated, use 'preferred_username'
        "preferred_username": user.display_name,
        "phone_number": user.phone_number,
        "email": user.email,
        "is_admin": user.is_admin,
        "role": role,
        "project_access": dict(user.project_access),
        "certificates_uploaded": [],
        "resources_granted": [],
        "groups": groups,
        "message": "",
    }

    if "fence_idp" in flask.session:
        info["fence_idp"] = flask.session["fence_idp"]
    if "shib_idp" in flask.session:
        info["shib_idp"] = flask.session["shib_idp"]

    # User SAs are stored in db with client_id = None
    primary_service_account = get_service_account(client_id=None,
                                                  user_id=user.id) or {}
    primary_service_account_email = getattr(primary_service_account, "email",
                                            None)
    info["primary_google_service_account"] = primary_service_account_email

    if hasattr(flask.current_app, "arborist"):
        try:
            resources = flask.current_app.arborist.list_resources_for_user(
                user.username)
            auth_mapping = flask.current_app.arborist.auth_mapping(
                user.username)
        except ArboristError:
            logger.error(
                "request to arborist for user's resources failed; going to list empty"
            )
            resources = []
            auth_mapping = {}
        info["resources"] = resources
        info["authz"] = auth_mapping

    if user.tags is not None and len(user.tags) > 0:
        info["tags"] = {tag.key: tag.value for tag in user.tags}

    if user.application:
        info["resources_granted"] = user.application.resources_granted
        info["certificates_uploaded"] = [
            c.name for c in user.application.certificates_uploaded
        ]
        info["message"] = user.application.message

    if flask.request.get_json(force=True, silent=True):
        requested_userinfo_claims = (flask.request.get_json(force=True).get(
            "claims", {}).get("userinfo", {}))
        optional_info = _get_optional_userinfo(user, requested_userinfo_claims)
        info.update(optional_info)

    # Include ga4gh passport visas if access token has ga4gh_passport_v1 in scope claim
    try:
        encoded_access_token = flask.g.access_token or get_jwt_header()
    except Unauthorized:
        # This only happens if a session token was present (since login_required did not throw an error)
        # but for some reason there was no access token in flask.g.access_token.
        # (Perhaps it was manually deleted by the user.)
        # In particular, a curl request made with no tokens shouldn't get here (bc of login_required).
        # So the request is probably from a browser.
        logger.warning(
            "Session token present but no access token found. "
            "Unable to check scopes in userinfo; some claims may not be included in response."
        )
        encoded_access_token = None

    if encoded_access_token:
        at_scopes = jwt.decode(encoded_access_token,
                               verify=False).get("scope", "")
        if "ga4gh_passport_v1" in at_scopes:
            info["ga4gh_passport_v1"] = []

    return info
Esempio n. 4
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. 5
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 = get_jwt_header()
        except Unauthorized as e:
            raise JWTError(e.message)

    aud = aud or {"openid"}
    aud = set(aud)
    iss = config["BASE_URL"]
    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.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