graphene-django/docs/authorization.rst
2019-01-14 16:58:25 -05:00

246 lines
7.2 KiB
ReStructuredText

Authorization in Django
=======================
There are several ways you may want to limit access to data when
working with Graphene and Django: limiting which fields are accessible
via GraphQL and limiting which objects a user can access.
Let's use a simple example model.
.. code:: python
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
published = models.BooleanField(default=False)
owner = models.ForeignKey('auth.User')
Limiting Field Access
---------------------
To limit fields in a GraphQL query simply use the ``only_fields`` meta attribute.
.. code:: python
from graphene import relay
from graphene_django.types import DjangoObjectType
from .models import Post
class PostNode(DjangoObjectType):
class Meta:
model = Post
only_fields = ('title', 'content')
interfaces = (relay.Node, )
conversely you can use ``exclude_fields`` meta attribute.
.. code:: python
from graphene import relay
from graphene_django.types import DjangoObjectType
from .models import Post
class PostNode(DjangoObjectType):
class Meta:
model = Post
exclude_fields = ('published', 'owner')
interfaces = (relay.Node, )
Queryset Filtering On Lists
---------------------------
In order to filter which objects are available in a queryset-based list,
define a resolve method for that field and return the desired queryset.
.. code:: python
from graphene import ObjectType
from graphene_django.filter import DjangoFilterConnectionField
from .models import Post
class Query(ObjectType):
all_posts = DjangoFilterConnectionField(PostNode)
def resolve_all_posts(self, info):
return Post.objects.filter(published=True)
User-based Queryset Filtering
-----------------------------
If you are using ``GraphQLView`` you can access Django's request
with the context argument.
.. code:: python
from graphene import ObjectType
from graphene_django.filter import DjangoFilterConnectionField
from .models import Post
class Query(ObjectType):
my_posts = DjangoFilterConnectionField(PostNode)
def resolve_my_posts(self, info):
# context will reference to the Django request
if not info.context.user.is_authenticated():
return Post.objects.none()
else:
return Post.objects.filter(owner=info.context.user)
If you're using your own view, passing the request context into the
schema is simple.
.. code:: python
result = schema.execute(query, context_value=request)
Filtering ID-based Node Access
------------------------------
In order to add authorization to id-based node access, we need to add a
method to your ``DjangoObjectType``.
.. code:: python
from graphene_django.types import DjangoObjectType
from .models import Post
class PostNode(DjangoObjectType):
class Meta:
model = Post
only_fields = ('title', 'content')
interfaces = (relay.Node, )
@classmethod
def get_node(cls, id, info):
try:
post = cls._meta.model.objects.get(id=id)
except cls._meta.model.DoesNotExist:
return None
if post.published or info.context.user == post.owner:
return post
return None
Require permissions
---------------------
If you want you can require Django permissions to access to *Nodes*,
*Mutations* and *Connections*.
Node example:
.. code:: python
from graphene_django.types import DjangoObjectType
from graphene_django.auth import node_require_permission
from .models import Reporter
class ReporterType(DjangoObjectType):
class Meta:
model = Reporter
interfaces = (Node, )
@classmethod
@node_require_permission(permissions=('can_view_report', 'can_edit_foo', ))
def get_node(cls, info, id):
return super(ReporterType, cls).get_node(info, id)
Mutation example:
.. code:: python
from rest_framework import serializers
from graphene_django.types import DjangoObjectType
from graphene_django.auth import node_require_permission
from graphene_django.rest_framework.mutation import SerializerMutation
from .models import Reporter
class ReporterSerializer(serializers.ModelSerializer):
class Meta:
model = Reporter
fields = '__all__'
class MyMutation(SerializerMutation):
class Meta:
serializer_class = ReporterSerializer
@classmethod
@mutation_require_permission(permissions=('can_view_foo', 'can_edit_foo', ))
def mutate_and_get_payload(cls, root, info, **input):
return super(MyMutation, cls).mutate_and_get_payload(root, info, **input)
Connection example:
.. code:: python
import graphene
from graphene_django.fields import DjangoConnectionField
from graphene_django.auth import connection_require_permission, node_require_permission
from graphene_django.types import DjangoObjectType
from .models import Reporter
class ReporterType(DjangoObjectType):
class Meta:
model = Reporter
interfaces = (Node, )
@classmethod
@node_require_permission(permissions=('can_view_report', 'can_edit_foo', ))
def get_node(cls, info, id):
return super(ReporterType, cls).get_node(info, id)
class MyAuthDjangoConnectionField(DjangoConnectionField):
@classmethod
@connection_require_permission(permissions=('can_view_foo', ))
def connection_resolver(cls, resolver, connection, default_manager, max_limit,
enforce_first_or_last, root, info, **args):
return super(MyAuthDjangoConnectionField, cls).connection_resolver(
resolver, connection, default_manager, max_limit,
enforce_first_or_last, root, info, **args)
class Query(graphene.ObjectType):
all_reporters = MyAuthDjangoConnectionField(ReporterType)
Adding Login Required
---------------------
To restrict users from accessing the GraphQL API page the standard Django LoginRequiredMixin_ can be used to create your own standard Django Class Based View, which includes the ``LoginRequiredMixin`` and subclasses the ``GraphQLView``.:
.. code:: python
#views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from graphene_django.views import GraphQLView
class PrivateGraphQLView(LoginRequiredMixin, GraphQLView):
pass
After this, you can use the new ``PrivateGraphQLView`` in the project's URL Configuration file ``url.py``:
For Django 1.9 and below:
.. code:: python
urlpatterns = [
# some other urls
url(r'^graphql', PrivateGraphQLView.as_view(graphiql=True, schema=schema)),
]
For Django 2.0 and above:
.. code:: python
urlpatterns = [
# some other urls
path('graphql', PrivateGraphQLView.as_view(graphiql=True, schema=schema)),
]
.. _LoginRequiredMixin: https://docs.djangoproject.com/en/1.10/topics/auth/default/#the-loginrequired-mixin