diff --git a/graphene_django/fields.py b/graphene_django/fields.py index 1ecce45..7c3cdfa 100644 --- a/graphene_django/fields.py +++ b/graphene_django/fields.py @@ -1,5 +1,6 @@ from functools import partial +from django.core.exceptions import PermissionDenied from django.db.models.query import QuerySet from promise import Promise @@ -9,7 +10,7 @@ from graphene.relay import ConnectionField, PageInfo from graphql_relay.connection.arrayconnection import connection_from_list_slice from .settings import graphene_settings -from .utils import maybe_queryset +from .utils import maybe_queryset, has_permissions, resolve_bound_resolver class DjangoListField(Field): @@ -151,3 +152,37 @@ class DjangoConnectionField(ConnectionField): self.max_limit, self.enforce_first_or_last, ) + + +class DjangoPermissionField(Field): + """Class to manage permission for fields""" + + def __init__(self, type, permissions, *args, **kwargs): + """Get permissions to access a field""" + super(DjangoPermissionField, self).__init__(type, *args, **kwargs) + self.permissions = permissions + + def permission_resolver(self, parent_resolver, raise_exception, root, info, **args): + """ + Middleware resolver to check viewer's permissions + :param parent_resolver: Field resolver + :param raise_exception: If True a PermissionDenied is raised + :param root: Schema root + :param info: Schema info + :param args: Schema args + :return: Resolved field. None if the viewer does not have permission to access the field. + """ + # Get viewer from context + user = info.context.user + if has_permissions(user, self.permissions): + if parent_resolver: + # A resolver is provided in the class + return resolve_bound_resolver(parent_resolver, root, info, **args) + # Get default resolver + elif raise_exception: + raise PermissionDenied() + return None + + def get_resolver(self, parent_resolver): + """Intercept resolver to analyse permissions""" + return partial(self.permission_resolver, parent_resolver, True) diff --git a/graphene_django/utils.py b/graphene_django/utils.py index 560f604..5adf427 100644 --- a/graphene_django/utils.py +++ b/graphene_django/utils.py @@ -5,6 +5,8 @@ from django.db.models.manager import Manager # from graphene.utils import LazyList +from graphene.types.resolver import get_default_resolver +from graphene.utils.get_unbound_function import get_unbound_function class LazyList(object): @@ -81,3 +83,28 @@ def import_single_dispatch(): ) return singledispatch + + +def has_permissions(viewer, permissions): + """ + Verify that at least one permission is accomplished + :param viewer: Field's viewer + :param permissions: Field permissions + :return: True if viewer has permission. False otherwise. + """ + if not permissions: + return True + return any([viewer.has_perm(perm) for perm in permissions]) + + +def resolve_bound_resolver(resolver, root, info, **args): + """ + Resolve provided resolver + :param resolver: Explicit field resolver + :param root: Schema root + :param info: Schema info + :param args: Schema args + :return: Resolved field + """ + resolver = get_unbound_function(resolver) + return resolver(root, info, **args)