Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permissions on Types in Lists #126

Closed
ryan-cat opened this issue Aug 21, 2018 · 4 comments
Closed

Permissions on Types in Lists #126

ryan-cat opened this issue Aug 21, 2018 · 4 comments

Comments

@ryan-cat
Copy link

I have the following schema and permission set up as an example where isOwner means that only the author of the post can view the post:

type User {
...
posts: [Post!]!
}

type Post {
...
author: User!
}

type Query {
  users: [User!]!
}

shield(
  {
    Post: isOwner
  }
)

What is the expected behavior with the isOwner rule placed on the User type itself and then you perform that users list query. Should each denied post be set to null? Should they be removed altogether? How does this affect pagination? Or is this just a bad way to implement this type of logic with shield? Just confused about the expected behavior of using rules on the types themselves with list queries.

@ryan-cat
Copy link
Author

@maticzav Any thoughts on this?

@maticzav
Copy link
Owner

Hey @ryan-cat 👋!

Thanks for being so patient with my response. I just got back from a vacation and must have missed your issue. I love the problem that you have. I'll explain a brief idea below, and probably introduce a more detailed in a blog post with #119 as this seems to be a common question.

My take on this fascinating concept is the following; GraphQL Schema serves as a contract between the client and the server. I have already posted something similar in one of the threads, nevermind I haven't found the thread; so, GraphQL Schema serves as a contract between client and server.

Let's start with a simple example;

type Query {
  user(id: ID!): User!
}

Can you spot an inconsistency in the schema above? Imagine you were to generate the id randomly. Can you imagine "infinite" possibilities of all the ids that might exist? How can we be certain that we have a User with every particular id in our database? GraphQL Schema is the contract between the client and the server. What I mean with this is that every non-nullable field has to be non-nullable and every nullable field can be nullable. If we are to break this, GraphQL will throw an error.

Let's fix the schema;

type Query {
  user(id: ID!): User
}

Since we don't have a User for every "existing" id we have to tell our client it cannot expect us to return a User for sure.


GraphQL Shield makes sure you have access permissions for a particular field. In case we don't have access it throws an error, or, as schema reflects this, returns null. Let's take a look at another example;

type Query {
  user(id: ID!): User
}

type User {
  id: ID!
  name: String!
  email: String!
}

and our imaginary database;

const users = [{
  id: "longid",
  name: "ryan-cat",
  email: "thisisprivate"
},{
  id: "evenlongerid",
  name: "matic",
  email: "verysecret"
},{
  id: "thelongestid",
  name: "johannes",
  email: null
}]

Our schema promises that every user should have an email. What this means is that we promise, every instance in our database has an email field. We cannot return null because we would break the contract. GraphQL brilliantly resolves this by making a user with lacking information a "non-user". If we were to query the third user instance the server response would look somewhat similar to this;

{
  "user": null
}

I kind of drifted now as I know I wanted to mention something else but forgot about it. I hope this gives you a good idea of what's going on behind the scenes and you'll be able to draw some parallels with your case. Let me know if you need more help. 🙂

@ryan-cat
Copy link
Author

@maticzav,

Thanks for the response and no worries about being away!

So what you have explained above I have seen you talk about before and I totally understand that concept. You have to make a field nullable, so that if permission is denied to that field, GraphQL can return null instead of an error that blows up the whole query. However, this really did not answer my question. My question had to do with lists queries/list relations.

In my above example, if I have a permission on the post type, how do I model that relation in the schema with the user type. Is it [Post!]!, or [Post]!, or [Post!], or [Post]? Or is it neither of those? Then, should I expect the posts that are denied permission to be returned as null or removed from the resulting list all together? How does this all affect pagination?

Overall, I am just confused on how lists permissions should be working when the permission involves the data as well as the logged in user on a per-object basis such as isOwner compared to a more static permission that does not involve the data and only the logged in user such as isAdmin? A permission such as isOwner works no problem on a query that returns a single object (like you explained above) as well as mutations, but I am confused about lists/relational lists.

I appreciate the help greatly!

@maticzav
Copy link
Owner

Haha, I see! Interesting!

I thought that you'd be able to derive that for yourself, luckily you also reminded me about the part I wanted to describe above.

So; first of all, let me mention this again, permissions are not a way of filtering results. In that sense, pagination shouldn't be a question here - it has to do with business logic, and you should tackle it on its own.

Regarding the list-thing; let's put together an imaginary schema real quick,

type Query {
  users: [User]!
}

type User {
  id: ID!
  name: String!
  email: String!
}

Now; it's not about making fields nullable, it's about using non-nullable and nullable fields for a specific purpose. In your case, you need a non-nullable field, as I imagine you want to return null as a User if permission fails. You cannot filter nulls this way though, which might cause problems on the frontend.

If you were to use [User!]!, the result should evaluate to null altogether as one of the items in the list is null and no item should be null, and so on, I imagine you get the idea now.

Let me conclude this real quick with the idea that there's no right or wrong. It's more about the tradeoffs. In your particular case 3 out of 4 might seem "wrong" because your app doesn't work as expected, but in reality, they do what they are told to do.

I hope this gives you some leads, let me know otherwise 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants