ACL in GraphQL. What are we doing wrong?

Hi there!

We are experimenting with enterprise feature ACL in combination with GraphQL endpoint (Docker image: dgraph/dgraph:v20.11.1).

Our setup is:

Types:

type Product {
    productID: ID!
    name: String @search(by: [term])
    reviews: [Review] @hasInverse(field: about)
}

type Customer {
    username: String! @id @search(by: [hash, regexp])
    reviews: [Review] @hasInverse(field: by)
}

type Review {
    id: ID!
    about: Product!
    by: Customer!
    comment: String @search(by: [fulltext])
    rating: Int @search
}

Mutations:

mutation {
  addProduct(input: [
    { name: "GraphQL on Dgraph"},
    { name: "Dgraph: The GraphQL Database"}
  ]) {
    product {
      productID
      name
    }
  }
  addCustomer(input: [{ username: "Michael"}]) {
    customer {
      username
    }
  }
}

mutation {
  addReview(input: [{
    by: {username: "Michael"}, 
    about: { productID: "0x5"}, 
    comment: "Fantastic, easy to install, worked great.  Best GraphQL server available",
    rating: 10}]) 
  {
    review {
      comment
      rating
      by { username }
      about { name }
    }
  }
}

Create one user Alice:

mutation {
  addUser(input: [{name: "alice", password: "newpassword"}]) {
    user {
      name
    }
  }
}

Create one group dev:

mutation {
  addGroup(input: [{name: "dev"}]) {
    group {
      name
      users {
        name
      }
    }
  }
}

Add Alice to dev group:

mutation {
  updateUser(input: {filter: {name: {eq: "alice"}}, set: {groups: [{name: "dev"}]}}) {
    user {
      name
      groups {
        name
    }
    }
  }
}

Set permisions for dev:

mutation {
  updateGroup(input: {filter: {name: {eq: "dev"}}, set: {rules: [
    {predicate: "Review.comment", permission: 7},
    {predicate: "Review.by", permission: 7},
    {predicate: "Review.about", permission: 7},
    {predicate: "Customer.username", permission: 7},
    {predicate: "Product.productID", permission: 7},
    {predicate: "Product.reviews", permission: 7},
    {predicate: "Review.rating", permission: 7},
]}}) {
    group {
      name
      rules {
        permission
        predicate
      }
    }
  }
}

Alice does not have permision for Product.name predicate.
When we execute GraphQL query like this:

query {
  queryProduct(filter: { name: {anyofterms: "Database"}}) {
    productID
    reviews {
      rating
    }
    name
  }
}

results are:

{
  "data": {
    "queryProduct": [
      {
        "productID": "0x5",
        "reviews": [
          {
            "rating": 10
          }
        ],
        "name": null
      },
      {
        "productID": "0x6",
        "name": null
      }
    ]
  }
}

Alice is not able to search in Product.name. Why we get listing of all Products?
We have try to reproduce same results in DQL and it looks that “query translator” from GraphQL to DQL is not correct. We got same results with query:

  byTypeAndFilter(func: type(Product)) @filter(anyofterms(Product.name,"Database")) {
    uid
    Product.name
    Product.reviews {
      Review.rating
    }
  }
}

but we are expecting results generated from this (Alice has not privilges so no results are returned):

{
  byName(func: anyofterms(Product.name,"Database")) {
    uid
    Product.name
    Product.reviews {
      Review.rating
    }
  }

What are we doing wrong? Can be used ACL in conbination with GraphQL?

Thank you,
Roman

Dgraph’s ACL is predicate-based, not object-based - This you can do with GraphQL Auth level. And I think the permission needs to be explicit. The docs say

If there are no rules for a predicate, the default behavior is to block 
all (READ, WRITE and MODIFY) operations.

I’m not sure what are you mean. Can you give me a example?

In my case, this is not object-base, because Alice can see reviews on Product (and another predicates…), but Alice is not allowed to do anything with name predicate, like you write

If there are no rules for a predicate, the default behavior is to block 
all (READ, WRITE and MODIFY) operations.

In my opinion Alice should be not able to use that predicate for search(filtering) and result should be same like in:

{
  byName(func: anyofterms(Product.name,"Database")) {
    uid
    Product.name
    Product.reviews {
      Review.rating
    }
  }

=> no response data, empty array…

In case of GraphQL query it returns array of all Products without name, but with reviews.
In this case I’m looking only for Products which have Database in the name.
My program does know nothing about Alice privileges so it handles given list of product like it is products about Database.
And it is wrong, accoring to me - not all products in DB are about Database.

I see.

So, this looks like a bug.

Question:
You have tried to reproduce it with DQL, and it worked like intended?

That might be a bug on the GraphQL “parsing”.

@gja @abhimanyusinghgaur @graphql can you confirm?

Looks like the GraphQL query is able to surpass the ACL restrictions when doing a search on a forbidden predicate.

Also ping @dmai.

I’m not sure if I reproduced it correctly, but when I use DQL(logged as Alice):

{
  byName(func: anyofterms(Product.name,"Database")) {
    uid
    Product.name
    Product.reviews {
      Review.rating
    }
  }

it does not return anything - required state.

This is a bug in ACL and query processing. For example, this DQL query also returns products whereas it shouldn’t.

{
  byName(func: type(Product)) @filter(anyofterms(Product.name,"Database")) {
    uid
    Product.name
    Product.reviews {
      Review.rating
    }
  }
  }

We rewrite the query based on the ACL permissions that the user has, seems like that rewriting happens correctly when we use eq(dgraph.type, "Product") but not when we do type(Product). Accepting this as a bug.

2 Likes