diff --git a/apiserver/plane/app/views/inbox/base.py b/apiserver/plane/app/views/inbox/base.py index d830a622f64..ade3f0e5df1 100644 --- a/apiserver/plane/app/views/inbox/base.py +++ b/apiserver/plane/app/views/inbox/base.py @@ -170,6 +170,7 @@ def list(self, request, slug, project_id): inbox_id = Inbox.objects.get( workspace__slug=slug, project_id=project_id ) + project = Project.objects.get(pk=project_id) filters = issue_filters(request.GET, "GET", "issue__") inbox_issue = ( InboxIssue.objects.filter( @@ -199,13 +200,16 @@ def list(self, request, slug, project_id): if inbox_status: inbox_issue = inbox_issue.filter(status__in=inbox_status) - if ProjectMember.objects.filter( - workspace__slug=slug, - project_id=project_id, - member=request.user, - role=5, - is_active=True, - ).exists(): + if ( + ProjectMember.objects.filter( + workspace__slug=slug, + project_id=project_id, + member=request.user, + role=5, + is_active=True, + ).exists() + and not project.guest_view_all_features + ): inbox_issue = inbox_issue.filter(created_by=request.user) return self.paginate( request=request, @@ -517,6 +521,7 @@ def partial_update(self, request, slug, project_id, pk): allowed_roles=[ ROLE.ADMIN, ROLE.MEMBER, + ROLE.GUEST, ], creator=True, model=Issue, @@ -525,6 +530,7 @@ def retrieve(self, request, slug, project_id, pk): inbox_id = Inbox.objects.get( workspace__slug=slug, project_id=project_id ) + project = Project.objects.get(pk=project_id) inbox_issue = ( InboxIssue.objects.select_related("issue") .prefetch_related( @@ -551,6 +557,21 @@ def retrieve(self, request, slug, project_id, pk): ) .get(inbox_id=inbox_id.id, issue_id=pk, project_id=project_id) ) + if ( + ProjectMember.objects.filter( + workspace__slug=slug, + project_id=project_id, + member=request.user, + role=5, + is_active=True, + ).exists() + and not project.guest_view_all_features + and not inbox_issue.created_by == request.user + ): + return Response( + {"error": "You are not allowed to view this issue"}, + status=status.HTTP_400_BAD_REQUEST, + ) issue = InboxIssueDetailSerializer(inbox_issue).data return Response( issue, diff --git a/apiserver/plane/app/views/project/invite.py b/apiserver/plane/app/views/project/invite.py index 4cb5f433326..7e31a332580 100644 --- a/apiserver/plane/app/views/project/invite.py +++ b/apiserver/plane/app/views/project/invite.py @@ -17,7 +17,7 @@ from .base import BaseViewSet, BaseAPIView from plane.app.serializers import ProjectMemberInviteSerializer -from plane.app.permissions import ProjectBasePermission +from plane.app.permissions import allow_permission, ROLE from plane.db.models import ( ProjectMember, @@ -35,10 +35,6 @@ class ProjectInvitationsViewset(BaseViewSet): search_fields = [] - permission_classes = [ - ProjectBasePermission, - ] - def get_queryset(self): return self.filter_queryset( super() @@ -49,6 +45,7 @@ def get_queryset(self): .select_related("workspace", "workspace__owner") ) + @allow_permission([ROLE.ADMIN]) def create(self, request, slug, project_id): emails = request.data.get("emails", []) @@ -59,24 +56,21 @@ def create(self, request, slug, project_id): status=status.HTTP_400_BAD_REQUEST, ) - requesting_user = ProjectMember.objects.get( - workspace__slug=slug, - project_id=project_id, - member_id=request.user.id, - ) - - # Check if any invited user has an higher role - if len( - [ - email - for email in emails - if int(email.get("role", 5)) > requesting_user.role - ] - ): - return Response( - {"error": "You cannot invite a user with higher role"}, - status=status.HTTP_400_BAD_REQUEST, - ) + for email in emails: + workspace_role = WorkspaceMember.objects.filter( + workspace__slug=slug, + member__email=email.get("email"), + is_active=True, + ).role + + if workspace_role in [5, 20] and workspace_role != email.get( + "role", 5 + ): + return Response( + { + "error": "You cannot invite a user with different role than workspace role" + }, + ) workspace = Workspace.objects.get(slug=slug) diff --git a/web/core/components/inbox/content/root.tsx b/web/core/components/inbox/content/root.tsx index cf6cbd7a164..b0d779f3f7d 100644 --- a/web/core/components/inbox/content/root.tsx +++ b/web/core/components/inbox/content/root.tsx @@ -5,7 +5,7 @@ import useSWR from "swr"; import { ContentWrapper } from "@plane/ui"; import { InboxIssueActionsHeader, InboxIssueMainContent } from "@/components/inbox"; // hooks -import { useProjectInbox, useUserPermissions } from "@/hooks/store"; +import { useProjectInbox, useUser, useUserPermissions } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; @@ -34,9 +34,10 @@ export const InboxContentRoot: FC = observer((props) => { // states const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved"); // hooks + const { data: currentUser } = useUser(); const { currentTab, fetchInboxIssueById, getIssueInboxByIssueId, getIsIssueAvailable } = useProjectInbox(); const inboxIssue = getIssueInboxByIssueId(inboxIssueId); - const { allowPermissions } = useUserPermissions(); + const { allowPermissions, projectPermissionsByWorkspaceSlugAndProjectId } = useUserPermissions(); // derived values const isIssueAvailable = getIsIssueAvailable(inboxIssueId?.toString() || ""); @@ -61,7 +62,13 @@ export const InboxContentRoot: FC = observer((props) => { } ); - const isEditable = allowPermissions([EUserPermissions.ADMIN, EUserPermissions.MEMBER], EUserPermissionsLevel.PROJECT); + const isEditable = allowPermissions( + [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], + EUserPermissionsLevel.PROJECT + ); + const isGuest = projectPermissionsByWorkspaceSlugAndProjectId(workspaceSlug, projectId) === EUserPermissions.GUEST; + const isOwner = inboxIssue.issue.created_by === currentUser?.id; + const readOnly = !isOwner && isGuest; if (!inboxIssue) return <>; @@ -87,7 +94,7 @@ export const InboxContentRoot: FC = observer((props) => { workspaceSlug={workspaceSlug} projectId={projectId} inboxIssue={inboxIssue} - isEditable={isEditable && !isIssueDisabled} + isEditable={isEditable && !isIssueDisabled && !readOnly} isSubmitting={isSubmitting} setIsSubmitting={setIsSubmitting} /> diff --git a/web/core/components/project/send-project-invitation-modal.tsx b/web/core/components/project/send-project-invitation-modal.tsx index 4105ef8b08d..c3b2fc17254 100644 --- a/web/core/components/project/send-project-invitation-modal.tsx +++ b/web/core/components/project/send-project-invitation-modal.tsx @@ -173,10 +173,10 @@ export const SendProjectInvitationModal: React.FC = observer((props) => { const currentMemberWorkspaceRole = getWorkspaceMemberDetails(value)?.role; if (!value || !currentMemberWorkspaceRole) return ROLE; - const isGuest = [EUserPermissions.GUEST].includes(currentMemberWorkspaceRole); + const isGuestOROwner = [EUserPermissions.ADMIN, EUserPermissions.GUEST].includes(currentMemberWorkspaceRole); return Object.fromEntries( - Object.entries(ROLE).filter(([key]) => !isGuest || [5].includes(parseInt(key))) + Object.entries(ROLE).filter(([key]) => !isGuestOROwner || [currentMemberWorkspaceRole].includes(parseInt(key))) ); };