Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/api/plane/app/views/project/invite.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ def get_queryset(self):
.select_related("workspace", "workspace__owner")
)

@allow_permission([ROLE.ADMIN])
def list(self, request, slug, project_id):
return super().list(request, slug=slug, project_id=project_id)

@allow_permission([ROLE.ADMIN])
def retrieve(self, request, slug, project_id, pk):
return super().retrieve(request, slug=slug, project_id=project_id, pk=pk)

@allow_permission([ROLE.ADMIN])
def create(self, request, slug, project_id):
emails = request.data.get("emails", [])
Expand Down Expand Up @@ -112,6 +120,10 @@ def create(self, request, slug, project_id):

return Response({"message": "Email sent successfully"}, status=status.HTTP_200_OK)

@allow_permission([ROLE.ADMIN])
def destroy(self, request, slug, project_id, pk):
return super().destroy(request, slug=slug, project_id=project_id, pk=pk)


class UserProjectInvitationsViewset(BaseViewSet):
serializer_class = ProjectMemberInviteSerializer
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Copyright (c) 2023-present Plane Software, Inc. and contributors
# SPDX-License-Identifier: AGPL-3.0-only
# See the LICENSE file for details.

import pytest
from rest_framework import status

from plane.db.models import (
Project,
ProjectMember,
ProjectMemberInvite,
User,
Workspace,
WorkspaceMember,
)


@pytest.mark.contract
class TestProjectInvitationAPI:
@pytest.mark.django_db
def test_foreign_workspace_user_cannot_read_project_invitations(self, session_client, workspace, create_user):
project = Project.objects.create(name="Invite Protected", identifier="IP", workspace=workspace)
ProjectMember.objects.create(project=project, member=create_user, role=20, is_active=True)
invitation = ProjectMemberInvite.objects.create(
project=project,
workspace=workspace,
email="invitee@example.com",
token="secret-project-invite-token",
role=15,
created_by=create_user,
)

foreign_user = User.objects.create_user(email="foreign@example.com", username="foreign")
foreign_workspace = Workspace.objects.create(name="Foreign Workspace", slug="foreign-workspace", owner=foreign_user)
WorkspaceMember.objects.create(workspace=foreign_workspace, member=foreign_user, role=15, is_active=True)

session_client.force_authenticate(user=foreign_user)

list_url = f"/api/workspaces/{workspace.slug}/projects/{project.id}/invitations/"
detail_url = f"{list_url}{invitation.id}/"

list_response = session_client.get(list_url)
detail_response = session_client.get(detail_url)

assert list_response.status_code == status.HTTP_403_FORBIDDEN
assert detail_response.status_code == status.HTTP_403_FORBIDDEN
assert b"secret-project-invite-token" not in list_response.content
assert b"secret-project-invite-token" not in detail_response.content