Esempio n. 1
0
def _create_access_token_cookie(app, session, response, user):
    keypair = app.keypairs[0]
    scopes = config["SESSION_ALLOWED_SCOPES"]

    now = int(time.time())
    expiration = now + config.get("ACCESS_TOKEN_EXPIRES_IN")

    # try to get from current session, if it's not there, we have to hit db
    linked_google_email = session.get("linked_google_email")
    if not linked_google_email:
        linked_google_email = get_linked_google_account_email(user.id)

    access_token = generate_signed_access_token(
        keypair.kid,
        keypair.private_key,
        user,
        config.get("ACCESS_TOKEN_EXPIRES_IN"),
        scopes,
        forced_exp_time=expiration,
        linked_google_email=linked_google_email,
    ).token

    domain = app.session_interface.get_cookie_domain(app)
    response.set_cookie(
        config["ACCESS_TOKEN_COOKIE_NAME"],
        access_token,
        expires=expiration,
        httponly=True,
        domain=domain,
    )

    return response
Esempio n. 2
0
def _get_users_without_access(db, auth_ids, user_emails, check_linking):
    """
    Build list of users without access to projects identified by auth_ids

    Args:
        db (str): database instance
        auth_ids (list(str)): list of project auth_ids to check access against
        user_emails (list(str)): list of emails to check access for
        check_linking (bool): flag to check for linked google email

    Returns:
        dict{str : (list(str))} : dictionary where keys are user emails,
        and values are list of project_ids they do not have access to

    """

    no_access = {}

    for user_email in user_emails:

        user = get_user_by_email(user_email, db) or get_user_by_linked_email(
            user_email, db)

        logger.info("Checking access for {}.".format(user.email))

        if not user:
            logger.info("Email ({}) does not exist in fence database.".format(
                user_email))
            continue

        if check_linking:
            link_email = get_linked_google_account_email(user.id, db)
            if not link_email:
                logger.info(
                    "User ({}) does not have a linked google account.".format(
                        user_email))
                continue

        no_access_auth_ids = []
        for auth_id in auth_ids:
            project = get_project_from_auth_id(auth_id, db)
            if project:
                if not user_has_access_to_project(user, project.id, db):
                    logger.info(
                        "User ({}) does NOT have access to project (auth_id: {})"
                        .format(user_email, auth_id))
                    # add to list to send email
                    no_access_auth_ids.append(auth_id)
                else:
                    logger.info(
                        "User ({}) has access to project (auth_id: {})".format(
                            user_email, auth_id))
            else:
                logger.warning(
                    "Project (auth_id: {}) does not exist.".format(auth_id))

        if no_access_auth_ids:
            no_access[user_email] = no_access_auth_ids

    return no_access
Esempio n. 3
0
def _get_optional_userinfo(user, claims):
    info = {}
    for claim in claims:
        if claim == "linked_google_account":
            google_email = get_linked_google_account_email(user.id)
            info["linked_google_account"] = google_email
        if claim == "linked_google_account_exp":
            google_account_exp = get_linked_google_account_exp(user.id)
            info["linked_google_account_exp"] = google_account_exp

    return info
Esempio n. 4
0
    def _link_google_account():
        provided_redirect = flask.request.args.get("redirect")

        if not provided_redirect:
            raise UserError({"error": "No redirect provided."})

        user_id = current_token["sub"]
        google_email = get_users_linked_google_email(user_id)
        proxy_group = get_or_create_proxy_group_id()

        # Set session flag to signify that we're linking and not logging in
        # Save info needed for linking in session since we need to AuthN first
        flask.session["google_link"] = True
        flask.session["user_id"] = user_id
        flask.session["google_proxy_group_id"] = proxy_group
        flask.session["linked_google_email"] = google_email

        if not google_email:
            # save off provided redirect in session and initiate Google AuthN
            flask.session["redirect"] = provided_redirect
            flask.redirect_url = flask.current_app.google_client.get_auth_url()

            # Tell Google to let user select an account
            flask.redirect_url = append_query_params(flask.redirect_url,
                                                     prompt="select_account")
        else:
            # double check that the token isn't stale by hitting db
            linked_email_in_db = get_linked_google_account_email(user_id)

            if linked_email_in_db:
                # skip Google AuthN, already linked, error
                redirect_with_errors = append_query_params(
                    provided_redirect,
                    error="g_acnt_link_error",
                    error_description=
                    "User already has a linked Google account.",
                )
                flask.redirect_url = redirect_with_errors
                _clear_google_link_info_from_session()
            else:
                # TODO can we handle this error?
                redirect_with_errors = append_query_params(
                    provided_redirect,
                    error="g_acnt_link_error",
                    error_description="Stale access token, please refresh.",
                )
                flask.redirect_url = redirect_with_errors
                _clear_google_link_info_from_session()

        return flask.redirect(flask.redirect_url)
Esempio n. 5
0
def test_google_id_token_linked(db_session, encoded_creds_jwt,
                                oauth_test_client):
    """
    Test google email and link expiration are in id_token for a linked account
    """
    user_id = encoded_creds_jwt["user_id"]
    proxy_group_id = encoded_creds_jwt["proxy_group_id"]

    original_expiration = 1000
    google_account = "*****@*****.**"

    # add google account and link
    existing_account = UserGoogleAccount(email=google_account, user_id=user_id)
    db_session.add(existing_account)
    db_session.commit()
    g_account_access = UserGoogleAccountToProxyGroup(
        user_google_account_id=existing_account.id,
        proxy_group_id=proxy_group_id,
        expires=original_expiration,
    )
    db_session.add(g_account_access)
    db_session.commit()

    # get google account info with utility function
    assert get_linked_google_account_email(user_id) == google_account
    assert get_linked_google_account_exp(user_id) == original_expiration

    # get the id token through endpoint
    data = {"confirm": "yes"}
    oauth_test_client.authorize(data=data)
    tokens = oauth_test_client.token()
    id_token = jwt.decode(tokens.id_token, verify=False)

    assert "google" in id_token["context"]["user"]
    assert (id_token["context"]["user"]["google"].get("linked_google_account")
            == google_account)
    assert (id_token["context"]["user"]["google"].get(
        "linked_google_account_exp") == original_expiration)
Esempio n. 6
0
    def _link_google_account():
        provided_redirect = flask.request.args.get("redirect")

        # will raise UserError if invalid
        validate_redirect(provided_redirect)

        if not provided_redirect:
            raise UserError({"error": "No redirect provided."})

        user_id = current_token["sub"]
        google_email = get_users_linked_google_email(user_id)
        proxy_group = get_or_create_proxy_group_id()

        # Set session flag to signify that we're linking and not logging in
        # Save info needed for linking in session since we need to AuthN first
        flask.session["google_link"] = True
        flask.session["user_id"] = user_id
        flask.session["google_proxy_group_id"] = proxy_group
        flask.session["linked_google_email"] = google_email

        if not google_email:
            # save off provided redirect in session and initiate Google AuthN
            flask.session["redirect"] = provided_redirect

            # requested time (in seconds) during which the link will be valid
            requested_expires_in = get_valid_expiration_from_request()
            if requested_expires_in:
                flask.session["google_link_expires_in"] = requested_expires_in

            # if we're mocking Google login, skip to callback
            if config.get("MOCK_GOOGLE_AUTH", False):
                flask.redirect_url = (config["BASE_URL"].strip("/") +
                                      "/link/google/callback?code=abc")
                response = flask.redirect(flask.redirect_url)
                # pass-through the authorization header. The user's username
                # MUST be a Google email for MOCK_GOOGLE_AUTH to actually link that
                # email correctly
                response.headers["Authorization"] = flask.request.headers.get(
                    "Authorization")
                return response

            flask.redirect_url = flask.current_app.google_client.get_auth_url()

            # Tell Google to let user select an account
            flask.redirect_url = append_query_params(flask.redirect_url,
                                                     prompt="select_account")
        else:
            # double check that the token isn't stale by hitting db
            linked_email_in_db = get_linked_google_account_email(user_id)

            if linked_email_in_db:
                # skip Google AuthN, already linked, error
                redirect_with_errors = append_query_params(
                    provided_redirect,
                    error="g_acnt_link_error",
                    error_description=
                    "User already has a linked Google account.",
                )
                flask.redirect_url = redirect_with_errors
                _clear_google_link_info_from_session()
            else:
                # TODO can we handle this error?
                redirect_with_errors = append_query_params(
                    provided_redirect,
                    error="g_acnt_link_error",
                    error_description="Stale access token, please refresh.",
                )
                flask.redirect_url = redirect_with_errors
                _clear_google_link_info_from_session()

        return flask.redirect(flask.redirect_url)
