Skip to content

Commit

Permalink
API: use restricted serializer for related projects (#11820)
Browse files Browse the repository at this point in the history
  • Loading branch information
stsewd authored Jan 13, 2025
1 parent 1788629 commit 1ab641a
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 10 deletions.
9 changes: 9 additions & 0 deletions docs/user/api/v3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -308,12 +308,21 @@ Project details

* **expand** (*string*) -- Add additional fields in the response.
Allowed values are: ``organization``.
We used to return a full organization object in the response,
due to privacy concerns, now we return only the slug of the organization.
If you need to get the full organization object, you can use the organization endpoint with the slug.

.. note::

The ``single_version`` attribute is deprecated,
use ``versioning_scheme`` instead.

.. note::

Previously, ``translation_of`` and ``subproject_of`` returned a full project object,
due to privacy concerns, now they return only the slug of the project.
If you need to get the full project object, you can use the project endpoint with the slug.

Project create
++++++++++++++

Expand Down
40 changes: 32 additions & 8 deletions readthedocs/api/v3/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,26 @@ def get_admin(self, obj):
return AdminPermission.is_admin(user, obj)


class RelatedProjectSerializer(serializers.ModelSerializer):

"""
Stripped version of the ProjectSerializer to be used when including related projects.
This serializer is used to avoid leaking information about a private project through
a public project. Instead of checking if user has access to the project,
we just show the slug.
"""

_links = ProjectLinksSerializer(source="*")

class Meta:
model = Project
fields = [
"slug",
"_links",
]


class ProjectSerializer(FlexFieldsModelSerializer):

"""
Expand Down Expand Up @@ -766,6 +786,8 @@ class ProjectSerializer(FlexFieldsModelSerializer):
created = serializers.DateTimeField(source="pub_date")
modified = serializers.DateTimeField(source="modified_date")

related_project_serializer = RelatedProjectSerializer

class Meta:
model = Project
fields = [
Expand Down Expand Up @@ -812,7 +834,7 @@ class Meta:
# Users can use the /api/v3/organizations/ endpoint to get more information
# about the organization.
"organization": (
"readthedocs.api.v3.serializers.RestrictedOrganizationSerializer",
"readthedocs.api.v3.serializers.RelatedOrganizationSerializer",
# NOTE: we cannot have a Project with multiple organizations.
{"source": "organizations.first"},
),
Expand Down Expand Up @@ -846,13 +868,16 @@ def get_homepage(self, obj):

def get_translation_of(self, obj):
if obj.main_language_project:
return self.__class__(obj.main_language_project).data
# Since the related project can be private, we use a restricted serializer.
return self.related_project_serializer(obj.main_language_project).data
return None

def get_subproject_of(self, obj):
try:
return self.__class__(obj.superprojects.first().parent).data
except Exception:
return None
parent_relationship = obj.superprojects.first()
if parent_relationship:
# Since the related project can be private, we use a restricted serializer.
return self.related_project_serializer(parent_relationship.parent).data
return None


class SubprojectCreateSerializer(FlexFieldsModelSerializer):
Expand Down Expand Up @@ -1230,7 +1255,7 @@ class Meta:
)


class RestrictedOrganizationSerializer(serializers.ModelSerializer):
class RelatedOrganizationSerializer(serializers.ModelSerializer):

"""
Stripped version of the OrganizationSerializer to be used when listing projects.
Expand All @@ -1244,7 +1269,6 @@ class RestrictedOrganizationSerializer(serializers.ModelSerializer):
class Meta:
model = Organization
fields = (
"name",
"slug",
"_links",
)
Expand Down
4 changes: 2 additions & 2 deletions readthedocs/proxito/tests/test_hosting.py
Original file line number Diff line number Diff line change
Expand Up @@ -843,7 +843,7 @@ def test_number_of_queries_url_subproject(self):
active=True,
)

with self.assertNumQueries(31):
with self.assertNumQueries(26):
r = self.client.get(
reverse("proxito_readthedocs_docs_addons"),
{
Expand All @@ -869,7 +869,7 @@ def test_number_of_queries_url_translations(self):
language=language,
)

with self.assertNumQueries(58):
with self.assertNumQueries(42):
r = self.client.get(
reverse("proxito_readthedocs_docs_addons"),
{
Expand Down
7 changes: 7 additions & 0 deletions readthedocs/proxito/views/hosting.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from readthedocs.api.v3.serializers import (
BuildSerializer,
ProjectSerializer,
RelatedProjectSerializer,
VersionSerializer,
)
from readthedocs.builds.constants import BUILD_STATE_FINISHED, LATEST
Expand Down Expand Up @@ -245,7 +246,13 @@ def __init__(self, *args, **kwargs):
# on El Proxito.
#
# See https://github.com/readthedocs/readthedocs-ops/issues/1323
class RelatedProjectSerializerNoLinks(NoLinksMixin, RelatedProjectSerializer):
pass


class ProjectSerializerNoLinks(NoLinksMixin, ProjectSerializer):
related_project_serializer = RelatedProjectSerializerNoLinks

def __init__(self, *args, **kwargs):
resolver = kwargs.pop("resolver", Resolver())
super().__init__(
Expand Down

0 comments on commit 1ab641a

Please sign in to comment.