3

I have the following schema in my graphene-django application:

import graphene
from django.contrib.auth import get_user_model
from graphene_django import DjangoObjectType


class UserType(DjangoObjectType):
    class Meta:
        model = get_user_model()
        fields = ("id", "username", "email")


class Query(object):
    user = graphene.Field(UserType, user_id=graphene.Int())

    def resolve_user(self, info, user_id):
        user = get_user_model().objects.get(pk=user_id)
        if info.context.user.id != user_id:
            # If the query didn't access email field -> query is ok
            # If the query tried to access email field -> raise an error
        else:
            # Logged in as the user we're querying -> let the query access all the fields

I want to be able to query the schema in the following way:

# Logged in as user 1 => no errors, because we're allowed to see all fields
query {
  user (userId: 1) {
    id
    username
    email
  }
}

# Not logged in as user 1 => no errors, because not trying to see email
query {
  user (userId: 1) {
    id
    username
  }
}

# Not logged in as user 1 => return error because accessing email
query {
  user (userId: 1) {
    id
    username
    email
  }
}

How can I make it so that only a logged in user can see the email field of their own profile and no one else can see the emails of others?

ruohola
  • 21,987
  • 6
  • 62
  • 97
  • Take a look at the [authorization example](http://docs.graphene-python.org/en/latest/execution/middleware/#example) of Graphene middleware. – Tomáš Linhart Oct 11 '19 at 07:48
  • @TomasLinhart I've gone through that many times, but can't wrap my head around how to do it. – ruohola Oct 11 '19 at 10:29
  • You are right that using authorization middleware for this case is maybe a bit superfluous. However, the solution depends on if you want to return the user object without `email` field (suggested by the first code block) or raise an error (suggested by the second code block). If the former, just do `user['email'] = None` or `del user['email']` in `if info.context.user.id != user_id:` branch. If the later, raise an exception there. – Tomáš Linhart Oct 11 '19 at 12:19
  • @TomášLinhart I edited my graphql and python comments a bit. I want to only raise an error if the query is accessing email and is not logged in to the user. Would you mind expanding your comment to an answer? – ruohola Oct 11 '19 at 13:02

3 Answers3

2

Here's the approach I would take based on the comments. The main issue here is to be able to get a list of fields requested by a query in the resolver. For that, I use a code adapted from here:

def get_requested_fields(info):
    """Get list of fields requested in a query."""
    fragments = info.fragments

    def iterate_field_names(prefix, field):
        name = field.name.value
        if isinstance(field, FragmentSpread):
            results = []
            new_prefix = prefix
            sub_selection = fragments[name].selection_set.selections
        else:
            results = [prefix + name]
            new_prefix = prefix + name + '.'
            sub_selection = \
                field.selection_set.selections if field.selection_set else []
        for sub_field in sub_selection:
            results += iterate_field_names(new_prefix, sub_field)
        return results

    results = iterate_field_names('', info.field_asts[0])
    return results

The rest should be quite straightforward:

import graphene
from django.contrib.auth import get_user_model
from graphene_django import DjangoObjectType


class AuthorizationError(Exception):
    """Authorization failed."""


class UserType(DjangoObjectType):
    class Meta:
        model = get_user_model()
        fields = ("id", "username", "email")


class Query(object):
    user = graphene.Field(UserType, user_id=graphene.Int())

    def resolve_user(self, info, user_id):
        user = get_user_model().objects.get(pk=user_id)
        if info.context.user.id != user_id:
            fields = get_requested_fields(info)
            if 'user.email' in fields:
                raise AuthorizationError('Not authorized to access user email')
        return user
Tomáš Linhart
  • 9,832
  • 1
  • 27
  • 39
1

I ended up just doing it like this, where the actual value of email is returned when querying one's own info, and None is returned for others:

import graphene
from django.contrib.auth import get_user_model
from graphene_django import DjangoObjectType


class UserType(DjangoObjectType):
    class Meta:
        model = get_user_model()
        fields = ("id", "username", "email")

    def resolve_email(self, info):
        if info.context.user.is_authenticated and self.pk == info.context.user.pk:
            return self.email
        else:
            return None


class Query(graphene.ObjectType):
    user = graphene.Field(UserType, user_id=graphene.Int())

    def resolve_user(self, info, user_id):
        return get_user_model().objects.get(pk=user_id)
ruohola
  • 21,987
  • 6
  • 62
  • 97
0

The current answer is wayyy overcomplicated. Just create two ObjectTypes e.g.:

class PublicUserType(DjangoObjectType):
    class Meta:
        model = get_user_model()
        fields  = ('id', 'username')

class PrivateUserType(DjangoObjectType):
    class Meta:
        model = get_user_model()

Spent over 4 hours trying other solutions before realized it was this simple

aaon
  • 5
  • 1