Skip to content

Commit

Permalink
[WEB-2460] fix: role permission validation (#5615)
Browse files Browse the repository at this point in the history
* fix: workspace menu quick action

* fix: guest role upgrade flow validation

* fix: create issue validation

* fix: create issue validation

* fix: cmd k permission validation

* fix: subscription validation

* fix: create label permission validation

* fix: build error

* chore: guest can comment in their created issues

* chore: changed the queryset

* chore: code refactor

* chore: code refactor

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
  • Loading branch information
anmolsinghbhatia and NarayanBavisetti authored Sep 16, 2024
1 parent 2e81665 commit 45da70c
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 76 deletions.
24 changes: 23 additions & 1 deletion apiserver/plane/app/views/issue/comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
IssueComment,
ProjectMember,
CommentReaction,
Project,
Issue,
)
from plane.bgtasks.issue_activities_task import issue_activity

Expand Down Expand Up @@ -67,9 +69,27 @@ def get_queryset(self):
[
ROLE.ADMIN,
ROLE.MEMBER,
ROLE.GUEST,
]
)
def create(self, request, slug, project_id, issue_id):
project = Project.objects.get(pk=project_id)
issue = Issue.objects.get(pk=issue_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 issue.created_by == request.user
):
return Response(
{"error": "You are not allowed to comment on the issue"},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = IssueCommentSerializer(data=request.data)
if serializer.is_valid():
serializer.save(
Expand All @@ -94,7 +114,7 @@ def create(self, request, slug, project_id, issue_id):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@allow_permission(
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER],
allowed_roles=[ROLE.ADMIN],
creator=True,
model=IssueComment,
)
Expand Down Expand Up @@ -182,6 +202,7 @@ def get_queryset(self):
[
ROLE.ADMIN,
ROLE.MEMBER,
ROLE.GUEST,
]
)
def create(self, request, slug, project_id, comment_id):
Expand Down Expand Up @@ -210,6 +231,7 @@ def create(self, request, slug, project_id, comment_id):
[
ROLE.ADMIN,
ROLE.MEMBER,
ROLE.GUEST,
]
)
def destroy(self, request, slug, project_id, comment_id, reaction_code):
Expand Down
7 changes: 3 additions & 4 deletions apiserver/plane/app/views/issue/reaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,14 @@
# Module imports
from .. import BaseViewSet
from plane.app.serializers import IssueReactionSerializer
from plane.app.permissions import ProjectLitePermission
from plane.app.permissions import allow_permission, ROLE
from plane.db.models import IssueReaction
from plane.bgtasks.issue_activities_task import issue_activity


class IssueReactionViewSet(BaseViewSet):
serializer_class = IssueReactionSerializer
model = IssueReaction
permission_classes = [
ProjectLitePermission,
]

def get_queryset(self):
return (
Expand All @@ -40,6 +37,7 @@ def get_queryset(self):
.distinct()
)

