Repository: saleor/saleor

Issue:
Add viewer query
With PR #3202 we've added ability to get the data of currently logged in user with the `user` query. I would recommend refactoring it a little bit and introducing a separate query for that. The problem with the `user` query is that it expects the `ID!` argument, but it also accepts passing `""` as ID value to resolve the logged in user. This brakes the single-responsibility rule (which is a good practice for GraphQL queries), is a bit unintuitive and makes to code harder to maintain.

I would propose changing the schema to have the following queries:
`viewer: User` - returns currently authenticated user
`user(id: ID!): User` - resolves a user by ID, where ID is required


Discussion:
@patrys Suggested that we might consider dropping `tokenVerify` and use the new `viewer` query to check the token validity - user would have to send the token to get `viewer` anyway, in case of invalid token this query would fail.
Dashboard 2.0 uses `tokenVerify` in the authentication flow. Before we change that, it would be good to discuss it with @dominik-zeglen and check if this change makes sense in the frontend code as well.
Another name that seems commonly used for such query is `me` - which I also like, simple, short and understandable 🙂 

Resolving patch:
diff --git a/saleor/graphql/account/resolvers.py b/saleor/graphql/account/resolvers.py
--- a/saleor/graphql/account/resolvers.py
+++ b/saleor/graphql/account/resolvers.py
@@ -1,4 +1,3 @@
-import graphene
 import graphene_django_optimizer as gql_optimizer
 from django.db.models import Q
 from i18naddress import get_validation_rules
@@ -6,7 +5,7 @@
 from ...account import models
 from ...core.utils import get_client_ip, get_country_by_ip
 from ..utils import filter_by_query_param
-from .types import AddressValidationData, ChoiceValue, User
+from .types import AddressValidationData, ChoiceValue
 
 USER_SEARCH_FIELDS = (
     'email', 'default_shipping_address__first_name',
@@ -14,16 +13,6 @@
     'default_shipping_address__country')
 
 
-def resolve_user(info, id):
-    logged_user = info.context.user
-    if not id:
-        return logged_user
-    user = graphene.Node.get_node_from_global_id(info, id, User)
-    if logged_user.has_perm('account.manage_users') or user == logged_user:
-        return user
-    return None
-
-
 def resolve_customers(info, query):
     qs = models.User.objects.filter(
         Q(is_staff=False) | (Q(is_staff=True) & Q(orders__isnull=False)))
diff --git a/saleor/graphql/account/schema.py b/saleor/graphql/account/schema.py
--- a/saleor/graphql/account/schema.py
+++ b/saleor/graphql/account/schema.py
@@ -9,8 +9,7 @@
     LoggedUserUpdate, PasswordReset, SetPassword, StaffCreate, StaffDelete,
     StaffUpdate)
 from .resolvers import (
-    resolve_address_validator, resolve_customers, resolve_staff_users,
-    resolve_user)
+    resolve_address_validator, resolve_customers, resolve_staff_users)
 from .types import AddressValidationData, AddressValidationInput, User
 
 
@@ -21,6 +20,8 @@ class AccountQueries(graphene.ObjectType):
     customers = PrefetchingConnectionField(
         User, description='List of the shop\'s customers.',
         query=graphene.String(description=DESCRIPTIONS['user']))
+    me = graphene.Field(
+        User, description='Logged in user data.')
     staff_users = PrefetchingConnectionField(
         User, description='List of the shop\'s staff users.',
         query=graphene.String(description=DESCRIPTIONS['user']))
@@ -35,13 +36,17 @@ def resolve_address_validator(self, info, input):
     def resolve_customers(self, info, query=None, **kwargs):
         return resolve_customers(info, query=query)
 
+    @login_required
+    def resolve_me(self, info):
+        return info.context.user
+
     @permission_required('account.manage_staff')
     def resolve_staff_users(self, info, query=None, **kwargs):
         return resolve_staff_users(info, query=query)
 
-    @login_required
+    @permission_required('account.manage_users')
     def resolve_user(self, info, id):
-        return resolve_user(info, id)
+        return graphene.Node.get_node_from_global_id(info, id, User)
 
 
 class AccountMutations(graphene.ObjectType):
