Skip to content

[WIP] Add support for permissions on fields #846

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion graphene/types/field.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import inspect
from graphql.error import GraphQLError
from collections import Mapping, OrderedDict
from functools import partial

Expand Down Expand Up @@ -31,6 +32,7 @@ def __init__(
required=False,
_creation_counter=None,
default_value=None,
permission_classes=[],
**extra_args
):
super(Field, self).__init__(_creation_counter=_creation_counter)
Expand Down Expand Up @@ -66,10 +68,39 @@ def __init__(
self.deprecation_reason = deprecation_reason
self.description = description
self.default_value = default_value
self.permission_classes = permission_classes

def get_permissions(self):
"""
Instantiates and returns the list of permissions that this field requires.
"""
return [permission() for permission in self.permission_classes]

def check_permissions(self, info):
for permission in self.get_permissions():
if not permission.has_permission(info, self):
self.permission_denied(
info, message=getattr(permission, 'message', None)
)

def permission_denied(self, info, message=None):
raise GraphQLError(message)

@property
def type(self):
return get_type(self._type)

def get_resolver(self, parent_resolver):
return self.resolver or parent_resolver
_resolver = self.resolver or parent_resolver

if not _resolver:
return None

def resolver(root, info, *args, **kwargs):

# TODO: pass root?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't kwargs be passed as well?, I have cases where i need to check permissions based on input params

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe not it the same place but yes. You often times need the context (request) and all the inputs to validate. When you have inputs that depend on each other you need that. Although you shouldn't need inputs to determine field access, just to validate.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, I think we should pass all the arguments to the permission class, I was a bit lazy with the comment 😅

self.check_permissions(info)

return _resolver(root, info, *args, **kwargs)

return resolver
44 changes: 44 additions & 0 deletions graphene/types/tests/test_field_permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from functools import partial

import pytest
from graphql.error import GraphQLError

from ..argument import Argument
from ..field import Field
from ..scalars import String
from ..structures import NonNull
from .utils import MyLazyType


class MyInstance(object):
value = "value"
value_func = staticmethod(lambda: "value_func")

def value_method(self):
return "value_method"


class AlwaysFalsePermission(object):
def has_permission(self, info, field):
return False

class AlwaysTruePermission(object):
def has_permission(self, info, field):
return True


def test_raises_error():
MyType = object()
field = Field(MyType, source="value", permission_classes=[AlwaysFalsePermission])

with pytest.raises(GraphQLError):

field.get_resolver(None)(MyInstance(), None)

# TODO: test error message

def test_does_not_raise_error():
MyType = object()
field = Field(MyType, source="value", permission_classes=[AlwaysTruePermission])

field.get_resolver(None)(MyInstance(), None)