Help Debugging Role, Not sure why Insufficent Permissions

I am getting 403 permission denied “Insufficient privileges to perform the action.” when I try and edit a student. I would really appreciate some help, as I am struggling to see where I went wrong. This should be fairly straight forward.

Here is the role that I setup.

CreateRole({
  name: "edit_students",
  membership: {
    resource: Collection("users"),
    predicate:
      Query(
        Lambda(
          "ref",
          Equals(
            Select(["data","grants","edit_students"], Get(Var("ref")), false),
            true
          )
        )
      )
  },
  privileges: [
    {
      resource: Collection("students"),
      actions: {
        write: Query(
          Lambda("ref",
            Equals(
              Select(['data', 'orgsRef'], Get(CurrentIdentity())),
              Select(['data', 'orgsRef'], Get(Var("ref")))
            )
          )
        ),
      }
    },
  ]
})

The general idea is that a user with data.grants.edit_students can edit any student. I then use the privileges actions to make sure the student they are editing belongs to the same organization.

Here is a sample user. Notice the data.orgsRef.

{
  "ref": Ref(Collection("users"), "286635854845182468"),
  "ts": 1610458774620000,
  "data": {
    "email": "test@example.com",
    "orgsRef": Ref(Collection("orgs"), "286635854833649156"),
    "grants": {
      "add_students": true,
      "edit_students": true,
      "delete_students": true
    }
  }
}

Here is an example student.

{
  "ref": Ref(Collection("students"), "286635854869299716"),
  "ts": 1610538599846000,
  "data": {
    "profile": {
      "firstName": "John",
      "lastName": "Doe"
    },
    "orgsRef": Ref(Collection("orgs"), "286635854833649156")
  }
}

I can verify that the role created fine by looking at the dashboard. I see that it has write with the code above listed. So it is confusing for me, am I miss understanding what the lambda is receiving as the ref? I understand it would be the ref for the student that the action is happening on?

It’s hard to tell what the problem is without knowing the broader context. Is this going through graphql? Is it going through a custom UDF resolver?

The first thing to do is to debug the role by setting it up like this:

CreateRole({
  name: "edit_students",
  membership: {
    resource: Collection("users"),
    predicate:
      Query(
        Lambda(
          "ref",
          Equals(
            Select(["data","grants","edit_students"], Get(Var("ref")), false),
            true
          )
        )
      )
  },
  privileges: [
    {
      resource: Collection("students"),
      actions: {
        write: true
      }
    },
  ]
})

If that works, then you know that your issue is the predicate, otherwise you are missing some privileges elsewhere.

For instance, if you are using a UDF-backed resolver, you need to add call permissions for that function. And if you are doing any reading of data before editing the students, then you might need to add read permissions for the students collection, or for any indexes the graphql engine needs to use to lookup the students.

I think that is the issue, it’s not more complex. I am not using a UDF. This is a simple update command to the students collections.

But I bet the issue is it does not have access to match against the index. I am assuming I need to add support to match on the index as well?

I need to double check, but I thought the generic users file I had added that. But let me validate.

Do you know if there is a way to query and see all roles applied by fauna. It would be simpler than removing the predicate logic.

So the calling FQL is this -

      q.Update(q.Select("ref", q.Get(q.Match(q.Index("students_by_id"), id))), {
        data: {
          profile: {
            ...data,
          },
        },
      })

And in secruity > roles > user (the one every user get), it has read access to that same index.

Changing the role to this -

CreateRole({
  name: "edit_students",
  membership: {
    resource: Collection("users"),
    predicate:
      Query(
        Lambda(
          "ref",
          Equals(
            Select(["data","grants","edit_students"], Get(Var("ref")), false),
            true
          )
        )
      )
  },
  privileges: [
    {
      resource: Collection("students"),
      actions: {
        write: true
      }
    },
  ]
})

Works. So the issue is in the privileges. The concern I have is that this is the privileges portion that lets a user read a specific student. And it works as expected.

    {
      resource: Collection("students"),
      actions: {
        read: Query(
          Lambda("ref",
            Equals(
              Select(['data', 'orgsRef'], Get(CurrentIdentity())),
              Select(['data', 'orgsRef'], Get(Var("ref")))
            )
          )
        ),
      }
    }

So the thing that I am comparing against is the same in both write and read. Is the Lambda getting something different as a the “ref” when it writes vs when it reads?

I solved it. It had two problems. The first is that I was not expecting two values to the lambda, old and new data. And I was not expecting the value to be the data, and instead of a ref to it. So I was using Get() around the var access.

CreateRole({
  name: "edit_students",
  membership: {
    resource: Collection("users"),
    predicate:
      Query(
        Lambda(
          "ref",
          Equals(
            Select(["data","grants","edit_students"], Get(Var("ref")), false),
            true
          )
        )
      )
  },
  privileges: [
    {
      resource: Collection("students"),
      actions: {
        write: Query(
          Lambda(["oldData", "newData"],
            Equals(
              Select(['data', 'orgsRef'], Get(CurrentIdentity())),
              Select(['data', 'orgsRef'], Var("oldData"))
            )
          )
        ),
      }
    },
  ]
})