Auth rule that uses values inside database

Hi all, I am trying to write an auth rule that allows the administrators to query all users. However, the different thing to normal is that instead of identifying the user as an administrator by JWT field, I want to identify the user as an administrator by one field under the type user. The structure is shown below:

"user{
      role{
            name
      }
}"

As shown above, I want the user that has “Admin” in the field “name” to be able to access all users in the database. Is it possible for me to do that with @auth directives? Thanks!

No this is not available atm. You can use RBAC but you have to provide the roles in the JWT or you can use ABAC (Attribute Based Access Control). This later only lets you see the data which has matching attributes and with Dgraph can be several nested layers deep.

What you are looking for is a mechanism to tie together ABAC → RBAC. Meaning get the role for this user from an attribute in the database and then use that role to process a RBAC. I looked for this functionality before as well. It would be helpful to immediately revoke access instead of needing to implement JWT revocation algorithms which are not possible in some cases.

IMO: this should be updated into a feature request. I will move the topic if you can add some of the

  • what you wanted to do
  • what you did alternatively
  • why that was not great
  • any external references

And reply below with any additional use cases you can come up with.


I have implemented an outside the box solution where each data point is linked to access control nodes that are linked to groups that are linked to users with access rights. So using then the ABAC I can query if the requesting user has been linked to a group with the correct admin access control that has been linked back to the data point.

You could essentially hack this even simpler by tying all users to a admin node as granting access and then tie that admin node to your admins. Then in essence your doing ABAC and RBAC together in one by just supplying the identity and not the roles.

1 Like

I believe this is only not possible because nested filters are not currently possible, although I wonder if you could do something like this:

queryUser {
  role(filter: { name: { eq: 'admin' } }) {
    users
  }
}

This assumes there is an inverse relationship with Role → Users

I could be wrong, but I think there might be a way to accomplish this now. I wrote an article about this an never actually wrote a working test.

J

I think this is a little different from the question in that this would show all users who have the admin role, and not allow users with the admin role to see all users

1 Like

Yes, sorry I misunderstood / misspoke (mistyped?).

So, theoretically you only need the $USER (username, id, user_id, whatever here) from the JWT.

Trying to think this out logically…

Run this rule:

query: { rule: """
  query ($USER: String!) { 
    queryUser(filter: { user: { eq: $USER } }) {
      id
    }
  }"""
}

IFF this returns an id, would be considered true - would also require nested filters (or filter inverse relationship like I wrote above):

query($USER: String!) {
  queryUser(filter: {
    user: { eq: $USER }
    and: { role: { name: { eq: "admin" } } }
  }) {
    id
  }
}

So this seems like a mess, but I imagine could be simplified in DQL with var.

I think what we are really talking about here is conditional rules based on values from the database, not just JWT. Postgresql RLS (Row Level Security) would be able to do this, so DGraph should too! This would be AMAZING!

The real question is how would be write this logic on the frontend @auth rules?

Perhaps we could come up with a good way to describe this here, then submit a separate feature request?

J

1 Like
type User {
  username: String! @id
  adminGroup: AdminGroup!
  adminOf: [AdminGroup]
  isAdmin: Boolean @depracated(reason:"admins are handled by AdminGroups")
}
type AdminGroup {
  id: ID!
  slug: String! @id #useful for reference without uid
  overUsers: [User] @hasInverse(field:"adminGroup")
  admins: [User] @hasInverse(field:"adminOf")
}
  • Every user must be in an adminGroup
  • Every AdminGroup should have an admin
  • An AdminGroup can have multiple admins

On the User add the auth query rule:

@auth(query: {
  or: [
    # query own user
    { rule: "query ($USERNAME: String!) { queryUser(filter: { username: { eq: $USERNAME } }) { username } }" },
    # admin can see all users they are over
    { rule: "query ($USERNAME: String!) { queryUser { adminGroup { admins(filter: { username: { eq: $USERNAME } }) { username } } } } " }
  ]
})

you could add another rule to let users see who their admins are if that is wanted/needed


But I think the OP aligns with this request:

Without this, implementing this across many types would entail a lot of boilerplate not to mention needing to always links nodes to an adminGroup. It would also be a faster query if we could just run an adhoc query and evaluate it as a rule.

Right now, rule holds both RBAC and ABAC GraphQL query. ABAC is ran for all data trying to be accessed on a node based level and uses cascade under the hood.

It would be better if, rule was split into rbac and abac and another was added for eval and in eval, it did something sort of like a query with test condition.

E.G. run a query using USERNAME and expect result to contain data.queryUser.username. I think a GraphQL testing library might be a good reference for exact syntax of an eval rule.

1 Like

Love the workaround! Of course, until the update-after is implemented, not sure if that is secure either.

@amaster507 I think you should redo the feature request with examples using the template. When I first read it, I had to re-read it a few times to understand what you were talking about, but obviously I agree with the premise.

While the Dgraph Team wants to look at it from a problem perspective, and not a solution perspective, I suspect if there was a solution that was simple, clearly thought-out, and matches Dgraph’s way of doing things, they may actually add to the list of important features to implement (attached to the problem feature request of course).

Another Idea?

My theory is that conditional rules would fix this.

The specific problem: Allow RBAC and ABAC in the @auth rules like SQL so a user doesn’t have to rely on JWT’s limitations

The broad problem: Non type related @auth rules

Currently @auth rules work by:

  1. Filtering out what a user can’t see based on data from the JWT
  2. Checking for specific values in the JWT using eq modifier (don’t believe in works either at this point?)

What is needed?

  1. Conditional Rules

Ex:

@auth(
  query: {
    if: [ 
      { value: "query($USER: String!) { queryUser(filter: { user: { eq: $USER } and: { role: { name: { eq: \"admin\" } } } }) { id } }"
    ],
    then: [
      { rule: "query ($USER: String!) { queryUser(filter: { user: { eq: $USER } }) { id } }"
    ]
  }
) 

The value only evaluates boolean based on whether or not there is a return value, or a return type / array of types. You could have several if values just like you can have several rules now.

Of course, another option is to allow variables in Graphql (although against the standard from my understanding) that could allow these types of evalulations…

Another Thought… From DQL…

{
  v as var(func: eq(user@en, $USER)) @filter(eq(name@en, "admin")) {
    id
  }
  query @if (gt(len(v), 0) {
    var(func: eq(user@en, $USER)) {
      id
    }
  }
}

Pardon my novelty in DQL, not even sure if you can do conditional queries…, but this is for prototyping anyway…

To a rule like this in GraphQL (probably not possible by graphql standards, but trying to think outside the box):

@auth(query: {
  rule: """
    isAdmin: query($USER: String!) { 
      queryUser(filter: { 
        user: { eq: $USER } 
        and: { role: { name: { eq: \"admin\" } } } })
      {
        id 
      }
    },
    if(filter: { isAdmin: { ge: 0 }) {
      query ($USER: String!) {
        queryUser(filter: { user: { eq: $USER } }) {
          id
         }
       }
    }
  """
})

So, just spitballing some stuff here, I realized when I typed that out, what was in my head makes no sense.

Either way, someone should create a new Feature Request stating the problem, and link to this post.

J

1 Like