Permissions and authentication

Main attributes

By default, a mutation is accessible by anything and everyone. To add access-control to a mutation, the meta-attributes permissions and login_required is used.

class CreateUserMutation(DjangoCreateMutation):
    class Meta:
        model = User
        login_required = True
        permissions = ("users.add_user",)


class UpdateUserMutation(DjangoUpdateMutation):
    class Meta:
        model = User
        permissions = ("users.change_user", "users.some_custom_perm")

Note that having a permissions typically (but not necessarily) implies that the user is authenticated. Hence in many cases, simply setting the permissions-array to something is sufficient to guarantee that the user is authenticated.

The get_permissions method

In some scenarios, we might want to grant permission to a mutation conditionally. For this, we can override the get_permissions classmethod, which by default simply returns the permissions-iterable.

Say for example, we want to grant access to update a user-object if the calling user is the same as the updated user, or if the calling user has the users.change_user-permission:

class UpdateUserMutation(DjangoUpdateMutation):
    class Meta:
        model = User
        login_required = True
        permissions = ("users.change_user",)

    @classmethod
    def get_permissions(cls, root, info, input, id) -> Iterable[str]:
        # Use the disambiguate_id utility from graphene_django_cud to parse the id
        if int(disambiguate_id(id)) == info.context.user.id:
            # Returning an empty array is essentially the same as granting access here.
            return []
        return cls._meta.permissions

The get_permissions method takes slightly different arguments depending on what mutation is being used. For patch and update mutations, the method is given (root, info, input, id). For create mutations, the method is given (root, info, input).

Overriding the permissions pipeline

Internally, all mutations call a method called check_permissions when checking permissions. The default implementation of this method simply calls the get_permissions-method, and checks these permissions against the calling user.

check_permissions will by default raise an exception if the calling user does not have the required permissions.

If some other pipeline is desired for checking permissions, you can override the check_permissions-method. For instance, we could implement the permissions-checking above in the following manner:

class UpdateUserMutation(DjangoUpdateMutation):
    class Meta:
        model = User
        login_required = True

    @classmethod
    def check_permissions(cls, root, info, input, id):
        if int(disambiguate_id(id)) == info.context.user.id \
           or info.context.user.has_perm("users.change_user"):
            # Not raising an Exception means the calling user has permission to access the mutation
            return

        raise GraphQLError("You do not have permission to access this mutation.")

You can also wrap check_permissions in decorators, if you so desire.

The check_permissions method takes slightly different arguments depending on what mutation is being used. For patch and update mutations, the method is given (root, info, input, id). For create mutations, the method is given (root, info, input).

Wrapping the mutate method

If none of the above is sufficient, the final frontier is overriding the mutate-method of each mutation class. Note that that check_permissions takes essentially the same arguments as mutate. Hence overriding mutate should only be required in very fringe scenarios.

class UpdateUserMutation(DjangoUpdateMutation):
    class Meta:
        model = User
        login_required = True

    @classmethod
    def mutate(cls, root, info, input, id):
        if int(disambiguate_id(id)) != info.context.user.id \
           and not info.context.user.has_perm("users.change_user"):
            raise GraphQLError("You do not have permission to access this mutation.")

        return super().mutate(root, info, input, id)