@allow_permission(ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST)
def create(self, request, slug, project_id, issue_id):
serializer = IssueReactionSerializer(data=request.data)
if serializer.is_valid():
Expand All @@ -62,6 +60,7 @@ def create(self, request, slug, project_id, issue_id):
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@allow_permission(ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST)
def destroy(self, request, slug, project_id, issue_id, reaction_code):
issue_reaction = IssueReaction.objects.get(
workspace__slug=slug,
Expand Down
49 changes: 29 additions & 20 deletions web/core/components/command-palette/command-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { ISSUE_DETAILS } from "@/constants/fetch-keys";
// helpers
import { getTabIndex } from "@/helpers/tab-indices.helper";
// hooks
import { useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store";
import { useCommandPalette, useEventTracker, useProject, useUser, useUserPermissions } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
import useDebounce from "@/hooks/use-debounce";
import { usePlatformOS } from "@/hooks/use-platform-os";
Expand All @@ -41,6 +41,7 @@ import { IssueIdentifier } from "@/plane-web/components/issues";
import { WorkspaceService } from "@/plane-web/services";
// services
import { IssueService } from "@/services/issue";
import { EUserPermissions, EUserPermissionsLevel } from "ee/constants/user-permissions";

const workspaceService = new WorkspaceService();
const issueService = new IssueService();
Expand Down Expand Up @@ -71,6 +72,7 @@ export const CommandModal: React.FC = observer(() => {
const [pages, setPages] = useState<string[]>([]);
const { isCommandPaletteOpen, toggleCommandPaletteModal, toggleCreateIssueModal, toggleCreateProjectModal } =
useCommandPalette();
const { allowPermissions } = useUserPermissions();
const { setTrackElement } = useEventTracker();

// router
Expand All @@ -84,6 +86,11 @@ export const CommandModal: React.FC = observer(() => {

const { baseTabIndex } = getTabIndex(undefined, isMobile);

const canPerformWorkspaceActions = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.WORKSPACE
);

// TODO: update this to mobx store
const { data: issueDetails } = useSWR(
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId.toString()) : null,
Expand Down Expand Up @@ -314,8 +321,7 @@ export const CommandModal: React.FC = observer(() => {
</Command.Item>
</Command.Group>
)}

{workspaceSlug && (
{workspaceSlug && canPerformWorkspaceActions && (
<Command.Group heading="Project">
<Command.Item
onSelect={() => {
Expand All @@ -335,23 +341,26 @@ export const CommandModal: React.FC = observer(() => {
)}

{/* project actions */}
{projectId && <CommandPaletteProjectActions closePalette={closePalette} />}

<Command.Group heading="Workspace Settings">
<Command.Item
onSelect={() => {
setPlaceholder("Search workspace settings...");
setSearchTerm("");
setPages([...pages, "settings"]);
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<Settings className="h-3.5 w-3.5" />
Search settings...
</div>
</Command.Item>
</Command.Group>
{projectId && canPerformAnyCreateAction && (
<CommandPaletteProjectActions closePalette={closePalette} />
)}
{canPerformWorkspaceAction && (
<Command.Group heading="Workspace Settings">
<Command.Item
onSelect={() => {
setPlaceholder("Search workspace settings...");
setSearchTerm("");
setPages([...pages, "settings"]);
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<Settings className="h-3.5 w-3.5" />
Search settings...
</div>
</Command.Item>
</Command.Group>
)}
<Command.Group heading="Account">
<Command.Item onSelect={createNewWorkspace} className="focus:outline-none">
<div className="flex items-center gap-2 text-custom-text-200">
Expand Down
7 changes: 5 additions & 2 deletions web/core/components/issues/issue-detail/label/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { IIssueLabel, TIssue } from "@plane/types";
// components
import { TOAST_TYPE, setToast } from "@plane/ui";
// hooks
import { useIssueDetail, useLabel, useProjectInbox } from "@/hooks/store";
import { useIssueDetail, useLabel, useProjectInbox, useUserPermissions } from "@/hooks/store";
// ui
// types
import { LabelList, LabelCreate, IssueLabelSelectRoot } from "./";
import { EUserPermissions, EUserPermissionsLevel } from "ee/constants/user-permissions";

export type TIssueLabel = {
workspaceSlug: string;
Expand All @@ -34,7 +35,9 @@ export const IssueLabel: FC<TIssueLabel> = observer((props) => {
issue: { getIssueById },
} = useIssueDetail();
const { getIssueInboxByIssueId } = useProjectInbox();
const { allowPermissions } = useUserPermissions();

const canCreateLabel = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
const issue = isInboxIssue ? getIssueInboxByIssueId(issueId)?.issue : getIssueById(issueId);

const labelOperations: TLabelOperations = useMemo(
Expand Down Expand Up @@ -99,7 +102,7 @@ export const IssueLabel: FC<TIssueLabel> = observer((props) => {
/>
)}

{!disabled && (
{!disabled && canCreateLabel && (
<LabelCreate
workspaceSlug={workspaceSlug}
projectId={projectId}
Expand Down
12 changes: 11 additions & 1 deletion web/core/components/issues/issue-detail/subscription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { Bell, BellOff } from "lucide-react";
// UI
import { Button, Loader, TOAST_TYPE, setToast } from "@plane/ui";
// hooks
import { useIssueDetail } from "@/hooks/store";
import { useIssueDetail, useUserPermissions } from "@/hooks/store";
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";

export type TIssueSubscription = {
workspaceSlug: string;
Expand All @@ -25,8 +26,16 @@ export const IssueSubscription: FC<TIssueSubscription> = observer((props) => {
} = useIssueDetail();
// state
const [loading, setLoading] = useState(false);
// hooks
const { allowPermissions } = useUserPermissions();

const isSubscribed = getSubscriptionByIssueId(issueId);
const isEditable = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.PROJECT,
workspaceSlug,
projectId
);

const handleSubscription = async () => {
setLoading(true);
Expand Down Expand Up @@ -64,6 +73,7 @@ export const IssueSubscription: FC<TIssueSubscription> = observer((props) => {
variant="outline-primary"
className="hover:!bg-custom-primary-100/20"
onClick={handleSubscription}
disabled={!isEditable}
>
{loading ? (
<span>
Expand Down
3 changes: 2 additions & 1 deletion web/core/components/project/settings/member-columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ export const AccountTypeColumn: React.FC<AccountTypeProps> = observer((props) =>
// derived values
const isCurrentUser = currentUser?.id === rowData.member.id;
const isAdminOrGuest = [EUserPermissions.ADMIN, EUserPermissions.GUEST].includes(rowData.role);
const isRoleNonEditable = isCurrentUser || isAdminOrGuest;
const userWorkspaceRole = getWorkspaceMemberDetails(rowData.member.id)?.role;
const isRoleNonEditable = isCurrentUser || (isAdminOrGuest && userWorkspaceRole !== EUserPermissions.MEMBER);

const checkCurrentOptionWorkspaceRole = (value: string) => {
const currentMemberWorkspaceRole = getWorkspaceMemberDetails(value)?.role as EUserPermissions | undefined;
Expand Down
12 changes: 9 additions & 3 deletions web/core/components/workspace/sidebar/quick-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import { CreateUpdateIssueModal } from "@/components/issues";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
import { useAppTheme, useCommandPalette, useEventTracker, useProject } from "@/hooks/store";
import { useAppTheme, useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
import useLocalStorage from "@/hooks/use-local-storage";
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";

export const SidebarQuickActions = observer(() => {
// states
Expand All @@ -28,11 +29,16 @@ export const SidebarQuickActions = observer(() => {
const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme();
const { setTrackElement } = useEventTracker();
const { joinedProjectIds } = useProject();
const { allowPermissions } = useUserPermissions();
// local storage
const { storedValue, setValue } = useLocalStorage<Record<string, Partial<TIssue>>>("draftedIssue", {});
// derived values
const disabled = joinedProjectIds.length === 0;
const workspaceDraftIssue = workspaceSlug ? storedValue?.[workspaceSlug] ?? undefined : undefined;
const canCreateIssue = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.WORKSPACE
);
const disabled = joinedProjectIds.length === 0 || !canCreateIssue;
const workspaceDraftIssue = workspaceSlug ? (storedValue?.[workspaceSlug] ?? undefined) : undefined;

const handleMouseEnter = () => {
// if enter before time out clear the timeout
Expand Down
Loading

0 comments on commit 45da70c

Please sign in to comment.