Skip to content

Commit

Permalink
Build: don't show listing or detail view if project is spam (#11633)
Browse files Browse the repository at this point in the history
* Build: don't show listing or detail view if project is spam

Check for `is_show_dashboard_denied` when accessing build listing or detail
pages and don't show the dashboard if the project is marked as spam.

I tried to write some test cases for this, but it's hard since we don't have
`readthedocs-ext` in here. I didn't find any test for similar cases either.
We could probably mock most of that code, but I don't think it's worth.
I tested this locally and it works as expected at least.

Closes readthedocs/readthedocs-ext#554

* Tests for dashboard views on spam projects
  • Loading branch information
humitos authored Oct 3, 2024
1 parent 698f6f0 commit 23780b9
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 5 deletions.
32 changes: 32 additions & 0 deletions readthedocs/builds/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,35 @@ def _get_project(self, owners, **kwargs):
@override_settings(RTD_ALLOW_ORGANIZATIONS=True)
class CancelBuildViewWithOrganizationsTests(CancelBuildViewTests):
pass


class BuildViewsTests(TestCase):
def setUp(self):
self.user = get(User, username="test")
self.project = get(Project, users=[self.user])
self.version = get(Version, project=self.project)
self.build = get(
Build,
project=self.project,
version=self.version,
task_id="1234",
state=BUILD_STATE_INSTALLING,
)

@mock.patch(
"readthedocs.builds.views.BuildList.is_show_dashboard_denied_wrapper",
mock.MagicMock(return_value=True),
)
def test_builds_list_view_spam_project(self):
url = reverse("builds_project_list", args=[self.project.slug])
response = self.client.get(url)
self.assertEqual(response.status_code, 410)

@mock.patch(
"readthedocs.builds.views.BuildDetail.is_show_dashboard_denied_wrapper",
mock.MagicMock(return_value=True),
)
def test_builds_detail_view_spam_project(self):
url = reverse("builds_detail", args=[self.project.slug, self.build.pk])
response = self.client.get(url)
self.assertEqual(response.status_code, 410)
19 changes: 17 additions & 2 deletions readthedocs/builds/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from readthedocs.core.utils import cancel_build, trigger_build
from readthedocs.doc_builder.exceptions import BuildAppError
from readthedocs.projects.models import Project
from readthedocs.projects.views.base import ProjectSpamMixin

log = structlog.get_logger(__name__)

Expand Down Expand Up @@ -124,9 +125,20 @@ def _get_versions(self, project):
)


class BuildList(FilterContextMixin, BuildBase, BuildTriggerMixin, ListView):
class BuildList(
FilterContextMixin,
ProjectSpamMixin,
BuildBase,
BuildTriggerMixin,
ListView,
):
filterset_class = BuildListFilter

def get_project(self):
# Call ``.get_queryset()`` to get the current project from ``kwargs``
self.get_queryset()
return self.project

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)

Expand Down Expand Up @@ -154,9 +166,12 @@ def get_context_data(self, **kwargs):
return context


class BuildDetail(BuildBase, DetailView):
class BuildDetail(BuildBase, ProjectSpamMixin, DetailView):
pk_url_kwarg = "build_pk"

def get_project(self):
return self.get_object().project

@method_decorator(login_required)
def post(self, request, project_slug, build_pk):
project = get_object_or_404(Project, slug=project_slug)
Expand Down
17 changes: 14 additions & 3 deletions readthedocs/projects/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,25 @@ class ProjectSpamMixin:
project's dashboard is denied.
"""

def get(self, request, *args, **kwargs):
def is_show_dashboard_denied_wrapper(self):
"""
Determine if the project has reached dashboard denied treshold.
This function is wrapped just for testing purposes,
so we are able to mock it from outside.
"""
if "readthedocsext.spamfighting" in settings.INSTALLED_APPS:
from readthedocsext.spamfighting.utils import ( # noqa
is_show_dashboard_denied,
)

if is_show_dashboard_denied(self.get_project()):
template_name = "errors/dashboard/spam.html"
return render(request, template_name=template_name, status=410)
return True
return False

def get(self, request, *args, **kwargs):
if self.is_show_dashboard_denied_wrapper():
template_name = "errors/dashboard/spam.html"
return render(request, template_name=template_name, status=410)

return super().get(request, *args, **kwargs)
9 changes: 9 additions & 0 deletions readthedocs/rtd_tests/tests/test_project_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,15 @@ def test_project_versions_only_shows_internal_versons(self):
self.assertNotIn(self.external_version, response.context["active_versions"])
self.assertNotIn(self.external_version, response.context["inactive_versions"])

@mock.patch(
"readthedocs.projects.views.base.ProjectSpamMixin.is_show_dashboard_denied_wrapper",
mock.MagicMock(return_value=True),
)
def test_project_detail_view_spam_project(self):
url = reverse("projects_detail", args=[self.pip.slug])
response = self.client.get(url)
self.assertEqual(response.status_code, 410)


@mock.patch("readthedocs.core.utils.trigger_build", mock.MagicMock())
class TestPrivateViews(TestCase):
Expand Down

0 comments on commit 23780b9

Please sign in to comment.