Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WEB-2460] fix: role permission validation #5615

Merged
merged 15 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from 11 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
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
6 changes: 3 additions & 3 deletions web/app/[workspaceSlug]/(projects)/settings/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import { FC, ReactNode } from "react";
import { observer } from "mobx-react";
// components
import { NotAuthorizedView } from "@/components/auth-screens";
import { AppHeader } from "@/components/core";
import { useUserPermissions } from "@/hooks/store";
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
// local components
import { WorkspaceSettingHeader } from "./header";
import { MobileWorkspaceSettingsTabs } from "./mobile-header-tabs";
import { WorkspaceSettingsSidebar } from "./sidebar";
import { NotAuthorizedView } from "@/components/auth-screens";
import { useUserPermissions } from "@/hooks/store";
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";

export interface IWorkspaceSettingLayout {
children: ReactNode;
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 canPerformWorkspaceAction = allowPermissions(
anmolsinghbhatia marked this conversation as resolved.
Show resolved Hide resolved
[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 && canPerformWorkspaceAction && (
<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
Loading