From 320d95333f2515cd5fc74270c1d69f70a4fbea4a Mon Sep 17 00:00:00 2001 From: Valder Gallo Date: Tue, 24 Jan 2017 13:28:19 -0200 Subject: [PATCH] fix #79 add decorator has_perms for nodes --- docs/authorization.rst | 25 ++++++++ graphene_django/decorators.py | 51 +++++++++++++++ graphene_django/tests/test_decorators.py | 80 ++++++++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 graphene_django/decorators.py create mode 100644 graphene_django/tests/test_decorators.py diff --git a/docs/authorization.rst b/docs/authorization.rst index 9ee7d0a..3cd08cb 100644 --- a/docs/authorization.rst +++ b/docs/authorization.rst @@ -108,3 +108,28 @@ method to your ``DjangoObjectType``. if post.published or context.user == post.owner: return post return None + + +Permission node access +---------------------- + +For restrict access using permissions, use the `has_perm` decorator in node. + +.. code:: python + + from graphene_django.types import DjangoObjectType + from graphene_django.decorator import has_perms + from .models import Post + + class PostNode(DjangoObjectType): + class Meta: + model = Post + only_fields = ('title', 'content') + interfaces = (relay.Node, ) + + @has_perms(["django_app.django_can_see_content_permission"]) + def resolve_content(self, id, context, info): + return self.content + + + diff --git a/graphene_django/decorators.py b/graphene_django/decorators.py new file mode 100644 index 0000000..94c68d4 --- /dev/null +++ b/graphene_django/decorators.py @@ -0,0 +1,51 @@ +# coding: utf-8 +from functools import wraps +from django.http import HttpResponseForbidden + + +def has_perms(permissions): + """ + Check if have user logged and permissions to see some value + + Example: + class CityNode(DjangoObjectType): + class Meta(object): + interfaces = (relay.Node,) + model = City + only_fields = ( + 'name', 'locality', 'slug', 'state', 'active', + ) + + @has_perms(["django_city_app.can_see_location"]) + def resolve_location(self, args, context, info): + return self.locality.pos + + Args: + permissions: ["django_app.permission_codename",] + """ + def decorator(method): + if callable(permissions): + method.permissions = [] + else: + method.permissions = permissions + + @wraps(method) + def wrapper(*args, **kwargs): + context = kwargs.get('context', dict(zip(method.func_code.co_varnames, + args)).get('context', None)) + if not context: + return HttpResponseForbidden('Forbidden. No context, no access.') + try: + user = context.user + except AttributeError: + return HttpResponseForbidden('Forbidden. No request.') + + if method.permissions and not user.has_perms(method.permissions): + return HttpResponseForbidden('Forbidden. User without access') + return method(*args, **kwargs) + return wrapper + + if callable(permissions): + return decorator(permissions) + + return decorator diff --git a/graphene_django/tests/test_decorators.py b/graphene_django/tests/test_decorators.py new file mode 100644 index 0000000..b40cec3 --- /dev/null +++ b/graphene_django/tests/test_decorators.py @@ -0,0 +1,80 @@ +# encoding: utf-8 +from django.utils.unittest import TestCase +from ..decorators import has_perms + + +class MockContext(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 + self.status_code = 200 + + def is_authenticated(self): + return self.authenticated + + def has_perms(self, check_perms): + for perm in check_perms: + if perm not in self.perms: + return False + return True + + +class TestHasPermsDecorator(TestCase): + + @classmethod + @has_perms(['django_app.dummy_permission']) + def check_user_perms_func(cls, input, context, info=None): + cls.status_code = 200 + cls.content = True + return cls + + def test_get_content_without_permission(self): + context = MockContext( + authenticated=True + ) + request = TestHasPermsDecorator.check_user_perms_func(None, context=context) + self.assertEqual(request.status_code, 403) + self.assertEqual(request.content, 'Forbidden. User without access') + + def test_get_content_without_authentication(self): + context = MockContext( + authenticated=False + ) + request = TestHasPermsDecorator.check_user_perms_func(None, context=context) + self.assertEqual(request.status_code, 403) + self.assertEqual(request.content, 'Forbidden. User is not authenticated.') + + def test_get_context_with_permission(self): + context = MockContext( + authenticated=True, + perms=['django_app.dummy_permission'] + + ) + request = TestHasPermsDecorator.check_user_perms_func(None, context=context) + self.assertEqual(request.status_code, 200) + self.assertEqual(request.content, True) + + def test_get_context_with_diffent_and_valid_permission(self): + context = MockContext( + authenticated=True, + perms=['another_app.dummy_permission', + 'django_app.dummy_permission'] + + ) + request = TestHasPermsDecorator.check_user_perms_func(None, context=context) + self.assertEqual(request.status_code, 200) + self.assertEqual(request.content, True) + + def test_get_context_with_diffent_and_invalid_permission(self): + context = MockContext( + authenticated=True, + perms=['another_app.dummy_permission', + 'another_app.dummy_permission2'] + + ) + request = TestHasPermsDecorator.check_user_perms_func(None, context=context) + self.assertEqual(request.status_code, 403) + self.assertEqual(request.content, 'Forbidden. User without access')