Esempio n. 7
0
def generate_implicit_response(client,
                               grant_type,
                               include_access_token=True,
                               expires_in=None,
                               user=None,
                               scope=None,
                               nonce=None,
                               **kwargs):
    # prevent those bothersome "not bound to session" errors
    if user not in current_session:
        user = current_session.query(User).filter_by(id=user.id).first()

    if not user:
        raise OIDCError("user not authenticated")

    keypair = flask.current_app.keypairs[0]

    linked_google_email = get_linked_google_account_email(user.id)
    linked_google_account_exp = get_linked_google_account_exp(user.id)

    if not isinstance(scope, list):
        scope = scope.split(" ")

    if not "user" in scope:
        scope.append("user")

    # ``expires_in`` is just the token expiration time.
    expires_in = config["ACCESS_TOKEN_EXPIRES_IN"]

    response = {
        "token_type": "Bearer",
        "expires_in": expires_in,
        # "state" handled in authlib
    }

    # don't provide user projects access in access_tokens for implicit flow
    # due to issues with "Location" header size during redirect (and b/c
    # of general deprecation of user access information in tokens)
    if include_access_token:
        access_token = generate_signed_access_token(
            kid=keypair.kid,
            private_key=keypair.private_key,
            user=user,
            expires_in=config["ACCESS_TOKEN_EXPIRES_IN"],
            scopes=scope,
            client_id=client.client_id,
            linked_google_email=linked_google_email,
            include_project_access=False,
        ).token
        response["access_token"] = access_token

    # don't provide user projects access in id_tokens for implicit flow
    # due to issues with "Location" header size during redirect (and b/c
    # of general deprecation of user access information in tokens)
    id_token = generate_signed_id_token(
        kid=keypair.kid,
        private_key=keypair.private_key,
        user=user,
        expires_in=config["ACCESS_TOKEN_EXPIRES_IN"],
        client_id=client.client_id,
        audiences=scope,
        nonce=nonce,
        linked_google_email=linked_google_email,
        linked_google_account_exp=linked_google_account_exp,
        include_project_access=False,
        auth_flow_type=AuthFlowTypes.IMPLICIT,
        access_token=access_token if include_access_token else None,
    ).token
    response["id_token"] = id_token

    return response