diff --git a/saleor/graphql/account/types.py b/saleor/graphql/account/types.py
--- a/saleor/graphql/account/types.py
+++ b/saleor/graphql/account/types.py
@@ -2,6 +2,7 @@
 import graphene_django_optimizer as gql_optimizer
 from django.contrib.auth import get_user_model
 from graphene import relay
+from graphql_jwt.decorators import permission_required
 
 from ...account import models
 from ...core.permissions import get_permissions
@@ -47,6 +48,7 @@ class User(CountableDjangoObjectType):
         PrefetchingConnectionField(
             Address, description='List of all user\'s addresses.'),
         model_field='addresses')
+    note = graphene.String(description='A note about the customer')
 
     class Meta:
         exclude_fields = ['password', 'is_superuser', 'OrderEvent_set']
@@ -65,6 +67,10 @@ def resolve_permissions(self, info, **kwargs):
     def resolve_addresses(self, info, **kwargs):
         return self.addresses.all()
 
+    @permission_required('account.manage_users')
+    def resolve_note(self, info):
+        return self.note
+
 
 class AddressValidationInput(graphene.InputObjectType):
     country_code = graphene.String()


Test patch:
diff --git a/tests/api/test_account.py b/tests/api/test_account.py
--- a/tests/api/test_account.py
+++ b/tests/api/test_account.py
@@ -152,32 +152,12 @@ def test_query_user(staff_api_client, customer_user, permission_manage_users):
 """
 
 
-def test_customer_can_see_his_own_data(user_api_client):
-    user = user_api_client.user
-    id = graphene.Node.to_global_id('User', user.id)
-    variables = {'id': id}
-    response = user_api_client.post_graphql(USER_QUERY, variables)
-    content = get_graphql_content(response)
-    data = content['data']['user']
-    assert data['email'] == user.email
-
-
 def test_customer_can_not_see_other_users_data(user_api_client,
                                                staff_user):
     id = graphene.Node.to_global_id('User', staff_user.id)
     variables = {'id': id}
     response = user_api_client.post_graphql(USER_QUERY, variables)
-    content = get_graphql_content(response)
-    data = content['data']['user']
-    assert data is None
-
-
-def test_user_query_with_empty_id(user_api_client):
-    variables = {'id': ''}
-    response = user_api_client.post_graphql(USER_QUERY, variables)
-    content = get_graphql_content(response)
-    data = content['data']['user']
-    assert data['email'] == user_api_client.user.email
+    assert_no_permission(response)
 
 
 def test_user_query_anonymous_user(api_client):
@@ -259,10 +239,7 @@ def test_who_can_see_user(
     ID = graphene.Node.to_global_id('User', customer_user.id)
     variables = {'id': ID}
     response = staff_api_client.post_graphql(USER_QUERY, variables)
-    data = get_graphql_content(response)
-    content = get_graphql_content(response)
-    data = content['data']['user']
-    assert data is None
+    assert_no_permission(response)
 
     response = staff_api_client.post_graphql(query)
     assert_no_permission(response)
@@ -278,6 +255,52 @@ def test_who_can_see_user(
     assert content['data']['customers']['totalCount'] == 1
 
 
+ME_QUERY = """
+    query Me {
+        me {
+            id
+            email
+        }
+    }
+"""
+
+
+def test_me_query(user_api_client):
+    response = user_api_client.post_graphql(ME_QUERY)
+    content = get_graphql_content(response)
+    data = content['data']['me']
+    assert data['email'] == user_api_client.user.email
+
+
+def test_me_query_anonymous_client(api_client):
+    response = api_client.post_graphql(ME_QUERY)
+    assert_no_permission(response)
+
+
+def test_me_query_customer_can_not_see_note(
+        staff_user, staff_api_client, permission_manage_users):
+    query = """
+    query Me {
+        me {
+            id
+            email
+            note
+        }
+    }
+    """
+    # Random person (even staff) can't see own note without permissions
+    response = staff_api_client.post_graphql(query)
+    assert_no_permission(response)
+
+    # Add permission and ensure staff can see own note
+    response = staff_api_client.post_graphql(
+        query, permissions=[permission_manage_users])
+    content = get_graphql_content(response)
+    data = content['data']['me']
+    assert data['email'] == staff_api_client.user.email
+    assert data['note'] == staff_api_client.user.note
+
+
 def test_customer_register(user_api_client):
     query = """
         mutation RegisterCustomer($password: String!, $email: String!) {