HasRole function

In order to properly reflect some state in front-end UX, for example, you can actually edit this thing (show an edit button) it’s necessary to know if the user has a specific role. It can of course be built with a table of “roles” for each user reference, but that seems like a lot of overhead. Fauna know this data, so why not a function “HasRole(Role(…), )” which returns true or false? I could write it as UDF, except, I cannot find a way to actually invoke the predicate Query(), call doesn’t work, nor does trying to create/update a function on the fly.

In addition, this would be a great help from the shell, to help track down permission problems.

I would vote this up if I had more votes because it sounds like a good idea.

For a convenient workaround and to keep you from duplicating logic, you can create a UDF and call that in the role predicate, rather than create a role and reference the predicate in a UDF.

CreateFunction({
  name: "isAdmin",
  body: Query(Lambda("ref", Select(["data", "admin"], Get(Var("ref")))))
})

// ...

CreateRole({
  name: "admin",
  membership: [
    {
      resource: Collection("users"),
      predicate: Query(Lambda(ref => Call("isAdmin", ref)))
    }
  ],
  privileges: [
    {
      resource: Collection("users"),
      actions: { read: true, write: true, create: true, delete: true }
    }
  ]
})

EDIT:

I also just checked and you can be more explicit about reusing the Function's body, which probably saves some compute effort for resolving the role.

predicate: Select('body', Get(Function('isAdmin')))

That’s basically my work around, at the moment, I have functions “role.” and I can call them, and share them. It gets complex.

Also, I can’t find a way, and maybe I just missed it, but is there a way to get the collection a reference belongs to?

Refs have an id and a collection property you can Select.

Let(
  {
    thingRef: Ref(Collection('Thing'), '295711981456851459'),
    thingDoc: Get(Var('thingRef')),
    multiCollectionLink: Select(['data', 'link'], Var('thingDoc')),
  },
  {
    id: Select('id', Var('multiCollectionLink')),
    collection: Select('collection', Var('multiCollectionLink'))
  }
)

// returns, for example
{
  id: 295688826915914242,
  collection: Collection('User')
}
1 Like

Awesome. I couldn’t figure out what the first part was called. I likely just missed it in the documentation.

Sorry about that. I’ve filed an issue to add an example to the Select reference page that demonstrates how to extract a Ref’s collection.

1 Like

That would a good thing, I might be there somewhere and I missed it.

However- I did find a bug when I messing with roles. If you specify a predicate, but no resource for the “membership” it crashes the dashboard (Web) and it certainly doesn’t work, even the through the FQL query accepts it, and will display it. A “GetCurrentRoles()” function would be awesome, and make debugging it so much easier.

I haven’t been able to reproduce the Dashboard problem. Do you have any repro steps?

Paginate(Roles()) fetches all existing roles, up to the default page size (64 entries). Is that what you are referring to?

No, I want the roles fauna things the current identity has, so when something goes wrong in the middle of the query, I can determine why.

I’ll try tonight and see if I can reproduce the problem with the roles crashing the the dashboard.

Thanks for clarifying. It sounds like you have two requests:

  • Create a HasRole function that takes a document reference and returns a boolean that indicates whether a role includes the document reference in its membership.
  • Create another function (ShowRoles ?) that take a document reference and returns a set of roles that has the document reference included in its membership.

Does that sound accurate?

Both would be nice, but given the first, I can author the second. However, I think it’s something like: HasRole(roleRef, documentRef).

If that’s the signature that you want to use, then you can write HasRole yourself. All you’d need to do is compare the document ref’s collection with the membership in the role for roleRef. Typically, membership would specify a collection reference, but it could also be a specific document.

You mentioned in the original post that you were having trouble calling your UDF. Can you show us what you tried?

@ewan It is not possible to execute the membership predicate, if provided.

Query(
  Lambda(
    ['instanceRef', 'membership'],
    If(
      // role.resource === instance.collection
      Equals(
        Select('resource', Var('membership')),
        Select('collection', Var('instanceRef'))
      ),
      // then if
      If(
        //  no predicate
        IsNull(Select('predicate', Var('membership'))),
        // hooray!
        true,
        // else compute predicate.
        // *********************************
        // missing higher order functions!!
        // cannot compute
        //**********************************
        Abort('Cannot compute predicate')
      ),
      // else role.resource !== instance.collection
      false
    )
  )
)

For a workaround, I could imagine creating a Function wrapper around CreateRole which creates Functions whose bodies match the Role memberships’ predicates. Using naming conventions, or stashing a reference to the Function in the Role’s data, you could substitute that Function for the appropriate Role predicate when you want it. That’s a lot of work though…

Aha! Thanks for that, not sure why I missed that nuance earlier.

My first thought would be to try defining the membership predicate as a function that calls a named UDF. Then you could call that UDF whenever you like. I have not tested that idea though.

Yeah, I was unable to figure out to execute the predicate (no way that I could find) so i was kind of stuck, basically, it means we have to manage our own roles in some other way, which seems completely redundant when the engine already knows the answer, and moreover, it knows the answer about multiple and overlapping roles.

My current workaround is a table of user document ref, with an entry for a role name, then I hookup the predicate to use an index to check them. A set of UDFs for managing the collection.

I guess another option would be make a set of collctions “userIsRoleA”, “userIsRoleB” and use the collection to manage them, that seems easy, but not really scalable if there are a lot of roles.

Maybe another implementation would to simply have CurrentIdentity return a list of applicable roles as well as the identity. or as mentioned above, a function that simply allow me to invoke the predicate, might be a more general purpose and useful thing.

Why this matters? So from a database perspective it doesn’t, you can lock everything down just fine, but when you’re working on a full stack application, the front might want to know things like ‘They can’t modify this item, so we shouldn’t give them the option to try’. Sure it’s a best effort on the front end, and the backend will apply the right policies, but it does provide a better user experience.

Sorry for the slow reply, real work called for a bit, as this is a personal spare time project.

Awesome feedback @half-halt.

For what it is worth, consider that the resolution of roles can be lazy process. If you need access to a resource the roles are scanned to check if if any apply. If multiple roles give access to the same action and the same resource, then whichever gets encountered first will allow the transaction to continue, and the other might not ever be evaluated.

To have every query actively compute all role-predicates in advance (e.g. include a list in CurrentIdentity by default) would add additional computation/latency to a lot of common Fauna operations for many that don’t need it.

All that said, I think there’s a clearly value in an HasRole function, as you’ve demonstrated.

@ptpaterson - That’s a great point about the lazy evaluation. For the most part user experiences need to know when they login what the user can do, and the rest can pushed to when the query runs as it is today, I wouldn’t want to change it. For a front end perspective it doesn’t matter if another roles gives them permissions to a resource.

I envisioned using this from a login UDF which returns the secret, the user information and the list of roles, that’s easy to build with HasRole, it’s not hard to build without it, other than you need an internal knowledge of what the predicate is.

As an unrelated question, is there a way to access the parent database from a child?

1 Like

Sounds like the right time for a new topic, haha :wink: The short answer there is no. Consider child databases as encapsulated and separated from their parents from the child’s perspective. You can read a child database from a parent, but not the other way around.

Jokes aside… :slight_smile: if you have a question regarding setups with multiple databases, another topic would be good to continue exploring that. And this topic can stay focused on discussion for the feature request.