mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-04-14 06:04:23 +03:00
Agregar permisos por Type (#29)
* Adding permission_to_all_fields to DjangoObjectType meta data * Adding permission_to_all_fields to DjangoObjectType meta data * Adding permission_to_all_fields to DjangoObjectType meta data * change raise_exception * Adding custom PermissionDenied * Adding custom PermissionDenied * Adding custom PermissionDenied * adding prints * adding prints * Removing prints * Adding prints * Adding prints * Change permissions * Fixing pylint * Change __get_field_permissions__ method * Revert __set_as_nullable__ method * Change permissions * Fixing resolvers * Merge django fields with meta fields * In get_resolver take parent_resolver when it have added permissions * In get_resolver take parent_resolver when it have added permissions * Change user_logged param for user * Merging * Fixing tests * adding prints * adding prints * revert * revert * Testing * Testing * Fixing tests * Fixing tests * Testing * Fixing bugs * Fixing pylint * Adding resolve_fields from interfaces * Adding resolve_fields from interfaces Co-authored-by: ariel1899 <radke1899@gmail.com>
This commit is contained in:
parent
ae663474e0
commit
133a597f6a
|
@ -219,7 +219,10 @@ class DjangoField(Field):
|
|||
|
||||
def get_resolver(self, parent_resolver):
|
||||
"""Intercept resolver to analyse permissions"""
|
||||
parent_resolver = super(DjangoField, self).get_resolver(parent_resolver)
|
||||
|
||||
if getattr(parent_resolver, "func", None) != auth_resolver:
|
||||
parent_resolver = super(DjangoField, self).get_resolver(parent_resolver)
|
||||
|
||||
if self.permissions:
|
||||
return partial(
|
||||
get_unbound_function(self.permissions_resolver),
|
||||
|
|
|
@ -5,7 +5,7 @@ import pytest
|
|||
from graphene import List, NonNull, ObjectType, Schema, String
|
||||
from mock import mock
|
||||
from unittest import TestCase
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from graphene_django.utils.utils import PermissionDenied
|
||||
from graphene_django.fields import DjangoField, DataLoaderField
|
||||
from promise.dataloader import DataLoader
|
||||
from promise import Promise
|
||||
|
|
|
@ -3,11 +3,13 @@ from textwrap import dedent
|
|||
|
||||
import pytest
|
||||
from django.db import models
|
||||
from django.test import TestCase
|
||||
from mock import patch
|
||||
|
||||
from graphene import Interface, ObjectType, Schema, Connection, String, Field
|
||||
from graphene.relay import Node
|
||||
|
||||
from graphene_django.utils.utils import PermissionDenied
|
||||
from .. import registry
|
||||
from ..settings import graphene_settings
|
||||
from ..types import DjangoObjectType, DjangoObjectTypeOptions
|
||||
|
@ -581,7 +583,9 @@ def extra_field_resolver(root, info, **kwargs):
|
|||
class PermissionArticle(DjangoObjectType):
|
||||
"""Basic Type to test"""
|
||||
|
||||
class Meta(object):
|
||||
extra_field = Field(String, resolver=extra_field_resolver)
|
||||
|
||||
class Meta:
|
||||
"""Meta Class"""
|
||||
|
||||
field_to_permission = {
|
||||
|
@ -593,85 +597,81 @@ class PermissionArticle(DjangoObjectType):
|
|||
}
|
||||
model = ArticleModel
|
||||
|
||||
extra_field = Field(String, resolver=extra_field_resolver)
|
||||
|
||||
def resolve_headline(self, info, **kwargs):
|
||||
return "headline"
|
||||
|
||||
|
||||
def test_django_permissions():
|
||||
expected = {
|
||||
"headline": ("content_type.permission1", "content_type.permission3"),
|
||||
"pub_date": ("content_type.permission2",),
|
||||
"reporter": ("content_type.permission3",),
|
||||
"extra_field": ("content_type.permission3",),
|
||||
}
|
||||
assert PermissionArticle.field_permissions == expected
|
||||
class PermissionTypesTests(TestCase):
|
||||
def test_django_permissions(self):
|
||||
expected = {
|
||||
"headline": ("content_type.permission1", "content_type.permission3"),
|
||||
"pub_date": ("content_type.permission2",),
|
||||
"reporter": ("content_type.permission3",),
|
||||
"extra_field": ("content_type.permission3",),
|
||||
}
|
||||
|
||||
self.assertEqual(PermissionArticle.field_permissions, expected)
|
||||
|
||||
def test_permission_resolver():
|
||||
MyType = object()
|
||||
def test_permission_resolver(self):
|
||||
MyType = object()
|
||||
|
||||
class Viewer(object):
|
||||
def has_perm(self, perm):
|
||||
return perm == "content_type.permission3"
|
||||
class Viewer(object):
|
||||
def has_perm(self, perm):
|
||||
return perm == "content_type.permission3"
|
||||
|
||||
class Info(object):
|
||||
class Context(object):
|
||||
user = Viewer()
|
||||
class Info(object):
|
||||
class Context(object):
|
||||
user = Viewer()
|
||||
|
||||
context = Context()
|
||||
context = Context()
|
||||
|
||||
resolved = PermissionArticle.resolve_headline(MyType, Info())
|
||||
assert resolved == "headline"
|
||||
resolved = PermissionArticle.resolve_headline(MyType, Info())
|
||||
self.assertEqual(resolved, "headline")
|
||||
|
||||
def test_resolver_without_permission(self):
|
||||
MyType = object()
|
||||
|
||||
def test_resolver_without_permission():
|
||||
MyType = object()
|
||||
class Viewer(object):
|
||||
def has_perm(self, perm):
|
||||
return False
|
||||
|
||||
class Viewer(object):
|
||||
def has_perm(self, perm):
|
||||
return False
|
||||
class Info(object):
|
||||
class Context(object):
|
||||
user = Viewer()
|
||||
|
||||
class Info(object):
|
||||
class Context(object):
|
||||
user = Viewer()
|
||||
context = Context()
|
||||
|
||||
context = Context()
|
||||
with self.assertRaises(PermissionDenied):
|
||||
PermissionArticle.resolve_headline(MyType, Info())
|
||||
|
||||
resolved = PermissionArticle.resolve_headline(MyType, Info())
|
||||
assert resolved is None
|
||||
def test_permission_resolver_to_field(self):
|
||||
MyType = object()
|
||||
|
||||
class Viewer(object):
|
||||
def has_perm(self, perm):
|
||||
return perm == "content_type.permission3"
|
||||
|
||||
def test_permission_resolver_to_field():
|
||||
MyType = object()
|
||||
class Info(object):
|
||||
class Context(object):
|
||||
user = Viewer()
|
||||
|
||||
class Viewer(object):
|
||||
def has_perm(self, perm):
|
||||
return perm == "content_type.permission3"
|
||||
context = Context()
|
||||
|
||||
class Info(object):
|
||||
class Context(object):
|
||||
user = Viewer()
|
||||
resolved = PermissionArticle.resolve_extra_field(MyType, Info())
|
||||
self.assertEqual(resolved, "extra field")
|
||||
|
||||
context = Context()
|
||||
def test_resolver_to_field_without_permission(self):
|
||||
MyType = object()
|
||||
|
||||
resolved = PermissionArticle.resolve_extra_field(MyType, Info())
|
||||
assert resolved == "extra field"
|
||||
class Viewer(object):
|
||||
def has_perm(self, perm):
|
||||
return perm != "content_type.permission3"
|
||||
|
||||
class Info(object):
|
||||
class Context(object):
|
||||
user = Viewer()
|
||||
|
||||
def test_resolver_to_field_without_permission():
|
||||
MyType = object()
|
||||
context = Context()
|
||||
|
||||
class Viewer(object):
|
||||
def has_perm(self, perm):
|
||||
return perm != "content_type.permission3"
|
||||
|
||||
class Info(object):
|
||||
class Context(object):
|
||||
user = Viewer()
|
||||
|
||||
context = Context()
|
||||
|
||||
resolved = PermissionArticle.resolve_extra_field(MyType, Info())
|
||||
assert resolved is None
|
||||
resolved = PermissionArticle.resolve_extra_field(MyType, Info())
|
||||
self.assertIsNone(resolved)
|
||||
|
|
|
@ -19,7 +19,6 @@ from .registry import Registry, get_global_registry
|
|||
from .settings import graphene_settings
|
||||
from .utils import (
|
||||
DJANGO_FILTER_INSTALLED,
|
||||
camelize,
|
||||
get_model_fields,
|
||||
is_valid_django_model,
|
||||
)
|
||||
|
@ -27,7 +26,6 @@ from .utils import (
|
|||
if six.PY3:
|
||||
from typing import Type
|
||||
|
||||
|
||||
ALL_FIELDS = "__all__"
|
||||
|
||||
|
||||
|
@ -133,15 +131,27 @@ def validate_fields(type_, model, fields, only_fields, exclude_fields):
|
|||
)
|
||||
|
||||
|
||||
def get_auth_resolver(name, permissions, resolver=None):
|
||||
def get_auth_resolver(
|
||||
name, permissions, resolver=None, raise_exception=False, permission_classes=None
|
||||
):
|
||||
"""
|
||||
Get middleware resolver to handle field permissions
|
||||
:param name: Field name
|
||||
:param permissions: List of permissions
|
||||
:param resolver: Field resolver
|
||||
:param raise_exception: If True a PermissionDenied is raised
|
||||
:param permission_classes: Permission for user
|
||||
:return: Middleware resolver to check permissions
|
||||
"""
|
||||
return partial(auth_resolver, resolver, permissions, name, None, False)
|
||||
return partial(
|
||||
auth_resolver,
|
||||
resolver,
|
||||
permissions,
|
||||
name,
|
||||
None,
|
||||
raise_exception,
|
||||
permission_classes=permission_classes,
|
||||
)
|
||||
|
||||
|
||||
class DjangoObjectTypeOptions(ObjectTypeOptions):
|
||||
|
@ -173,6 +183,7 @@ class DjangoObjectType(ObjectType):
|
|||
convert_choices_to_enum=True,
|
||||
field_to_permission=None,
|
||||
permission_to_field=None,
|
||||
permission_to_all_fields=None,
|
||||
_meta=None,
|
||||
**options
|
||||
):
|
||||
|
@ -273,21 +284,26 @@ class DjangoObjectType(ObjectType):
|
|||
_meta.fields = django_fields
|
||||
_meta.connection = connection
|
||||
|
||||
field_permissions = cls.__get_field_permissions__(
|
||||
field_to_permission, permission_to_field
|
||||
)
|
||||
if field_permissions:
|
||||
cls.__set_as_nullable__(field_permissions, model, registry)
|
||||
permission_classes = getattr(cls, "permission_classes", None)
|
||||
|
||||
super(DjangoObjectType, cls).__init_subclass_with_meta__(
|
||||
_meta=_meta, interfaces=interfaces, **options
|
||||
)
|
||||
|
||||
field_permissions, fields_raise_exception = cls.__get_field_permissions__(
|
||||
field_to_permission,
|
||||
permission_to_field,
|
||||
permission_to_all_fields,
|
||||
permission_classes,
|
||||
)
|
||||
|
||||
# Validate fields
|
||||
validate_fields(cls, model, _meta.fields, fields, exclude)
|
||||
|
||||
if field_permissions:
|
||||
cls.__set_permissions_resolvers__(field_permissions)
|
||||
cls.__set_permissions_resolvers__(
|
||||
field_permissions, fields_raise_exception, permission_classes
|
||||
)
|
||||
|
||||
cls.field_permissions = field_permissions
|
||||
|
||||
|
@ -295,18 +311,44 @@ class DjangoObjectType(ObjectType):
|
|||
registry.register(cls)
|
||||
|
||||
@classmethod
|
||||
def __get_field_permissions__(cls, field_to_permission, permission_to_field):
|
||||
def __get_field_permissions__(
|
||||
cls,
|
||||
field_to_permission,
|
||||
permission_to_field,
|
||||
permission_to_all_fields,
|
||||
permission_classes,
|
||||
):
|
||||
"""Combines permissions from meta"""
|
||||
permissions = field_to_permission if field_to_permission else {}
|
||||
if permission_to_field:
|
||||
perm_to_field = cls.__get_permission_to_fields__(permission_to_field)
|
||||
for field, perms in perm_to_field.items():
|
||||
if field in permissions:
|
||||
permissions[field] += perms
|
||||
else:
|
||||
permissions[field] = perms
|
||||
perm_to_field = cls.__get_permission_to_fields__(
|
||||
permission_to_field if permission_to_field else {}
|
||||
)
|
||||
fields_raise_exception = {}
|
||||
|
||||
return permissions
|
||||
for name, field in cls._meta.fields.items():
|
||||
if name == "id":
|
||||
continue
|
||||
|
||||
if permission_classes:
|
||||
permissions[name] = ()
|
||||
|
||||
if name in perm_to_field:
|
||||
if name in permissions:
|
||||
permissions[name] += perm_to_field[name]
|
||||
else:
|
||||
permissions[name] = perm_to_field[name]
|
||||
|
||||
if permission_to_all_fields:
|
||||
permissions[name] = tuple(
|
||||
set(permissions.get(name, ()) + permission_to_all_fields)
|
||||
)
|
||||
|
||||
if name in permissions:
|
||||
fields_raise_exception[name] = hasattr(field, "_type") and isinstance(
|
||||
field._type, NonNull
|
||||
)
|
||||
|
||||
return permissions, fields_raise_exception
|
||||
|
||||
@classmethod
|
||||
def __get_permission_to_fields__(cls, permission_to_field):
|
||||
|
@ -327,19 +369,41 @@ class DjangoObjectType(ObjectType):
|
|||
return permissions
|
||||
|
||||
@classmethod
|
||||
def __set_permissions_resolvers__(cls, permissions):
|
||||
def __set_permissions_resolvers__(
|
||||
cls, permissions, fields_raise_exception, permission_classes
|
||||
):
|
||||
"""Set permission resolvers"""
|
||||
for field_name, field_permissions in permissions.items():
|
||||
raise_exception = fields_raise_exception.get(field_name, False)
|
||||
attr = "resolve_{}".format(field_name)
|
||||
resolver = getattr(
|
||||
cls._meta.fields[field_name], "resolver", None
|
||||
) or getattr(cls, attr, None)
|
||||
|
||||
if not resolver:
|
||||
|
||||
for interface in cls._meta.interfaces:
|
||||
|
||||
resolver = getattr(
|
||||
interface._meta.fields.get(field_name, None), "resolver", None
|
||||
) or getattr(interface, attr, None)
|
||||
|
||||
if resolver:
|
||||
break
|
||||
|
||||
if not hasattr(field_permissions, "__iter__"):
|
||||
field_permissions = tuple(field_permissions)
|
||||
|
||||
setattr(
|
||||
cls, attr, get_auth_resolver(field_name, field_permissions, resolver)
|
||||
cls,
|
||||
attr,
|
||||
get_auth_resolver(
|
||||
field_name,
|
||||
field_permissions,
|
||||
resolver,
|
||||
raise_exception,
|
||||
permission_classes,
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import inspect
|
||||
|
||||
import six
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import models
|
||||
from django.db.models.manager import Manager
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from graphql.error import GraphQLError
|
||||
|
||||
# from graphene.utils import LazyList
|
||||
from graphene.types.resolver import get_default_resolver
|
||||
|
@ -43,6 +44,20 @@ def camelize(data):
|
|||
return data
|
||||
|
||||
|
||||
class PermissionDenied(GraphQLError):
|
||||
"""Exception for permission denied. This exception must be used when a user does not have access to a resource"""
|
||||
|
||||
message = _("Permission denied.")
|
||||
code = "permission-denied"
|
||||
|
||||
def __init__(
|
||||
self, nodes=None, stack=None, source=None, positions=None, locations=None
|
||||
):
|
||||
super(PermissionDenied, self).__init__(
|
||||
self.__class__.message, nodes, stack, source, positions, locations
|
||||
)
|
||||
|
||||
|
||||
def get_reverse_fields(model, local_field_names):
|
||||
for name, attr in model.__dict__.items():
|
||||
# Don't duplicate any local fields
|
||||
|
@ -159,7 +174,17 @@ def auth_resolver(
|
|||
raise PermissionDenied()
|
||||
user = info.context.user
|
||||
|
||||
if has_permissions(user, permissions):
|
||||
permission_classes = args.pop("permission_classes", None)
|
||||
|
||||
if has_permissions(user, permissions) and (
|
||||
not permission_classes
|
||||
or all(
|
||||
(
|
||||
perm.has_permission(user=user, instance=root, **args)
|
||||
for perm in permission_classes
|
||||
)
|
||||
)
|
||||
):
|
||||
if parent_resolver:
|
||||
# A resolver is provided in the class
|
||||
return resolve_bound_resolver(parent_resolver, root, info, **args)
|
||||
|
|
Loading…
Reference in New Issue
Block a user