mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-07-04 12:23:13 +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):
|
def get_resolver(self, parent_resolver):
|
||||||
"""Intercept resolver to analyse permissions"""
|
"""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:
|
if self.permissions:
|
||||||
return partial(
|
return partial(
|
||||||
get_unbound_function(self.permissions_resolver),
|
get_unbound_function(self.permissions_resolver),
|
||||||
|
|
|
@ -5,7 +5,7 @@ import pytest
|
||||||
from graphene import List, NonNull, ObjectType, Schema, String
|
from graphene import List, NonNull, ObjectType, Schema, String
|
||||||
from mock import mock
|
from mock import mock
|
||||||
from unittest import TestCase
|
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 graphene_django.fields import DjangoField, DataLoaderField
|
||||||
from promise.dataloader import DataLoader
|
from promise.dataloader import DataLoader
|
||||||
from promise import Promise
|
from promise import Promise
|
||||||
|
|
|
@ -3,11 +3,13 @@ from textwrap import dedent
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.test import TestCase
|
||||||
from mock import patch
|
from mock import patch
|
||||||
|
|
||||||
from graphene import Interface, ObjectType, Schema, Connection, String, Field
|
from graphene import Interface, ObjectType, Schema, Connection, String, Field
|
||||||
from graphene.relay import Node
|
from graphene.relay import Node
|
||||||
|
|
||||||
|
from graphene_django.utils.utils import PermissionDenied
|
||||||
from .. import registry
|
from .. import registry
|
||||||
from ..settings import graphene_settings
|
from ..settings import graphene_settings
|
||||||
from ..types import DjangoObjectType, DjangoObjectTypeOptions
|
from ..types import DjangoObjectType, DjangoObjectTypeOptions
|
||||||
|
@ -581,7 +583,9 @@ def extra_field_resolver(root, info, **kwargs):
|
||||||
class PermissionArticle(DjangoObjectType):
|
class PermissionArticle(DjangoObjectType):
|
||||||
"""Basic Type to test"""
|
"""Basic Type to test"""
|
||||||
|
|
||||||
class Meta(object):
|
extra_field = Field(String, resolver=extra_field_resolver)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
"""Meta Class"""
|
"""Meta Class"""
|
||||||
|
|
||||||
field_to_permission = {
|
field_to_permission = {
|
||||||
|
@ -593,85 +597,81 @@ class PermissionArticle(DjangoObjectType):
|
||||||
}
|
}
|
||||||
model = ArticleModel
|
model = ArticleModel
|
||||||
|
|
||||||
extra_field = Field(String, resolver=extra_field_resolver)
|
|
||||||
|
|
||||||
def resolve_headline(self, info, **kwargs):
|
def resolve_headline(self, info, **kwargs):
|
||||||
return "headline"
|
return "headline"
|
||||||
|
|
||||||
|
|
||||||
def test_django_permissions():
|
class PermissionTypesTests(TestCase):
|
||||||
expected = {
|
def test_django_permissions(self):
|
||||||
"headline": ("content_type.permission1", "content_type.permission3"),
|
expected = {
|
||||||
"pub_date": ("content_type.permission2",),
|
"headline": ("content_type.permission1", "content_type.permission3"),
|
||||||
"reporter": ("content_type.permission3",),
|
"pub_date": ("content_type.permission2",),
|
||||||
"extra_field": ("content_type.permission3",),
|
"reporter": ("content_type.permission3",),
|
||||||
}
|
"extra_field": ("content_type.permission3",),
|
||||||
assert PermissionArticle.field_permissions == expected
|
}
|
||||||
|
|
||||||
|
self.assertEqual(PermissionArticle.field_permissions, expected)
|
||||||
|
|
||||||
def test_permission_resolver():
|
def test_permission_resolver(self):
|
||||||
MyType = object()
|
MyType = object()
|
||||||
|
|
||||||
class Viewer(object):
|
class Viewer(object):
|
||||||
def has_perm(self, perm):
|
def has_perm(self, perm):
|
||||||
return perm == "content_type.permission3"
|
return perm == "content_type.permission3"
|
||||||
|
|
||||||
class Info(object):
|
class Info(object):
|
||||||
class Context(object):
|
class Context(object):
|
||||||
user = Viewer()
|
user = Viewer()
|
||||||
|
|
||||||
context = Context()
|
context = Context()
|
||||||
|
|
||||||
resolved = PermissionArticle.resolve_headline(MyType, Info())
|
resolved = PermissionArticle.resolve_headline(MyType, Info())
|
||||||
assert resolved == "headline"
|
self.assertEqual(resolved, "headline")
|
||||||
|
|
||||||
|
def test_resolver_without_permission(self):
|
||||||
|
MyType = object()
|
||||||
|
|
||||||
def test_resolver_without_permission():
|
class Viewer(object):
|
||||||
MyType = object()
|
def has_perm(self, perm):
|
||||||
|
return False
|
||||||
|
|
||||||
class Viewer(object):
|
class Info(object):
|
||||||
def has_perm(self, perm):
|
class Context(object):
|
||||||
return False
|
user = Viewer()
|
||||||
|
|
||||||
class Info(object):
|
context = Context()
|
||||||
class Context(object):
|
|
||||||
user = Viewer()
|
|
||||||
|
|
||||||
context = Context()
|
with self.assertRaises(PermissionDenied):
|
||||||
|
PermissionArticle.resolve_headline(MyType, Info())
|
||||||
|
|
||||||
resolved = PermissionArticle.resolve_headline(MyType, Info())
|
def test_permission_resolver_to_field(self):
|
||||||
assert resolved is None
|
MyType = object()
|
||||||
|
|
||||||
|
class Viewer(object):
|
||||||
|
def has_perm(self, perm):
|
||||||
|
return perm == "content_type.permission3"
|
||||||
|
|
||||||
def test_permission_resolver_to_field():
|
class Info(object):
|
||||||
MyType = object()
|
class Context(object):
|
||||||
|
user = Viewer()
|
||||||
|
|
||||||
class Viewer(object):
|
context = Context()
|
||||||
def has_perm(self, perm):
|
|
||||||
return perm == "content_type.permission3"
|
|
||||||
|
|
||||||
class Info(object):
|
resolved = PermissionArticle.resolve_extra_field(MyType, Info())
|
||||||
class Context(object):
|
self.assertEqual(resolved, "extra field")
|
||||||
user = Viewer()
|
|
||||||
|
|
||||||
context = Context()
|
def test_resolver_to_field_without_permission(self):
|
||||||
|
MyType = object()
|
||||||
|
|
||||||
resolved = PermissionArticle.resolve_extra_field(MyType, Info())
|
class Viewer(object):
|
||||||
assert resolved == "extra field"
|
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():
|
context = Context()
|
||||||
MyType = object()
|
|
||||||
|
|
||||||
class Viewer(object):
|
resolved = PermissionArticle.resolve_extra_field(MyType, Info())
|
||||||
def has_perm(self, perm):
|
self.assertIsNone(resolved)
|
||||||
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
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ from .registry import Registry, get_global_registry
|
||||||
from .settings import graphene_settings
|
from .settings import graphene_settings
|
||||||
from .utils import (
|
from .utils import (
|
||||||
DJANGO_FILTER_INSTALLED,
|
DJANGO_FILTER_INSTALLED,
|
||||||
camelize,
|
|
||||||
get_model_fields,
|
get_model_fields,
|
||||||
is_valid_django_model,
|
is_valid_django_model,
|
||||||
)
|
)
|
||||||
|
@ -27,7 +26,6 @@ from .utils import (
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
|
|
||||||
ALL_FIELDS = "__all__"
|
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
|
Get middleware resolver to handle field permissions
|
||||||
:param name: Field name
|
:param name: Field name
|
||||||
:param permissions: List of permissions
|
:param permissions: List of permissions
|
||||||
:param resolver: Field resolver
|
: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: 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):
|
class DjangoObjectTypeOptions(ObjectTypeOptions):
|
||||||
|
@ -173,6 +183,7 @@ class DjangoObjectType(ObjectType):
|
||||||
convert_choices_to_enum=True,
|
convert_choices_to_enum=True,
|
||||||
field_to_permission=None,
|
field_to_permission=None,
|
||||||
permission_to_field=None,
|
permission_to_field=None,
|
||||||
|
permission_to_all_fields=None,
|
||||||
_meta=None,
|
_meta=None,
|
||||||
**options
|
**options
|
||||||
):
|
):
|
||||||
|
@ -273,21 +284,26 @@ class DjangoObjectType(ObjectType):
|
||||||
_meta.fields = django_fields
|
_meta.fields = django_fields
|
||||||
_meta.connection = connection
|
_meta.connection = connection
|
||||||
|
|
||||||
field_permissions = cls.__get_field_permissions__(
|
permission_classes = getattr(cls, "permission_classes", None)
|
||||||
field_to_permission, permission_to_field
|
|
||||||
)
|
|
||||||
if field_permissions:
|
|
||||||
cls.__set_as_nullable__(field_permissions, model, registry)
|
|
||||||
|
|
||||||
super(DjangoObjectType, cls).__init_subclass_with_meta__(
|
super(DjangoObjectType, cls).__init_subclass_with_meta__(
|
||||||
_meta=_meta, interfaces=interfaces, **options
|
_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
|
||||||
validate_fields(cls, model, _meta.fields, fields, exclude)
|
validate_fields(cls, model, _meta.fields, fields, exclude)
|
||||||
|
|
||||||
if field_permissions:
|
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
|
cls.field_permissions = field_permissions
|
||||||
|
|
||||||
|
@ -295,18 +311,44 @@ class DjangoObjectType(ObjectType):
|
||||||
registry.register(cls)
|
registry.register(cls)
|
||||||
|
|
||||||
@classmethod
|
@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"""
|
"""Combines permissions from meta"""
|
||||||
permissions = field_to_permission if field_to_permission else {}
|
permissions = field_to_permission if field_to_permission else {}
|
||||||
if permission_to_field:
|
perm_to_field = cls.__get_permission_to_fields__(
|
||||||
perm_to_field = cls.__get_permission_to_fields__(permission_to_field)
|
permission_to_field if permission_to_field else {}
|
||||||
for field, perms in perm_to_field.items():
|
)
|
||||||
if field in permissions:
|
fields_raise_exception = {}
|
||||||
permissions[field] += perms
|
|
||||||
else:
|
|
||||||
permissions[field] = perms
|
|
||||||
|
|
||||||
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
|
@classmethod
|
||||||
def __get_permission_to_fields__(cls, permission_to_field):
|
def __get_permission_to_fields__(cls, permission_to_field):
|
||||||
|
@ -327,19 +369,41 @@ class DjangoObjectType(ObjectType):
|
||||||
return permissions
|
return permissions
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __set_permissions_resolvers__(cls, permissions):
|
def __set_permissions_resolvers__(
|
||||||
|
cls, permissions, fields_raise_exception, permission_classes
|
||||||
|
):
|
||||||
"""Set permission resolvers"""
|
"""Set permission resolvers"""
|
||||||
for field_name, field_permissions in permissions.items():
|
for field_name, field_permissions in permissions.items():
|
||||||
|
raise_exception = fields_raise_exception.get(field_name, False)
|
||||||
attr = "resolve_{}".format(field_name)
|
attr = "resolve_{}".format(field_name)
|
||||||
resolver = getattr(
|
resolver = getattr(
|
||||||
cls._meta.fields[field_name], "resolver", None
|
cls._meta.fields[field_name], "resolver", None
|
||||||
) or getattr(cls, attr, 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__"):
|
if not hasattr(field_permissions, "__iter__"):
|
||||||
field_permissions = tuple(field_permissions)
|
field_permissions = tuple(field_permissions)
|
||||||
|
|
||||||
setattr(
|
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
|
@classmethod
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
import six
|
import six
|
||||||
from django.core.exceptions import PermissionDenied
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.manager import Manager
|
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.utils import LazyList
|
||||||
from graphene.types.resolver import get_default_resolver
|
from graphene.types.resolver import get_default_resolver
|
||||||
|
@ -43,6 +44,20 @@ def camelize(data):
|
||||||
return 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):
|
def get_reverse_fields(model, local_field_names):
|
||||||
for name, attr in model.__dict__.items():
|
for name, attr in model.__dict__.items():
|
||||||
# Don't duplicate any local fields
|
# Don't duplicate any local fields
|
||||||
|
@ -159,7 +174,17 @@ def auth_resolver(
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
user = info.context.user
|
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:
|
if parent_resolver:
|
||||||
# A resolver is provided in the class
|
# A resolver is provided in the class
|
||||||
return resolve_bound_resolver(parent_resolver, root, info, **args)
|
return resolve_bound_resolver(parent_resolver, root, info, **args)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user