Required repetition of Auth rules

Hi guys,

Often we find that permissions to query a given node in the graph is linked back to a common node which is being checked for permissions.

As an example, let’s take this schema:

type Core @auth(query: { rule: """
    query ($API_KEY: String!){
        queryCore(filter: { apiKey: { eq: $API_KEY } }) {
            id
        }
    } 
""" }){
  id: ID!
  apiKey: String! @id
  children: [Middle]
}

type Middle @auth(rule: """
    query ($API_KEY: String!){
        queryMiddle {
            parent(filter: { apiKey: { eq: $API_KEY } }) {
                id
            }
        }
    } 
"""){
    parent: Core! @hasInverse(field: children)
    children: [Lower]
}

type Lower @auth(rule: """
    query ($API_KEY: String!){
        queryLower {
            parent
                parent(filter: { apiKey: { eq: $API_KEY } }) {
                    id
                }
            }
        }
    } 
"""){
    parent: Middle! @hasInverse(field: children)
}

The fact that we have to repeat the same rules for accessing the ‘Core’ node is creating complexities and repeated code, which when you have multiple paths that a request can be authorised for a given node, becomes rather a serious issue.

If the end result in GraphQL needs to be represented this way to process the requests efficiently or in parallel, that’s fine, but having to redefine the same rules over and over again across hundreds of nodes is becoming tedious and really prone to human error.

I have tried changing the schema to the following to try to have Dgraph ‘honour’ the rules of the core node, but all it does is restrict the returning of that core node as a child of a query request for, lets say, the Middle node (i.e. if you request the Middle node with its ‘parent’ Core node, it will stop the Core node from returning, but it won’t stop the Middle node from returning):

type Core @auth(query: { rule: """
    query ($API_KEY: String!){
        queryCore(filter: { apiKey: { eq: $API_KEY } }) {
            id
        }
    } 
""" }){
  id: ID!
  apiKey: String! @id
  children: [Middle]
}

type Middle @auth(rule: """
    query (){
        queryMiddle {
            parent {
                id
            }
        }
    } 
"""){
    parent: Core! @hasInverse(field: children)
    children: [Lower]
}

type Lower @auth(rule: """
    query ($API_KEY: String!){
        queryLower {
            parent
                parent {
                    id
                }
            }
        }
    } 
"""){
    parent: Middle! @hasInverse(field: children)
}

I understand how this can become an optimisation issue as trying to have the @auth rules respect each other may become complex, however, the @auth queries are being validated when trying to update the schema, so presumably, that point in the process could ‘expand’ the given @auth rule to respect the @auth rules of the linked entity as well.

I hope I’m being clear here, but in some way, I’d appreciate a way to not have to repeat myself in the schema, redefining the same rules over and over again; all I should need to do is define rules with variables in the entry point where required, and then in all other places, provided I want the engine to respect the rules of that entity through graph traversal, provide a path for the engine to take to ‘get’ to another entity.

I imagine this could be done by expanding the auth rules of a given type to take into account the rules of linked entities if those paths are provided in the auth rule (e.g., in my above schema, do not return a Middle or Lower node if the request does not also honour the auth rules of the Core node).

Let me know your thoughts; I’m open to dealing with this in any way that avoid repetition of code.

I’ve just had a thought about how this could be explicitly represented by the user as their intention:

type Core @auth(query: { rule: """
    query ($API_KEY: String!){
        queryCore(filter: { apiKey: { eq: $API_KEY } }) {
            id
        }
    } 
""" }){
  id: ID!
  apiKey: String! @id
  children: [Middle]
}

type Middle @auth(rule: """
    query (){
        queryMiddle {
            parent {
                __auth
            }
        }
    } 
"""){
    parent: Core! @hasInverse(field: children)
    children: [Lower]
}

type Lower @auth(rule: """
    query ($API_KEY: String!){
        queryLower {
            parent
                parent {
                    __auth
                }
            }
        }
    } 
"""){
    parent: Middle! @hasInverse(field: children)
}

Using a new keyword/field, __auth would tell the engine that the request would need to honour that entity’s auth rules to be returned.

1 Like

See if this helps give context to the current handling:

@amaster507

It’s an interesting approach, but I’m not clear on how you would use a fragment for a given entity on another entity without still supplying the ‘path’ that should be taken to get back to the entity which has the ‘real’ auth rules to be checked.

Much like you, I am having to repeat the same 10 or so rules over and over again. I was thinking that with the __auth field that I proposed, the schema parser could expand the rules directly into the entity from the referenced entity (obviously taking into account recursive loops and blocking the use of this if that is the case).

Honestly, so long as an approach is thought up and implemented that would allow me to not have to repeat the same rules over and over again, I’m all for it in whatever its form is.

It looks like the ticket you referenced stopped being discussed. Any ideas on whether further thought was put into this? I’m so worried about human error with all of the copying and pasting, I’m considering writing a tool which will do this ‘expansion’ I’m talking about.

1 Like