PedroBern / django-graphql-auth

Django registration and authentication with GraphQL.
https://django-graphql-auth.readthedocs.io/en/latest/
MIT License
333 stars 105 forks source link

Protect graphene django "auto" queries and mutations #47

Open ckristhoff opened 4 years ago

ckristhoff commented 4 years ago

Prerequisites

Description

Hi, nice work.

I'm trying to private graphene django auto queries and mutations. "Auto" means queries with DjangoObjectType and DjangoFilterConnectionField, and mutations with DjangoModelFormMutation and DjangoFormMutation

The @login_required decorator in graphql_jwt package does not works if resolve method does not exist.

vadolasi commented 4 years ago

Although the question is an old one, I went through the same problem and maybe other people will, so here is the solution:

For DjangoObjectType and DjangoFilterConnectionField:

class LoginRequiredMixin(object):
    @classmethod
    @login_required
    def get_queryset(cls, queryset, info):
        return queryset

class YourType(LoginRequiredMixin, DjangoObjectType):
    ...

For DjangoFormMutation:

class LoginRequiredMixin(object):
    @classmethod
    @login_required
    def perform_mutate(cls, form, info):
        form.save()
        return cls(errors=[], **form.cleaned_data)

class YourMutation(LoginRequiredMixin, DjangoFormMutation):
    ...

For DjangoModelFormMutation:

class LoginRequiredMixin(object):
    @classmethod
    @login_required
    def mutate_and_get_payload(cls, root, info, **input):
        form = cls.get_form(root, info, **input)

        if form.is_valid():
            return cls.perform_mutate(form, info)
        else:
            errors = ErrorType.from_errors(form.errors)

        return cls(errors=errors)

class YourMutation(LoginRequiredMixin, DjangoModelFormMutation):
    ...

And for SerializerMutation (https://docs.graphene-python.org/projects/django/en/latest/mutations/#django-rest-framework):

from django.shortcuts import get_object_or_404

class LoginRequiredMixin(object):
    @classmethod
    @login_required
    def get_serializer_kwargs(cls, root, info, **input_data):
        lookup_field = cls._meta.lookup_field
        model_class = cls._meta.model_class

        if model_class:
            if "update" in cls._meta.model_operations and lookup_field in input_data:
                instance = get_object_or_404(
                    model_class, **{lookup_field: input_data[lookup_field]}
                )
                partial = True
            elif "create" in cls._meta.model_operations:
                instance = None
                partial = False
            else:
                raise Exception(
                    "Invalid update operation. input_data parameter \"{}\" required.".format(
                        lookup_field
                    )
                )

            return {
                "instance": instance,
                "data": input_data,
                "context": {"request": info.context},
                "partial": partial,
            }

        return {"data": input_data, "context": {"request": info.context}}

class YourMutation(LoginRequiredMixin, SerializerMutation):
    ...
lionelyoung commented 3 years ago
errors = ErrorType.from_errors(form.errors)

Here are the imports:

from graphql_jwt.decorators import login_required
from graphene_django.forms.mutation import DjangoFormMutation
from graphene_django.forms.types import ErrorType
lionelyoung commented 3 years ago

@PedroBern is this LoginRequiredMixin appropriate for a PR, if anybody else comes along and wants to pick it up?