Repository: saleor/saleor

Issue:
Fetch user address by its ID
### What I'm trying to achieve
I need to create a checkout in the saleor using a separate service that uses a service account. To do that I need to provide billing and shipping addresses, both of which I store in the saleor in user's addresses and store only IDs on my side.
There is currently no way to fetch a single address by its ID using a query.

### Describe a proposed solution
Add query:
`address(id: ID!): Address`
that is accessible for at least a service account, preferably by address owner too. 




Discussion:

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
@@ -150,3 +150,14 @@ def prepare_graphql_payment_sources_type(payment_sources):
             }
         )
     return sources
+
+
+def resolve_address(info, id):
+    user = info.context.user
+    service_account = info.context.service_account
+    _model, address_pk = graphene.Node.from_global_id(id)
+    if service_account:
+        return models.Address.objects.filter(pk=address_pk).first()
+    if user and not user.is_anonymous:
+        return user.addresses.filter(id=address_pk).first()
+    return PermissionDenied()
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
@@ -52,6 +52,7 @@
     UserUpdatePrivateMeta,
 )
 from .resolvers import (
+    resolve_address,
     resolve_address_validation_rules,
     resolve_customers,
     resolve_service_accounts,
@@ -59,7 +60,7 @@
     resolve_user,
 )
 from .sorters import ServiceAccountSortingInput, UserSortingInput
-from .types import AddressValidationData, ServiceAccount, User
+from .types import Address, AddressValidationData, ServiceAccount, User
 
 
 class CustomerFilterInput(FilterInputObjectType):
@@ -94,6 +95,13 @@ class AccountQueries(graphene.ObjectType):
             graphene.String, description="Sublocality like a district."
         ),
     )
+    address = graphene.Field(
+        Address,
+        id=graphene.Argument(
+            graphene.ID, description="ID of an address.", required=True
+        ),
+        description="Look up an address by ID.",
+    )
     customers = FilterInputConnectionField(
         User,
         filter=CustomerFilterInput(description="Filtering options for customers."),
@@ -166,6 +174,9 @@ def resolve_staff_users(self, info, query=None, **kwargs):
     def resolve_user(self, info, id):
         return resolve_user(info, id)
 
+    def resolve_address(self, info, id):
+        return resolve_address(info, id)
+
 
 class AccountMutations(graphene.ObjectType):
     # Base mutations


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
@@ -3002,3 +3002,53 @@ def test_change_active_status_for_himself(staff_api_client, permission_manage_us
         data["errors"][0]["message"] == "Cannot activate or deactivate "
         "your own account."
     )
+
+
+ADDRESS_QUERY = """
+query address($id: ID!) {
+    address(id: $id) {
+        postalCode
+        lastName
+        firstName
+        city
+        country {
+          code
+        }
+    }
+}
+"""
+
+
+def test_address_query_as_owner(user_api_client, customer_user):
+    address = customer_user.addresses.first()
+    variables = {"id": graphene.Node.to_global_id("Address", address.pk)}
+    response = user_api_client.post_graphql(ADDRESS_QUERY, variables)
+    content = get_graphql_content(response)
+    data = content["data"]["address"]
+    assert data["country"]["code"] == address.country.code
+
+
+def test_address_query_as_not_owner(
+    user_api_client, customer_user, address_other_country
+):
+    variables = {"id": graphene.Node.to_global_id("Address", address_other_country.pk)}
+    response = user_api_client.post_graphql(ADDRESS_QUERY, variables)
+    content = get_graphql_content(response)
+    data = content["data"]["address"]
+    assert not data
+
+
+def test_address_query_as_service_account(
+    service_account_api_client, service_account, address_other_country
+):
+    variables = {"id": graphene.Node.to_global_id("Address", address_other_country.pk)}
+    response = service_account_api_client.post_graphql(ADDRESS_QUERY, variables)
+    content = get_graphql_content(response)
+    data = content["data"]["address"]
+    assert data["country"]["code"] == address_other_country.country.code
+
+
+def test_address_query_as_anonymous_user(api_client, address_other_country):
+    variables = {"id": graphene.Node.to_global_id("Address", address_other_country.pk)}
+    response = api_client.post_graphql(ADDRESS_QUERY, variables)
+    assert_no_permission(response)