Repository: pulp/pulpcore

Issue:
accesing /pulp/api/v3/ requires auth since 3.16, while didn't before
**Version**
3.16.3

**Describe the bug**
since 3.16 accessing `/pulp/api/v3/` requires auth, while before that it did not.

I *think* this is a side effect of https://github.com/pulp/pulpcore/pull/1593, just not sure if that was intentional or not.

**To Reproduce**
Steps to reproduce the behavior:
- setup a 3.16.3 isntall
- `curl -k https://localhost/pulp/api/v3/`

**Expected behavior**
Until 3.15, this would return 200, now it returns 403, unless I do provide auth.
Accessing `/pulp/api/v3/status/` still works fine without auth.

**Additional context**
Originally found while working on Puppet integration of 3.16+ in https://github.com/theforeman/puppet-pulpcore/pull/249



Discussion:

Resolving patch:
diff --git a/pulpcore/app/urls.py b/pulpcore/app/urls.py
--- a/pulpcore/app/urls.py
+++ b/pulpcore/app/urls.py
@@ -10,6 +10,7 @@
     SpectacularSwaggerView,
 )
 from rest_framework_nested import routers
+from rest_framework.routers import APIRootView
 
 from pulpcore.app.apps import pulp_plugin_configs
 from pulpcore.app.views import OrphansView, PulpImporterImportCheckView, RepairView, StatusView
@@ -104,6 +105,19 @@ def __repr__(self):
             return str(self.viewset)
 
 
+class PulpAPIRootView(APIRootView):
+    """A Pulp-defined APIRootView class with no authentication requirements."""
+
+    authentication_classes = []
+    permission_classes = []
+
+
+class PulpDefaultRouter(routers.DefaultRouter):
+    """A DefaultRouter class that benefits from the customized PulpAPIRootView class."""
+
+    APIRootView = PulpAPIRootView
+
+
 all_viewsets = []
 plugin_patterns = []
 # Iterate over each app, including pulpcore and the plugins.
@@ -118,9 +132,6 @@ def __repr__(self):
 for viewset in sorted_by_depth:
     vs_tree.add_decendent(ViewSetNode(viewset))
 
-#: The Pulp Platform v3 API router, which can be used to manually register ViewSets with the API.
-root_router = routers.DefaultRouter()
-
 urlpatterns = [
     path(f"{API_ROOT}repair/", RepairView.as_view()),
     path(f"{API_ROOT}status/", StatusView.as_view()),
@@ -184,6 +195,8 @@ def __repr__(self):
     )
 )
 
+#: The Pulp Platform v3 API router, which can be used to manually register ViewSets with the API.
+root_router = PulpDefaultRouter()
 
 all_routers = [root_router] + vs_tree.register_with(root_router)
 for router in all_routers:


Test patch:
diff --git a/pulpcore/tests/functional/__init__.py b/pulpcore/tests/functional/__init__.py
--- a/pulpcore/tests/functional/__init__.py
+++ b/pulpcore/tests/functional/__init__.py
@@ -249,6 +249,11 @@ def pulp_api_v3_path(cli_client):
     return v3_api_root
 
 
+@pytest.fixture(scope="session")
+def pulp_api_v3_url(pulp_cfg, pulp_api_v3_path):
+    return f"{pulp_cfg.get_base_url()}{pulp_api_v3_path}"
+
+
 @pytest.fixture
 def random_artifact(random_artifact_factory):
     return random_artifact_factory()
diff --git a/pulpcore/tests/functional/api/test_root_endpoint.py b/pulpcore/tests/functional/api/test_root_endpoint.py
new file mode 100644
--- /dev/null
+++ b/pulpcore/tests/functional/api/test_root_endpoint.py
@@ -0,0 +1,10 @@
+import asyncio
+import pytest
+
+from pulpcore.tests.functional.utils import get_response
+
+
+@pytest.mark.parallel
+def test_anonymous_access_to_root(pulp_api_v3_url):
+    response = asyncio.run(get_response(pulp_api_v3_url))
+    assert response.ok
diff --git a/pulpcore/tests/functional/utils.py b/pulpcore/tests/functional/utils.py
--- a/pulpcore/tests/functional/utils.py
+++ b/pulpcore/tests/functional/utils.py
@@ -1,4 +1,6 @@
 """Utilities for Pulpcore tests."""
+import aiohttp
+
 from functools import partial
 from unittest import SkipTest
 
@@ -7,6 +9,11 @@
 from pulpcore.client.pulpcore import ApiClient
 
 
+async def get_response(url):
+    async with aiohttp.ClientSession() as session:
+        return await session.get(url)
+
+
 skip_if = partial(selectors.skip_if, exc=SkipTest)  # pylint:disable=invalid-name
 """The ``@skip_if`` decorator, customized for unittest.