diff --git a/taiga/base/filters.py b/taiga/base/filters.py index 2533f0b8..b90740b1 100644 --- a/taiga/base/filters.py +++ b/taiga/base/filters.py @@ -32,6 +32,30 @@ from taiga.base.utils.db import to_tsquery logger = logging.getLogger(__name__) +def get_filter_expression_can_view_projects(user, project_id=None): + # Filter by user permissions + if user.is_authenticated() and user.is_superuser: + return Q() + elif user.is_authenticated(): + # authenticated user & project member + membership_model = apps.get_model("projects", "Membership") + memberships_qs = membership_model.objects.filter(user=user) + if project_id: + memberships_qs = memberships_qs.filter(project_id=project_id) + memberships_qs = memberships_qs.filter( + Q(role__permissions__contains=['view_project']) | + Q(is_admin=True)) + + projects_list = [membership.project_id for membership in + memberships_qs] + + return (Q(id__in=projects_list) | + Q(public_permissions__contains=["view_project"])) + else: + # external users / anonymous + return Q(anon_permissions__contains=["view_project"]) + + ##################################################################### # Base and Mixins ##################################################################### diff --git a/taiga/projects/api.py b/taiga/projects/api.py index b1c98f75..a8349551 100644 --- a/taiga/projects/api.py +++ b/taiga/projects/api.py @@ -136,10 +136,25 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin, return qs def retrieve(self, request, *args, **kwargs): + qs = self.get_queryset() if self.action == "by_slug": self.lookup_field = "slug" + # If we retrieve the project by slug we want to filter by user the + # permissions and return 404 in case the user don't have access + flt = filters.get_filter_expression_can_view_projects( + self.request.user) - return super().retrieve(request, *args, **kwargs) + qs = qs.filter(flt) + + self.object = get_object_or_404(qs, **kwargs) + + self.check_permissions(request, 'retrieve', self.object) + + if self.object is None: + raise Http404 + + serializer = self.get_serializer(self.object) + return response.Ok(serializer.data) def get_serializer_class(self): if self.action == "list": diff --git a/taiga/projects/filters.py b/taiga/projects/filters.py index 4dacd29f..679ff67c 100644 --- a/taiga/projects/filters.py +++ b/taiga/projects/filters.py @@ -23,6 +23,7 @@ from django.utils.translation import ugettext as _ from taiga.base import exceptions as exc from taiga.base.filters import FilterBackend +from taiga.base.filters import get_filter_expression_can_view_projects from taiga.base.utils.db import to_tsquery logger = logging.getLogger(__name__) @@ -63,28 +64,11 @@ class CanViewProjectObjFilterBackend(FilterBackend): )) raise exc.BadRequest(_("'project' must be an integer value.")) - qs = queryset + filter_expression = get_filter_expression_can_view_projects( + request.user, + project_id) - # Filter by user permissions - if request.user.is_authenticated() and request.user.is_superuser: - # superuser - qs = qs - elif request.user.is_authenticated(): - # authenticated user & project member - membership_model = apps.get_model("projects", "Membership") - memberships_qs = membership_model.objects.filter(user=request.user) - if project_id: - memberships_qs = memberships_qs.filter(project_id=project_id) - memberships_qs = memberships_qs.filter(Q(role__permissions__contains=['view_project']) | - Q(is_admin=True)) - - projects_list = [membership.project_id for membership in memberships_qs] - - qs = qs.filter((Q(id__in=projects_list) | - Q(public_permissions__contains=["view_project"]))) - else: - # external users / anonymous - qs = qs.filter(anon_permissions__contains=["view_project"]) + qs = queryset.filter(filter_expression) return super().filter_queryset(request, qs, view) diff --git a/tests/integration/test_projects.py b/tests/integration/test_projects.py index 8fbe6e03..3f7016b0 100644 --- a/tests/integration/test_projects.py +++ b/tests/integration/test_projects.py @@ -72,6 +72,21 @@ def test_get_project_by_slug(client): assert response.status_code == 404 +def test_get_private_project_by_slug(client): + project = f.create_project(is_private=True) + f.MembershipFactory(user=project.owner, project=project, is_admin=True) + + url = reverse("projects-by-slug") + + response = client.json.get(url, {"slug": project.slug}) + + assert response.status_code == 404 + + client.login(project.owner) + response = client.json.get(url, {"slug": project.slug}) + assert response.status_code == 200 + + def test_create_project(client): user = f.create_user() url = reverse("projects-list")