Esempio n. 8
0
def generate_token_response(client,
                            grant_type,
                            expires_in=None,
                            user=None,
                            scope=None,
                            include_refresh_token=True,
                            nonce=None,
                            refresh_token=None,
                            refresh_token_claims=None,
                            **kwargs):
    # prevent those bothersome "not bound to session" errors
    if user not in current_session:
        user = current_session.query(User).filter_by(id=user.id).first()

    if not user:
        # Find the ``User`` model.
        # The way to do this depends on the grant type.
        if grant_type == "authorization_code":
            # For authorization code grant, get the code from either the query
            # string or the form data, and use that to look up the user.
            if flask.request.method == "GET":
                code = flask.request.args.get("code")
            else:
                code = flask.request.form.get("code")
            user = (current_session.query(AuthorizationCode).filter_by(
                code=code).first().user)
        if grant_type == "refresh_token":
            # For refresh token, the user ID is the ``sub`` field in the token.
            user = (current_session.query(User).filter_by(
                id=int(refresh_token_claims["sub"])).first())

    keypair = flask.current_app.keypairs[0]

    linked_google_email = get_linked_google_account_email(user.id)
    linked_google_account_exp = get_linked_google_account_exp(user.id)

    if not isinstance(scope, list):
        scope = scope.split(" ")

    access_token = generate_signed_access_token(
        kid=keypair.kid,
        private_key=keypair.private_key,
        user=user,
        expires_in=config["ACCESS_TOKEN_EXPIRES_IN"],
        scopes=scope,
        client_id=client.client_id,
        linked_google_email=linked_google_email,
    ).token
    id_token = generate_signed_id_token(
        kid=keypair.kid,
        private_key=keypair.private_key,
        user=user,
        expires_in=config["ACCESS_TOKEN_EXPIRES_IN"],
        client_id=client.client_id,
        audiences=scope,
        nonce=nonce,
        linked_google_email=linked_google_email,
        linked_google_account_exp=linked_google_account_exp,
        auth_flow_type=AuthFlowTypes.CODE,
        access_token=access_token,
    ).token
    # If ``refresh_token`` was passed (for instance from the refresh
    # grant), use that instead of generating a new one.
    if refresh_token is None:
        refresh_token = generate_signed_refresh_token(
            kid=keypair.kid,
            private_key=keypair.private_key,
            user=user,
            expires_in=config["REFRESH_TOKEN_EXPIRES_IN"],
            scopes=scope,
            client_id=client.client_id,
        ).token
    # ``expires_in`` is just the access token expiration time.
    expires_in = config["ACCESS_TOKEN_EXPIRES_IN"]
    return {
        "token_type": "Bearer",
        "id_token": id_token,
        "access_token": access_token,
        "refresh_token": refresh_token,
        "expires_in": expires_in,
    }
Esempio n. 9
0
def generate_implicit_response(client,
                               grant_type,
                               include_access_token=True,
                               expires_in=None,
                               user=None,
                               scope=None,
                               nonce=None,
                               **kwargs):
    # prevent those bothersome "not bound to session" errors
    if user not in current_session:
        user = current_session.query(User).filter_by(id=user.id).first()

    if not user:
        raise OIDCError("user not authenticated")

    keypair = flask.current_app.keypairs[0]

    linked_google_email = get_linked_google_account_email(user.id)
    linked_google_account_exp = get_linked_google_account_exp(user.id)

    if not isinstance(scope, list):
        scope = scope.split(" ")

    if not "user" in scope:
        scope.append("user")

    id_token = generate_signed_id_token(
        kid=keypair.kid,
        private_key=keypair.private_key,
        user=user,
        expires_in=ACCESS_TOKEN_EXPIRES_IN,
        client_id=client.client_id,
        audiences=scope,
        nonce=nonce,
        linked_google_email=linked_google_email,
        linked_google_account_exp=linked_google_account_exp,
    ).token

    # ``expires_in`` is just the token expiration time.
    expires_in = ACCESS_TOKEN_EXPIRES_IN

    response = {
        "token_type": "Bearer",
        "id_token": id_token,
        "expires_in": expires_in,
        # "state" handled in authlib
    }

    if include_access_token:
        access_token = generate_signed_access_token(
            kid=keypair.kid,
            private_key=keypair.private_key,
            user=user,
            expires_in=ACCESS_TOKEN_EXPIRES_IN,
            scopes=scope,
            client_id=client.client_id,
            linked_google_email=linked_google_email,
        ).token
        response["access_token"] = access_token

    return response