-
Notifications
You must be signed in to change notification settings - Fork 819
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
Authorization for Node queries #385
Comments
Hey @barakcoh I was concerned about this as well. Personally, I am not sure that it actually impacts me as I protect data at the resolver level, since if I have defined it as a relayNode, I have explicitly asked to be able to resolve it through the node resolver. Can you elaborate, or provide an example of when you would want a node resolver to be protected, and not sub fields of said node? Is this mainly a concern of leaking data by being able to guess types / ids and construct node queries to find data? |
Hi, I am interested in this topic. Anyone of you already did some draft about this? In first hand, I had the impression that a model structured based in @annotations is great. What do you think about this? Please, check this another issue about this some area of knowledge - graphql-python/graphene-django#79 Thank you! |
I think adding a |
@ekampf That sounds fantastic, This seems django specific to me, so I think we can probably close this and track in graphql-python/graphene-django#79 |
Might be too high-level though... Maybe its best to add a class method to the model:
and then call that from a middleware... |
This would certainly keep it from getting overly specific. Are you picturing |
yes |
This sounds terrific then. Do you want to start on this, or I can and submit a PR? Either is fine with me 👍 |
Out graphql handler does the authentication and sets |
@nickhudkins I think this is very implementation specific, what would you want such a PR to contain? |
maybe we can make the middleware generic... |
I am thinking that there would be two pieces: 1.) Provide a "UserMiddleware" that provides a hook to simply do whatever you need to get a user. The middleware would place that user on context, and the rest would be ensuring that object resolvers (if |
|
as for the actual logic for the middleware I think there are 2 levels of authorization. Second level of authorization is - given a resolved value, check if user is allowed to get it.
|
(I shared an implementation on graphql-python/graphene-django#79 (comment), but it might make more sense here…) Attached is a version of ACLMiddleware I'm experimenting with. Note that my implementation has:
Those implementation details aren't Django specific, but I think this problem is more general than the Django, and this is where the conversation is happening. As for constraints from previous comments:
class ACLMiddleware:
def resolve(self, next, root, args, context, info):
result = next(root, args, context, info)
graphene_type = getattr(info.return_type, 'graphene_type', None)
if not isinstance(graphene_type, SQLAlchemyObjectTypeMeta):
return result
permission = getattr(graphene_type._meta, 'permission', None)
if not permission:
return result
authenticated_user = context.get('authenticated_user', None)
principals = _User.get_principals(
context['session'],
authenticated_user,
)
return result if permits(result.value, principals, permission) else None |
One consequence of the approach I took above is that edge nodes that a user doesn't have access to will be returned as I.e. {
"data": {
"node": {
"id": "VXNlcjp5QnJWTXd6bVV0SFRVM245MmRuN3M1",
"memberships": {
"totalCount": 2,
"edges": [
{
"node": null
},
{
"node": null
}
]
}
}
}
} |
I was working on something similar, and thought someone could refine/make use of, My use case is that I don't even the queries/mutations to appear, (I'm using directives for field level)
Then
I haven't extensively tested it, however, or tested performance. I have no idea if this is remotely legit, but it looks like schema level decorators to filter out mutations/queries like we can do with fields with directives is nowhere to be found (yet - though some people have been making proposals for it) |
There is continued activity here, but after working heavily with graphene over the last few months, it seems like the right way to do this is to use middleware. If you want permissions on your models ( Or for a more complex authorization architecture, don't define permissions as strings, but add a callable (class, function, or instance – that defines For an example, see this: https://github.com/graphql-python/graphene/blob/master/graphene/tests/issues/test_425.py |
@dfee For permissions on models, that is great :) I hadn't seen that before. What I was referring to was a dynamic generation of the schema to filter the queries / mutations / etc. Would it work the same way? More complex oyvey. Actually, I'm trying to get away from the super complex, which I was gravitating to via neomodel and doing scopes on structuredrels and the like and defining roles as nodes(in the neo4j sense) unto themselves and checking for the rel vs the string. |
@ProjectCheshire I was actually responding to the top and hadn't read all the way down. Anyways, yeah, you could definitely generate a dynamic schema per user. That's a creative idea. But no, that'd be entirely different. You're going to have a bit more code overhead on your part, but it's definitely doable. You'd likely want to cache the Funny enough, the codebase I've been writing tests on to release actually does something very similar to generating dynamic schemas. Take a look at the edit: just checked, and indeed |
@dfee bollocks. I have dynamic schema generation working (I want to run some tests on performance) using the method I outlined. I have a base mutation/query/subscription class I inherit the rest from, so I can actually access them all through Base.subclasses() Given that list, I then filter them (using the primitive scopes method I described with strings, heh - each base has default scopes) and then the filtered list becomes what the schema is built from using type() Currently I have it under the RootValue(GraphqlView) class so when I resolve the get root value, I can generate the schema. I ensure that I have a user since I'm using flask_classful decorators and login_required. At load time, I use a default schema with dummy operations (eg returning a random cat from https://placekitten.com/) What Id love to see is schema level decorators as mentioned in https://github.com/apollographql/graphql-decorators and some other places. There is a long (but really interesting) discussion of filters on introspection / schema at graphql/graphql-js#113 which I think? resolved to facebook sorts saying 'don't do it' Hm, I wonder if caching the schema in the session or flask.g would work (Note: I'm not advanced with web whatnot so pardon my naivete. I mostly work on embedded and found graphql to be pretty amazing for some of the edge/cloud/fog work I'm doing. I'm working on a proto port of using graphql as the api layer inside EdgeXFoundry) |
So.. I think I'm going back to my original what-feels-heavy implementation, but using some concepts from your implementation above with Permits / resources (at least for the models portion. I'm still waffling on schema filtering) Still sketching it out, and I need to verify how neomodel/neo4j will store it. The permit may become simply a structuredrel between a user node and specific instance nodes that are outside their role set of permits (eg, exceptions, or a user cannot edit all nodes of type x but can edit this specific node of type x So far, graphql plays really nicely with neomodel, so I'm pretty confident I can stuff this then into the AuthorizationMiddleware as suggested above. Thanks for the commentary and pointers! |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
@dfee thank you for the suggestion. That helped me a lot. For reference for people that might end up here via search: I was relying on https://github.com/taoufik07/django-graphene-permissions to handle permissions mostly. But their from django_graphene_permissions import PermissionDenied, check_object_permissions
class AuthorizationMiddleware(object):
"""
Make sure to filter out objects where the info.context (e.g. the authenticated user)
does not have the authorization to query.
"""
def resolve(self, next, root, info, **args):
result = next(root, info, **args)
try:
if check_object_permissions(
info.return_type.graphene_type.permission_classes(),
info.context,
result.value,
):
return result
else:
raise PermissionDenied()
except AttributeError:
return result |
Hi
It would be helpful to have a standard way to do authorization in
graphene
, specifically forNode
Relay queries. In non-Node queries it's easy enough to enforce authorization inresolve_XXX
(mentioned in #94) but forNode
we need a hook to inject type-specific and argument-specific checks.We're most using @ekampf 's
graphene-gae
so in theory we could inherit and overrideNdbObjectType.get_node
but that would require doing a similar override in for non-ndb types.Any plans to support authorization directly in
graphene
?The text was updated successfully, but these errors were encountered: