Predicates for roles returning false

Hi,

I’ve seen Permission denied when predicate set for collection, but works when boolean true which appears to be similar to the issue I’m having. And I admit my issue may be that I have been staring at this too long/have overthought this.

I have a collection of Documents that has an owner attribute which is a User ref, and I want users to only be able to fetch the documents that they own.

I created an index:

CreateIndex(
  {
    name: "documents_by_owner",
    source: Collection('documents'),
    terms: [{ field: ['data', 'owner'] }]
  }
)

And I created a Role to restrict queries on that index so that the authenticated user is only able to query against themselves:

CreateRole({
  name: "all_users",
  membership: {
    resource: Collection("users")
  },
  privileges: [
	  {
	    resource: Index("documents_by_owner"),
	    actions: {
	      read: Query(Lambda("ref", Equals(Var("ref"), Identity())))
	    }
	  }
  ]
})

But querying against it always returns false unless I explicitly set read to true. ie.

Paginate(Match(Index("documents_by_owner"), Identity()))

I have also attempted to explicitly set read: true on both the User and Documents collections, and created an additional “all_documents” and “all_users” index with read: true on that role just in case. Unless I remove the check against Identity() it continues to return an insufficient privileges error.

To test, I have been using the faunadb JS lib, and also generated a token secret for the user and have run the queries in shell.

Have I misunderstood how the role privileges should be set?

Thanks

I have since found https://stackoverflow.com/questions/59738603/abac-permissions-dont-work-the-same-for-read-and-write so it looks like the solution is to:

  1. Set read: true on the index
  2. Create functions to impose the Identity() restrictions with the call privilege set in the role
  3. Call the functions instead of the index directly

Is that correct, or should there be a way to have the Identity check directly on the role?

Putting a check against Identity/owner for read and write on the collection seems to work as expected though.

Thanks

Hey solace,

I completely understand your confusion since you are touching on a specific kind of permission that I have not seen any example for since I work at FaunaDB (writing a lambda on an index). Hence, I have not done this myself either and according to the documentation:

or one or more terms to be matched against the target index.

What you have done seems correct.

I don’t think so

That is a workaround and would work. You basically move the rules to your FQL data logic. That’s perfectly fine if you don’t get it working with roles. I’ll try to run some tests and find out though why the role you wrote is not working.

Oh good. Not insane then. Thank you. Look forward to seeing what you find. :slight_smile:

There is something else going on. I just tested it as it’s described in the docs and it seems to work fine for my use case. In my case the role actions look like this:

   {
      resource: Index('accounts_by_email'),
      actions: { read: Query(Lambda(['email'], Equals(Var('email'), 'normal@test.com'))) }
    }

and since this is an index with an e-mail term I just match whether the email is the same as the e-mail I’m planning to use. I indeed get access for that one e-mail and a permission denied for any other e-mail.

I’m not sure what is going wrong in your approach. It might help to show us an example document and example user.

Sample User document is

{
  "ref": Ref(Collection("users"), "274005401495667208"),
  "ts": 1597667767516000,
  "data": {
    "createdAt": "2020-08-13T04:14:00.569Z"
  }
}

Sample document document

{
  "ref": Ref(Collection("documents"), "274016608442122764"),
  "ts": 1597581451770000,
  "data": {
    "owner": Ref(Collection("users"), "274005401495667208"),
    "name": "Name"
  }
}

The document is created by adding owner: Identity() as part of the data and querying with:

Paginate(Match(Index("documents_by_owner"), Identity()))

Using either the JS client or shell. The same condition in the UDF works fine.

Have you tried your test using Identity instead of email?

No, not yet. I was planning to :smile: but something else came in the way.

1 Like

Hah…

The problem is rather subtle.

Lambda("terms", Equals(Var("terms"), [Identity()]))

will work. But notice how it compared to an array with the Identity() and not the Identity() directly.

Lambda(["term"], Equals(Var("term"), [Identity()]))

will not work (term is now the first argument, not the array)

So make sure to use Lambda("terms" if you want to match the array of terms or Lambda(["term"] if you want to match the single term.
In your case it has to be:

Lambda(["ref"], Equals(Var("ref"), Identity()))

Oh dear. Thanks for that.

Irritatingly, I remember thinking at one point that there might have been something significant about the array of terms in the index definition, and tried to figure out if there was a way to debug the role so I could inspect what it thought "ref" was because it was clearly not null. How embarrassing. :frowning:

Thank you for your time!

You’re welcome, I learned something as well… I should look at those arguments more closely in user questions, they matter! :). That’ll teach me to always want to be consistent. I even write my single var lambda attributes in the array style, hence why I never noticed the difference how they are handled :upside_down_face: :man_facepalming:

Not embarrassing at all, we still lack good feedback for the roles so it’s easy to make such a mistake :confused: