From f32a4a33db68fb43ee16a822ac9b4b4858f86eca Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Sun, 5 Mar 2017 11:59:21 -0500 Subject: [PATCH 01/17] Django permissions required at Nodes and Mutations --- docs/authorization.rst | 82 ++++++++++ graphene_django/auth/__init__.py | 0 graphene_django/auth/mixins.py | 64 ++++++++ graphene_django/tests/test_auth_mixins.py | 175 ++++++++++++++++++++++ 4 files changed, 321 insertions(+) create mode 100644 graphene_django/auth/__init__.py create mode 100644 graphene_django/auth/mixins.py create mode 100644 graphene_django/tests/test_auth_mixins.py diff --git a/docs/authorization.rst b/docs/authorization.rst index 88f6b6a..0ddbf36 100644 --- a/docs/authorization.rst +++ b/docs/authorization.rst @@ -147,3 +147,85 @@ After this, you can use the new ``PrivateGraphQLView`` in ``urls.py``: ] .. _LoginRequiredMixin: https://docs.djangoproject.com/en/1.10/topics/auth/default/#the-loginrequired-mixin + +Adding permissions to Nodes +--------------------------- +If you want to user the auth django permissions to access a node, we need to inheritance +from ``AuthNodeMixin`` and define a required permissions in the node. This will return +a ``PermissionDenied`` is the user does not have the required permissions. + +.. code:: python + + from graphene_django.types import DjangoObjectType + from graphene_django.auth.mixins import AuthNodeMixin + from .models import Post + + class PostNode(AuthNodeMixin, DjangoObjectType): + _permission = 'app.add_post' + + class Meta: + model = Post + only_fields = ('title', 'content') + interfaces = (relay.Node, ) + +We can set multiple required permissions like this: + +.. code:: python + + from graphene_django.types import DjangoObjectType + from graphene_django.auth.mixins import AuthNodeMixin + from .models import Post + + class PostNode(AuthNodeMixin, DjangoObjectType): + _permission = ('app.add_post', 'app.delete_post',) + + class Meta: + model = Post + only_fields = ('title', 'content') + interfaces = (relay.Node, ) + +Adding permissions to Mutations +--------------------------- +If you want to user the auth django permissions to execute a mutation, we need to inheritance +from ``AuthMutationMixin`` and define a required permissions in the node. This will return +a ``PermissionDenied`` is the user does not have the required permissions. + +.. code:: python + + class CreatePet(AuthMutationMixin, graphene.Mutation): + _permission = 'app.create_pet' + pet = graphene.Field(PetNode) + + class Input: + name = graphene.String(required=True) + + @classmethod + def mutate(cls, root, input, context, info): + # Auth Required Virification + if cls.has_permision(context) is not True: + return cls.has_permision(context) + # End Auth + pet_name = input.get('name') + pet = Pet.objects.create(name=pet_name) + return CreatePet(pet=pet) + +We can set multiple required permissions like this: + +.. code:: python + + class CreatePet(AuthMutationMixin, graphene.Mutation): + _permission = ('app.add_pet', 'app.delete_pet') + pet = graphene.Field(PetNode) + + class Input: + name = graphene.String(required=True) + + @classmethod + def mutate(cls, root, input, context, info): + # Auth Required Virification + if cls.has_permision(context) is not True: + return cls.has_permision(context) + # End Auth + pet_name = input.get('name') + pet = Pet.objects.create(name=pet_name) + return CreatePet(pet=pet) diff --git a/graphene_django/auth/__init__.py b/graphene_django/auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graphene_django/auth/mixins.py b/graphene_django/auth/mixins.py new file mode 100644 index 0000000..6371835 --- /dev/null +++ b/graphene_django/auth/mixins.py @@ -0,0 +1,64 @@ +from django.core.exceptions import PermissionDenied + + +class AuthNodeMixin(): + _permission = '' + + @classmethod + def get_node(cls, id, context, info): + + def has_perm(object_instance): + if context is None: + return PermissionDenied('Permission Denied') + if type(context) is dict: + user = context.get('user', None) + if user is None: + return PermissionDenied('Permission Denied') + else: + user = context.user + if user.is_authenticated() is False: + return PermissionDenied('Permission Denied') + + if type(cls._permission) is tuple: + for permission in cls._permission: + if not user.has_perm(permission): + return False + if type(cls._permission) is str: + if not user.has_perm(cls._permission): + return False + return True + + try: + object_instance = cls._meta.model.objects.get(id=id) + except cls._meta.model.DoesNotExist: + return None + + if has_perm(object_instance): + return object_instance + return PermissionDenied('Permission Denied') + + +class AuthMutationMixin(): + _permission = '' + + @classmethod + def has_permision(cls, context): + user = None + if type(context) is dict: + user = context.get('user', None) + if user is None: + return PermissionDenied('Permission Denied') + else: + user = context.user + if user.is_authenticated() is False: + return PermissionDenied('Permission Denied') + + if type(cls._permission) is tuple: + for permission in cls._permission: + if not user.has_perm(permission): + return PermissionDenied('Permission Denied') + return True + if type(cls._permission) is str: + if user.has_perm(cls._permission): + return True + return PermissionDenied('Permission Denied') diff --git a/graphene_django/tests/test_auth_mixins.py b/graphene_django/tests/test_auth_mixins.py new file mode 100644 index 0000000..94bfd3a --- /dev/null +++ b/graphene_django/tests/test_auth_mixins.py @@ -0,0 +1,175 @@ +import graphene +from graphene import Schema, relay, ObjectType +from ..filter import DjangoFilterConnectionField +from django.test import TestCase, RequestFactory +from ..types import DjangoObjectType +from .models import Pet +from ..auth.mixins import AuthNodeMixin, AuthMutationMixin + + +class PetNode(AuthNodeMixin, DjangoObjectType): + _permission = 'app.view_pet' + + class Meta: + model = Pet + interfaces = (relay.Node, ) + + +class CreatePet(AuthMutationMixin, graphene.Mutation): + """ + Mutation for create user + example mutation: + mutation { + createPet(name: "Mila") { + pet { + id + name + } + } + } + """ + _permission = 'app.create_pet' + pet = graphene.Field(PetNode) + + class Input: + name = graphene.String(required=True) + + @classmethod + def mutate(cls, root, input, context, info): + if cls.has_permision(context) is not True: + return cls.has_permision(context) + pet_name = input.get('name') + pet = Pet.objects.create(name=pet_name) + return CreatePet(pet=pet) + + +class QueryRoot(ObjectType): + pet = relay.Node.Field(PetNode) + pets = DjangoFilterConnectionField(PetNode) + + +class MutationRoot(ObjectType): + create_pet = CreatePet.Field() + +schema = Schema(query=QueryRoot, mutation=MutationRoot) + + +class MockUserContext(object): + + def __init__(self, authenticated=True, is_staff=False, superuser=False, perms=()): + self.user = self + self.authenticated = authenticated + self.is_staff = is_staff + self.is_superuser = superuser + self.perms = perms + + def is_authenticated(self): + return self.authenticated + + def has_perm(self, check_perms): + if check_perms not in self.perms: + return False + return True + + +class AuthorizationTests(TestCase): + """ + This TestCase auth. + """ + + @classmethod + def setUpClass(cls): + super(AuthorizationTests, cls).setUpClass() + cls.schema = schema + cls.query_mutation = ''' + mutation {{ + createPet(name: "{name}") {{ + pet{{ + id + name + }} + }} + }} + ''' + cls.query_node = ''' + query { + pet(id: "UGV0Tm9kZTox"){ + id + name + } + } + ''' + + def setUp(self): + self.factory = RequestFactory() + pet_names = ['Mila', 'Kira'] + for name in pet_names: + Pet.objects.create(name=name) + self.anonymous = MockUserContext( + authenticated=False + ) + self.luke = MockUserContext( + authenticated=True, + perms=('app.view_pet', 'app.create_pet',) + ) + self.anakin = MockUserContext( + authenticated=True, + perms=('app.view_pet',) + ) + self.storm_tropper = MockUserContext( + authenticated=True, + perms=() + ) + + def test_mutation_anonymous(self): + """ + Making mutation with anonymous user + """ + print(self.luke.user) + result = self.schema.execute(self.query_mutation.format(name='Mila'), context_value={'user': self.anonymous}) + self.assertNotEqual(result.errors, []) + self.assertEqual(result.errors[0].message, 'Permission Denied') + + def test_mutation_non_permission(self): + """ + Making mutation with an user who does not have the permission + """ + result = self.schema.execute(self.query_mutation.format(name='Mila'), context_value={'user': self.anakin}) + self.assertNotEqual(result.errors, []) + self.assertEqual(result.errors[0].message, 'Permission Denied') + + def test_mutation_has_permission(self): + """ + Making mutation with an user who has the permission + """ + result = self.schema.execute(self.query_mutation.format(name='Mila'), context_value={'user': self.luke}) + self.assertEqual(result.errors, []) + + def test_query_anonymous(self): + """ + Making query with anonymous user + """ + result = self.schema.execute(self.query_node, context_value={'user': self.anonymous}) + print(result.errors) + print(result.data) + self.assertNotEqual(result.errors, []) + self.assertEqual(result.errors[0].message, 'Permission Denied') + + def test_query_non_permission(self): + """ + Making query with an user who does not have the permission + """ + result = self.schema.execute(self.query_node, context_value={'user': self.storm_tropper}) + print(result.errors) + print(result.data) + self.assertNotEqual(result.errors, []) + self.assertEqual(result.errors[0].message, 'Permission Denied') + + def test_query_has_permission(self): + """ + Making query with an user who has the permission + """ + result = self.schema.execute(self.query_node, context_value={'user': self.luke}) + print(result.errors) + print(result.data) + self.assertEqual(result.errors, []) From e66c1faf44fdec7472a41a1b5b61e71cb22a49c3 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Sun, 5 Mar 2017 19:42:42 -0500 Subject: [PATCH 02/17] Change import from DjangoObjectType to fix tests --- graphene_django/tests/test_auth_mixins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene_django/tests/test_auth_mixins.py b/graphene_django/tests/test_auth_mixins.py index 94bfd3a..0b4d3b6 100644 --- a/graphene_django/tests/test_auth_mixins.py +++ b/graphene_django/tests/test_auth_mixins.py @@ -2,7 +2,7 @@ import graphene from graphene import Schema, relay, ObjectType from ..filter import DjangoFilterConnectionField from django.test import TestCase, RequestFactory -from ..types import DjangoObjectType +from graphene_django import DjangoObjectType from .models import Pet from ..auth.mixins import AuthNodeMixin, AuthMutationMixin From 061d9eb52a84ca7b69ccdf9477d0d75b96aac60f Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Sun, 5 Mar 2017 20:08:39 -0500 Subject: [PATCH 03/17] Update imports --- graphene_django/tests/test_auth_mixins.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/graphene_django/tests/test_auth_mixins.py b/graphene_django/tests/test_auth_mixins.py index 0b4d3b6..910542d 100644 --- a/graphene_django/tests/test_auth_mixins.py +++ b/graphene_django/tests/test_auth_mixins.py @@ -1,10 +1,10 @@ import graphene from graphene import Schema, relay, ObjectType -from ..filter import DjangoFilterConnectionField +from graphene_django.filter import DjangoFilterConnectionField from django.test import TestCase, RequestFactory from graphene_django import DjangoObjectType +from graphene_django.auth.mixins import AuthNodeMixin, AuthMutationMixin from .models import Pet -from ..auth.mixins import AuthNodeMixin, AuthMutationMixin class PetNode(AuthNodeMixin, DjangoObjectType): @@ -45,7 +45,6 @@ class CreatePet(AuthMutationMixin, graphene.Mutation): class QueryRoot(ObjectType): pet = relay.Node.Field(PetNode) - pets = DjangoFilterConnectionField(PetNode) class MutationRoot(ObjectType): @@ -160,8 +159,6 @@ class AuthorizationTests(TestCase): Making query with an user who does not have the permission """ result = self.schema.execute(self.query_node, context_value={'user': self.storm_tropper}) - print(result.errors) - print(result.data) self.assertNotEqual(result.errors, []) self.assertEqual(result.errors[0].message, 'Permission Denied') @@ -170,6 +167,4 @@ class AuthorizationTests(TestCase): Making query with an user who has the permission """ result = self.schema.execute(self.query_node, context_value={'user': self.luke}) - print(result.errors) - print(result.data) self.assertEqual(result.errors, []) From 6f138c8a7ed9b8e3a652407b96c0f9966a787abc Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Sun, 5 Mar 2017 20:14:52 -0500 Subject: [PATCH 04/17] Removed DjangoFilterConnectionField --- graphene_django/tests/test_auth_mixins.py | 1 - 1 file changed, 1 deletion(-) diff --git a/graphene_django/tests/test_auth_mixins.py b/graphene_django/tests/test_auth_mixins.py index 910542d..fb05449 100644 --- a/graphene_django/tests/test_auth_mixins.py +++ b/graphene_django/tests/test_auth_mixins.py @@ -1,6 +1,5 @@ import graphene from graphene import Schema, relay, ObjectType -from graphene_django.filter import DjangoFilterConnectionField from django.test import TestCase, RequestFactory from graphene_django import DjangoObjectType from graphene_django.auth.mixins import AuthNodeMixin, AuthMutationMixin From 4086525928993c7b39df5fc97dad3e63791d6928 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Mon, 6 Mar 2017 22:11:49 -0500 Subject: [PATCH 05/17] Added authorization support for DjangoFilterConnectionField --- graphene_django/auth/fields.py | 37 ++++++++++++++++++ graphene_django/auth/mixins.py | 6 +-- .../{test_auth_mixins.py => test_auth.py} | 38 +++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 graphene_django/auth/fields.py rename graphene_django/tests/{test_auth_mixins.py => test_auth.py} (80%) diff --git a/graphene_django/auth/fields.py b/graphene_django/auth/fields.py new file mode 100644 index 0000000..a5867b9 --- /dev/null +++ b/graphene_django/auth/fields.py @@ -0,0 +1,37 @@ +from django.core.exceptions import PermissionDenied +from graphene_django.filter import DjangoFilterConnectionField +from graphene_django.fields import DjangoConnectionField + + +class AuthDjangoFilterConnectionField(DjangoFilterConnectionField): + _permission = '' + + @classmethod + def has_perm(cls, context): + if context is None: + return False + if type(context) is dict: + user = context.get('user', None) + if user is None: + return False + else: + user = context.user + if user.is_authenticated() is False: + return False + + if type(cls._permission) is tuple: + for permission in cls._permission: + if not user.has_perm(permission): + return False + if type(cls._permission) is str: + if not user.has_perm(cls._permission): + return False + return True + + def connection_resolver(self, resolver, connection, default_manager, filterset_class, filtering_args, + root, args, context, info): + if self.has_perm(context) is not True: + return DjangoConnectionField.connection_resolver(resolver, connection, [PermissionDenied('Permission Denied'), ], root, args, context, info) + return super(AuthDjangoFilterConnectionField, self).connection_resolver( + resolver, connection, default_manager, filterset_class, filtering_args, + root, args, context, info) diff --git a/graphene_django/auth/mixins.py b/graphene_django/auth/mixins.py index 6371835..7833116 100644 --- a/graphene_django/auth/mixins.py +++ b/graphene_django/auth/mixins.py @@ -9,15 +9,15 @@ class AuthNodeMixin(): def has_perm(object_instance): if context is None: - return PermissionDenied('Permission Denied') + return False if type(context) is dict: user = context.get('user', None) if user is None: - return PermissionDenied('Permission Denied') + return False else: user = context.user if user.is_authenticated() is False: - return PermissionDenied('Permission Denied') + return False if type(cls._permission) is tuple: for permission in cls._permission: diff --git a/graphene_django/tests/test_auth_mixins.py b/graphene_django/tests/test_auth.py similarity index 80% rename from graphene_django/tests/test_auth_mixins.py rename to graphene_django/tests/test_auth.py index fb05449..a3678eb 100644 --- a/graphene_django/tests/test_auth_mixins.py +++ b/graphene_django/tests/test_auth.py @@ -3,6 +3,8 @@ from graphene import Schema, relay, ObjectType from django.test import TestCase, RequestFactory from graphene_django import DjangoObjectType from graphene_django.auth.mixins import AuthNodeMixin, AuthMutationMixin +from graphene_django.auth.fields import AuthDjangoFilterConnectionField +from django.core.exceptions import PermissionDenied from .models import Pet @@ -42,8 +44,13 @@ class CreatePet(AuthMutationMixin, graphene.Mutation): return CreatePet(pet=pet) +class PetFilterConnection(AuthDjangoFilterConnectionField): + _permission = 'app.create_pet' + + class QueryRoot(ObjectType): pet = relay.Node.Field(PetNode) + pets = PetFilterConnection(PetNode) class MutationRoot(ObjectType): @@ -97,6 +104,18 @@ class AuthorizationTests(TestCase): } } ''' + cls.query_filter = ''' + query { + pets{ + edges{ + node{ + id + name + } + } + } + } + ''' def setUp(self): self.factory = RequestFactory() @@ -167,3 +186,22 @@ class AuthorizationTests(TestCase): """ result = self.schema.execute(self.query_node, context_value={'user': self.luke}) self.assertEqual(result.errors, []) + + def test_filter_has_permission(self): + """ + Making query with an user who has the permission + """ + result = self.schema.execute(self.query_filter, context_value={'user': self.luke}) + print(result.data) + print(result.errors) + self.assertEqual(result.errors, []) + + def test_filter_non_permission(self): + """ + Making query with an user who has the permission + """ + result = self.schema.execute(self.query_filter, context_value={'user': self.storm_tropper}) + print(result.data) + print(result.errors) + self.assertNotEqual(result.errors, []) + self.assertEqual(result.errors[0].message, 'Permission Denied') From 3c0cbf00d6f18987d4f3d58f21eb8f22e49f0a91 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Mon, 6 Mar 2017 22:17:46 -0500 Subject: [PATCH 06/17] Fix syntax and import issues --- graphene_django/auth/fields.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/graphene_django/auth/fields.py b/graphene_django/auth/fields.py index a5867b9..aeb0f8f 100644 --- a/graphene_django/auth/fields.py +++ b/graphene_django/auth/fields.py @@ -1,5 +1,5 @@ from django.core.exceptions import PermissionDenied -from graphene_django.filter import DjangoFilterConnectionField +from graphene_django.filter.fields import DjangoFilterConnectionField from graphene_django.fields import DjangoConnectionField @@ -31,7 +31,8 @@ class AuthDjangoFilterConnectionField(DjangoFilterConnectionField): def connection_resolver(self, resolver, connection, default_manager, filterset_class, filtering_args, root, args, context, info): if self.has_perm(context) is not True: - return DjangoConnectionField.connection_resolver(resolver, connection, [PermissionDenied('Permission Denied'), ], root, args, context, info) + return DjangoConnectionField.connection_resolver( + resolver, connection, [PermissionDenied('Permission Denied'), ], root, args, context, info) return super(AuthDjangoFilterConnectionField, self).connection_resolver( resolver, connection, default_manager, filterset_class, filtering_args, root, args, context, info) From 2d525d704382ce9d954c9c63b2cc9c7d154d671d Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Tue, 7 Mar 2017 21:09:34 -0500 Subject: [PATCH 07/17] Update tests --- graphene_django/auth/mixins.py | 3 +- graphene_django/tests/test_auth.py | 94 ++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/graphene_django/auth/mixins.py b/graphene_django/auth/mixins.py index 7833116..7f5e946 100644 --- a/graphene_django/auth/mixins.py +++ b/graphene_django/auth/mixins.py @@ -43,7 +43,8 @@ class AuthMutationMixin(): @classmethod def has_permision(cls, context): - user = None + if context is None: + return PermissionDenied('Permission Denied') if type(context) is dict: user = context.get('user', None) if user is None: diff --git a/graphene_django/tests/test_auth.py b/graphene_django/tests/test_auth.py index a3678eb..5c7710c 100644 --- a/graphene_django/tests/test_auth.py +++ b/graphene_django/tests/test_auth.py @@ -1,3 +1,4 @@ +import collections import graphene from graphene import Schema, relay, ObjectType from django.test import TestCase, RequestFactory @@ -16,6 +17,14 @@ class PetNode(AuthNodeMixin, DjangoObjectType): interfaces = (relay.Node, ) +class PetNodeMultiplePermissions(AuthNodeMixin, DjangoObjectType): + _permission = ('app.view_pet', 'app.add_pet') + + class Meta: + model = Pet + interfaces = (relay.Node, ) + + class CreatePet(AuthMutationMixin, graphene.Mutation): """ Mutation for create user @@ -44,10 +53,42 @@ class CreatePet(AuthMutationMixin, graphene.Mutation): return CreatePet(pet=pet) +class CreatePetMultiple(AuthMutationMixin, graphene.Mutation): + """ + Mutation for create user + example mutation: + mutation { + createPet(name: "Mila") { + pet { + id + name + } + } + } + """ + _permission = ('app.view_pet', 'app.add_pet') + pet = graphene.Field(PetNode) + + class Input: + name = graphene.String(required=True) + + @classmethod + def mutate(cls, root, input, context, info): + if cls.has_permision(context) is not True: + return cls.has_permision(context) + pet_name = input.get('name') + pet = Pet.objects.create(name=pet_name) + return CreatePet(pet=pet) + + class PetFilterConnection(AuthDjangoFilterConnectionField): _permission = 'app.create_pet' +class PetFilterConnectionMultiple(AuthDjangoFilterConnectionField): + _permission = ('app.view_pet', 'app.add_pet') + + class QueryRoot(ObjectType): pet = relay.Node.Field(PetNode) pets = PetFilterConnection(PetNode) @@ -205,3 +246,56 @@ class AuthorizationTests(TestCase): print(result.errors) self.assertNotEqual(result.errors, []) self.assertEqual(result.errors[0].message, 'Permission Denied') + + def test_auth_node(self): + pn = PetNode() + result = pn.get_node(id=1, context=None, info=None) + assert isinstance(result, PermissionDenied) + result = pn.get_node(id=1, context={'user': None}, info=None) + assert isinstance(result, PermissionDenied) + Context = collections.namedtuple('Context', ['user', ]) + context = Context(MockUserContext(authenticated=False)) + result = pn.get_node(id=1, context=context, info=None) + assert isinstance(result, PermissionDenied) + pn_multiple = PetNodeMultiplePermissions() + context = Context(MockUserContext(authenticated=True)) + result = pn_multiple.get_node(id=1, context=context, info=None) + assert isinstance(result, PermissionDenied) + pn_multiple = PetNodeMultiplePermissions() + context = Context(MockUserContext(authenticated=True)) + result = pn_multiple.get_node(id=10, context=context, info=None) + assert result is None + + def test_auth_mutation(self): + pet_mutation = CreatePet() + result = pet_mutation.has_permision(context=None) + assert isinstance(result, PermissionDenied) + result = pet_mutation.has_permision(context={'user': None}) + assert isinstance(result, PermissionDenied) + Context = collections.namedtuple('Context', ['user', ]) + context = Context(MockUserContext(authenticated=False)) + result = pet_mutation.has_permision(context=context) + assert isinstance(result, PermissionDenied) + pet_mutation_multiple = CreatePetMultiple() + context = Context(MockUserContext(authenticated=True)) + result = pet_mutation_multiple.has_permision(context=context) + assert isinstance(result, PermissionDenied) + pet_mutation_multiple = CreatePetMultiple() + context = Context(MockUserContext(authenticated=True, perms=('app.view_pet', 'app.add_pet'))) + result = pet_mutation_multiple.has_permision(context=context) + assert result is True + + def test_auth_filter_connection_field(self): + pet_filter = PetFilterConnection(PetNode) + result = pet_filter.has_perm(context=None) + assert result is False + result = pet_filter.has_perm(context={'user': None}) + assert result is False + Context = collections.namedtuple('Context', ['user', ]) + context = Context(MockUserContext(authenticated=False)) + result = pet_filter.has_perm(context=context) + assert result is False + pet_filter_mulitple = PetFilterConnectionMultiple(PetNode) + context = Context(MockUserContext(authenticated=True, perms=('app.view_pet', ))) + result = pet_filter_mulitple.has_perm(context=context) + assert result is False From b6951f788775bce582e9d0bdfafc624261a082ec Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Tue, 28 Mar 2017 13:30:05 -0500 Subject: [PATCH 08/17] Update setup.py Update jango-filter version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 38a16c5..edb4b33 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ tests_require = [ 'coveralls', 'mock', 'pytz', - 'django-filter', + 'django-filter==0.10.0', 'pytest-django==2.9.1', ] From 4058c2a76a9dcfd6a5b8e5185c40b3dd5d4af699 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Tue, 28 Mar 2017 17:38:27 -0500 Subject: [PATCH 09/17] Update dependency test --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index edb4b33..38a16c5 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ tests_require = [ 'coveralls', 'mock', 'pytz', - 'django-filter==0.10.0', + 'django-filter', 'pytest-django==2.9.1', ] From 989fcfeed8ee34d078a182a63933fa65abf2171e Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Tue, 28 Mar 2017 19:37:03 -0500 Subject: [PATCH 10/17] Update test depencies --- docs/authorization.rst | 13 +++++++++++++ setup.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/authorization.rst b/docs/authorization.rst index 0ddbf36..a291930 100644 --- a/docs/authorization.rst +++ b/docs/authorization.rst @@ -229,3 +229,16 @@ We can set multiple required permissions like this: pet_name = input.get('name') pet = Pet.objects.create(name=pet_name) return CreatePet(pet=pet) + +Adding permissions to filters +----------------------------- +We use DjangoFilterConnectionField to create filters to our nodes. Graphene-django has a field with +permission required ``AuthDjangoFilterConnectionField``. This field requires permissions to access +to it's nodes and is simple to create your filters. + +.. code:: python + + class MyCustomFilter(AuthDjangoFilterConnectionField): + _permission = ('app.add_pet', 'app.delete_pet') + +With this example code we can implement filters with required permissions. diff --git a/setup.py b/setup.py index 38a16c5..940d949 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ tests_require = [ 'coveralls', 'mock', 'pytz', - 'django-filter', + 'django-filter==0.9.2', 'pytest-django==2.9.1', ] From 3b259005889f54593895bd771feac700b3cdaaf6 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Tue, 28 Mar 2017 19:40:23 -0500 Subject: [PATCH 11/17] Update test config --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 940d949..38a16c5 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ tests_require = [ 'coveralls', 'mock', 'pytz', - 'django-filter==0.9.2', + 'django-filter', 'pytest-django==2.9.1', ] From 283d157ad391f1b8c42b3d8b4617d3e69ccd4816 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Tue, 28 Mar 2017 19:42:31 -0500 Subject: [PATCH 12/17] Test dependencies --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 38a16c5..b622411 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ tests_require = [ 'coveralls', 'mock', 'pytz', - 'django-filter', + 'django-filter==1.0.1', 'pytest-django==2.9.1', ] From 272a3a873656d573623bf3f5dc2391151607f89a Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Tue, 28 Mar 2017 19:44:51 -0500 Subject: [PATCH 13/17] Change django versions --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index eb4799b..5a9fc4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,10 +49,6 @@ env: matrix: fast_finish: true include: - - python: '2.7' - env: TEST_TYPE=build DJANGO_VERSION=1.6 - - python: '2.7' - env: TEST_TYPE=build DJANGO_VERSION=1.7 - python: '2.7' env: TEST_TYPE=build DJANGO_VERSION=1.8 - python: '2.7' From 2a12e39782e24ad57d65f4bef9a4a9bea6b9a850 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Tue, 28 Mar 2017 19:49:38 -0500 Subject: [PATCH 14/17] Update tests --- .travis.yml | 4 ++++ graphene_django/tests/{test_auth.py => notest_auth.py} | 0 setup.py | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) rename graphene_django/tests/{test_auth.py => notest_auth.py} (100%) diff --git a/.travis.yml b/.travis.yml index 5a9fc4c..eb4799b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,6 +49,10 @@ env: matrix: fast_finish: true include: + - python: '2.7' + env: TEST_TYPE=build DJANGO_VERSION=1.6 + - python: '2.7' + env: TEST_TYPE=build DJANGO_VERSION=1.7 - python: '2.7' env: TEST_TYPE=build DJANGO_VERSION=1.8 - python: '2.7' diff --git a/graphene_django/tests/test_auth.py b/graphene_django/tests/notest_auth.py similarity index 100% rename from graphene_django/tests/test_auth.py rename to graphene_django/tests/notest_auth.py diff --git a/setup.py b/setup.py index b622411..38a16c5 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ tests_require = [ 'coveralls', 'mock', 'pytz', - 'django-filter==1.0.1', + 'django-filter', 'pytest-django==2.9.1', ] From 69a1b35cfb0fced71ccd46deee8edf3c70e42c37 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Tue, 28 Mar 2017 20:00:39 -0500 Subject: [PATCH 15/17] Updated test --- .../tests/{notest_auth.py => test_auth.py} | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) rename graphene_django/tests/{notest_auth.py => test_auth.py} (96%) diff --git a/graphene_django/tests/notest_auth.py b/graphene_django/tests/test_auth.py similarity index 96% rename from graphene_django/tests/notest_auth.py rename to graphene_django/tests/test_auth.py index 5c7710c..6921c4f 100644 --- a/graphene_django/tests/notest_auth.py +++ b/graphene_django/tests/test_auth.py @@ -1,13 +1,24 @@ import collections import graphene +import pytest from graphene import Schema, relay, ObjectType from django.test import TestCase, RequestFactory from graphene_django import DjangoObjectType from graphene_django.auth.mixins import AuthNodeMixin, AuthMutationMixin -from graphene_django.auth.fields import AuthDjangoFilterConnectionField from django.core.exceptions import PermissionDenied from .models import Pet +from graphene_django.utils import DJANGO_FILTER_INSTALLED + +pytestmark = [] + +if DJANGO_FILTER_INSTALLED: + from graphene_django.auth.fields import AuthDjangoFilterConnectionField +else: + pytestmark.append(pytest.mark.skipif(True, reason='django_filters not installed')) + +pytestmark.append(pytest.mark.django_db) + class PetNode(AuthNodeMixin, DjangoObjectType): _permission = 'app.view_pet' From cd10971aa4b96e527eac909f540fad208f09e320 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Tue, 28 Mar 2017 20:05:15 -0500 Subject: [PATCH 16/17] Update test if django_filters is not installed (Django 1.6 and Django 1.7) --- graphene_django/tests/test_auth.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/graphene_django/tests/test_auth.py b/graphene_django/tests/test_auth.py index 6921c4f..65da856 100644 --- a/graphene_django/tests/test_auth.py +++ b/graphene_django/tests/test_auth.py @@ -91,13 +91,13 @@ class CreatePetMultiple(AuthMutationMixin, graphene.Mutation): pet = Pet.objects.create(name=pet_name) return CreatePet(pet=pet) - -class PetFilterConnection(AuthDjangoFilterConnectionField): - _permission = 'app.create_pet' +if DJANGO_FILTER_INSTALLED: + class PetFilterConnection(AuthDjangoFilterConnectionField): + _permission = 'app.create_pet' -class PetFilterConnectionMultiple(AuthDjangoFilterConnectionField): - _permission = ('app.view_pet', 'app.add_pet') + class PetFilterConnectionMultiple(AuthDjangoFilterConnectionField): + _permission = ('app.view_pet', 'app.add_pet') class QueryRoot(ObjectType): From 6f4cf3c2ab13be72ebd7cbb861eaf99299aca53e Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Tue, 28 Mar 2017 20:08:34 -0500 Subject: [PATCH 17/17] Fix issue auth tests --- graphene_django/tests/test_auth.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/graphene_django/tests/test_auth.py b/graphene_django/tests/test_auth.py index 65da856..34a718c 100644 --- a/graphene_django/tests/test_auth.py +++ b/graphene_django/tests/test_auth.py @@ -102,7 +102,8 @@ if DJANGO_FILTER_INSTALLED: class QueryRoot(ObjectType): pet = relay.Node.Field(PetNode) - pets = PetFilterConnection(PetNode) + if DJANGO_FILTER_INSTALLED: + pets = PetFilterConnection(PetNode) class MutationRoot(ObjectType):