Repository: saleor/saleor

Issue:
Fetching unpublished products by slug
It is possible to fetch unpublished products by slugs. The bug is in the `resolve_product_by_slug` function which operates on all products from the DB, instead of using `visible_to_user` queryset. The same bug may happen in case of other `PublishableModel` types which can be fetched by slug, eg. Collections or Pages. We need to check and fix if needed all of them.

### Steps to reproduce the problem
1. Unpublish an existing product.
2. In API run query `product(slug: "some-slug")` as an anonymous user providing slug of the product from the first step.
3. The product is returned to the user.

### What I expected to happen
Not return an unpublished product.




Discussion:

Resolving patch:
diff --git a/saleor/graphql/product/resolvers.py b/saleor/graphql/product/resolvers.py
--- a/saleor/graphql/product/resolvers.py
+++ b/saleor/graphql/product/resolvers.py
@@ -33,8 +33,11 @@ def resolve_categories(info, level=None, **_kwargs):
     return qs.distinct()
 
 
-def resolve_collection_by_slug(slug):
-    return models.Collection.objects.filter(slug=slug).first()
+def resolve_collection_by_slug(info, slug):
+    requestor = get_user_or_app_from_context(info.context)
+    return (
+        models.Collection.objects.visible_to_user(requestor).filter(slug=slug).first()
+    )
 
 
 def resolve_collections(info, **_kwargs):
@@ -46,8 +49,9 @@ def resolve_digital_contents(info):
     return models.DigitalContent.objects.all()
 
 
-def resolve_product_by_slug(slug):
-    return models.Product.objects.filter(slug=slug).first()
+def resolve_product_by_slug(info, slug):
+    requestor = get_user_or_app_from_context(info.context)
+    return models.Product.objects.visible_to_user(requestor).filter(slug=slug).first()
 
 
 def resolve_products(info, stock_availability=None, **_kwargs):
diff --git a/saleor/graphql/product/schema.py b/saleor/graphql/product/schema.py
--- a/saleor/graphql/product/schema.py
+++ b/saleor/graphql/product/schema.py
@@ -264,7 +264,7 @@ def resolve_collection(self, info, id=None, slug=None):
         if id:
             return graphene.Node.get_node_from_global_id(info, id, Collection)
         if slug:
-            return resolve_collection_by_slug(slug=slug)
+            return resolve_collection_by_slug(info, slug=slug)
 
     def resolve_collections(self, info, **kwargs):
         return resolve_collections(info, **kwargs)
@@ -282,7 +282,7 @@ def resolve_product(self, info, id=None, slug=None):
         if id:
             return graphene.Node.get_node_from_global_id(info, id, Product)
         if slug:
-            return resolve_product_by_slug(slug=slug)
+            return resolve_product_by_slug(info, slug=slug)
 
     def resolve_products(self, info, **kwargs):
         return resolve_products(info, **kwargs)


Test patch:
diff --git a/saleor/graphql/product/tests/test_collection.py b/saleor/graphql/product/tests/test_collection.py
--- a/saleor/graphql/product/tests/test_collection.py
+++ b/saleor/graphql/product/tests/test_collection.py
@@ -47,6 +47,44 @@ def test_collection_query_by_slug(
     assert collection_data["name"] == collection.name
 
 
+def test_collection_query_unpublished_collection_by_slug(
+    user_api_client, collection, permission_manage_products
+):
+    # given
+    user = user_api_client.user
+    user.user_permissions.add(permission_manage_products)
+
+    collection.is_published = False
+    collection.save(update_fields=["is_published"])
+    variables = {"slug": collection.slug}
+
+    # when
+    response = user_api_client.post_graphql(QUERY_COLLECTION, variables=variables)
+
+    # then
+    content = get_graphql_content(response)
+    collection_data = content["data"]["collection"]
+    assert collection_data is not None
+    assert collection_data["name"] == collection.name
+
+
+def test_collection_query_unpublished_collection_by_slug_and_anonympus_user(
+    api_client, collection,
+):
+    # given
+    collection.is_published = False
+    collection.save(update_fields=["is_published"])
+    variables = {"slug": collection.slug}
+
+    # when
+    response = api_client.post_graphql(QUERY_COLLECTION, variables=variables)
+
+    # then
+    content = get_graphql_content(response)
+    collection_data = content["data"]["collection"]
+    assert collection_data is None
+
+
 def test_collection_query_error_when_id_and_slug_provided(
     user_api_client, collection, graphql_log_handler,
 ):
diff --git a/saleor/graphql/product/tests/test_product.py b/saleor/graphql/product/tests/test_product.py
--- a/saleor/graphql/product/tests/test_product.py
+++ b/saleor/graphql/product/tests/test_product.py
@@ -126,9 +126,9 @@ def test_product_query_by_id(
 
     response = user_api_client.post_graphql(QUERY_PRODUCT, variables=variables)
     content = get_graphql_content(response)
-    collection_data = content["data"]["product"]
-    assert collection_data is not None
-    assert collection_data["name"] == product.name
+    product_data = content["data"]["product"]
+    assert product_data is not None
+    assert product_data["name"] == product.name
 
 
 def test_product_query_by_slug(
@@ -137,9 +137,47 @@ def test_product_query_by_slug(
     variables = {"slug": product.slug}
     response = user_api_client.post_graphql(QUERY_PRODUCT, variables=variables)
     content = get_graphql_content(response)
-    collection_data = content["data"]["product"]
-    assert collection_data is not None
-    assert collection_data["name"] == product.name
+    product_data = content["data"]["product"]
+    assert product_data is not None
+    assert product_data["name"] == product.name
+
+
+def test_product_query_unpublished_products_by_slug(
+    user_api_client, product, permission_manage_products
+):
+    # given
+    user = user_api_client.user
+    user.user_permissions.add(permission_manage_products)
+
+    product.is_published = False
+    product.save(update_fields=["is_published"])
+    variables = {"slug": product.slug}
+
+    # when
+    response = user_api_client.post_graphql(QUERY_PRODUCT, variables=variables)
+
+    # then
+    content = get_graphql_content(response)
+    product_data = content["data"]["product"]
+    assert product_data is not None
+    assert product_data["name"] == product.name
+
+
+def test_product_query_unpublished_products_by_slug_and_anonympus_user(
+    api_client, product,
+):
+    # given
+    product.is_published = False
+    product.save(update_fields=["is_published"])
+    variables = {"slug": product.slug}
+
+    # when
+    response = api_client.post_graphql(QUERY_PRODUCT, variables=variables)
+
+    # then
+    content = get_graphql_content(response)
+    product_data = content["data"]["product"]
+    assert product_data is None
 
 
 def test_product_query_error_when_id_and_slug_provided(