SECURITY - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: ограничение создания проектов
This commit is contained in:
parent
87e1857f53
commit
5e7c9e08a0
|
|
@ -34,7 +34,7 @@ class ProjectBasePermission(BasePermission):
|
|||
return WorkspaceMember.objects.filter(
|
||||
workspace__slug=view.workspace_slug,
|
||||
member=request.user,
|
||||
role__in=[ROLE.ADMIN.value, ROLE.MEMBER.value],
|
||||
role=ROLE.ADMIN.value,
|
||||
is_active=True,
|
||||
).exists()
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ class ProjectMemberPermission(BasePermission):
|
|||
return WorkspaceMember.objects.filter(
|
||||
workspace__slug=view.workspace_slug,
|
||||
member=request.user,
|
||||
role__in=[ROLE.ADMIN.value, ROLE.MEMBER.value],
|
||||
role=ROLE.ADMIN.value,
|
||||
is_active=True,
|
||||
).exists()
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ from plane.app.serializers import (
|
|||
from plane.app.views.base import BaseAPIView, BaseViewSet
|
||||
from plane.bgtasks.recent_visited_task import recent_visited_task
|
||||
from plane.bgtasks.webhook_task import model_activity, webhook_activity
|
||||
from plane.authentication.nodedc_project_memberships import (
|
||||
ensure_project_admin_membership,
|
||||
ensure_workspace_admin_project_memberships,
|
||||
)
|
||||
from plane.db.models import (
|
||||
UserFavorite,
|
||||
DeployBoard,
|
||||
|
|
@ -49,6 +53,20 @@ class ProjectViewSet(BaseViewSet):
|
|||
webhook_event = "project"
|
||||
use_read_replica = True
|
||||
|
||||
def ensure_workspace_admin_project_access(self, request, slug):
|
||||
workspace_member = (
|
||||
WorkspaceMember.objects.filter(
|
||||
member=request.user,
|
||||
workspace__slug=slug,
|
||||
is_active=True,
|
||||
role=ROLE.ADMIN.value,
|
||||
)
|
||||
.select_related("workspace")
|
||||
.first()
|
||||
)
|
||||
if workspace_member is not None:
|
||||
ensure_workspace_admin_project_memberships(workspace_member.workspace)
|
||||
|
||||
def get_queryset(self):
|
||||
sort_order = ProjectUserProperty.objects.filter(
|
||||
user=self.request.user,
|
||||
|
|
@ -99,6 +117,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def list_detail(self, request, slug):
|
||||
self.ensure_workspace_admin_project_access(request, slug)
|
||||
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
||||
projects = self.get_queryset().order_by("sort_order", "name")
|
||||
if WorkspaceMember.objects.filter(
|
||||
|
|
@ -119,12 +138,9 @@ class ProjectViewSet(BaseViewSet):
|
|||
role=ROLE.MEMBER.value,
|
||||
).exists():
|
||||
projects = projects.filter(
|
||||
Q(
|
||||
project_projectmember__member=self.request.user,
|
||||
project_projectmember__is_active=True,
|
||||
)
|
||||
| Q(network=2)
|
||||
)
|
||||
|
||||
if request.GET.get("per_page", False) and request.GET.get("cursor", False):
|
||||
return self.paginate(
|
||||
|
|
@ -139,6 +155,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def list(self, request, slug):
|
||||
self.ensure_workspace_admin_project_access(request, slug)
|
||||
sort_order = ProjectUserProperty.objects.filter(
|
||||
user=self.request.user,
|
||||
project_id=OuterRef("pk"),
|
||||
|
|
@ -209,12 +226,9 @@ class ProjectViewSet(BaseViewSet):
|
|||
role=ROLE.MEMBER.value,
|
||||
).exists():
|
||||
projects = projects.filter(
|
||||
Q(
|
||||
project_projectmember__member=self.request.user,
|
||||
project_projectmember__is_active=True,
|
||||
)
|
||||
| Q(network=2)
|
||||
)
|
||||
return Response(projects, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
|
|
@ -227,7 +241,14 @@ class ProjectViewSet(BaseViewSet):
|
|||
member_ids = [str(project_member.member_id) for project_member in project.members_list]
|
||||
|
||||
if str(request.user.id) not in member_ids:
|
||||
if project.network == ProjectNetwork.SECRET.value:
|
||||
if WorkspaceMember.objects.filter(
|
||||
member=request.user,
|
||||
workspace__slug=slug,
|
||||
is_active=True,
|
||||
role=ROLE.ADMIN.value,
|
||||
).exists():
|
||||
ensure_project_admin_membership(project, request.user)
|
||||
elif project.network == ProjectNetwork.SECRET.value:
|
||||
return Response(
|
||||
{"error": "You do not have permission"},
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
|
|
@ -249,7 +270,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
serializer = ProjectListSerializer(project)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
@allow_permission([ROLE.ADMIN], level="WORKSPACE")
|
||||
def create(self, request, slug):
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
|
||||
|
|
@ -290,6 +311,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
project = self.get_queryset().filter(pk=serializer.data["id"]).first()
|
||||
ensure_workspace_admin_project_memberships(workspace, project=project)
|
||||
|
||||
# Create the model activity
|
||||
model_activity.delay(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ from django.db.models import Min
|
|||
from .base import BaseViewSet, BaseAPIView
|
||||
from plane.app.realtime.issue_events import publish_assignee_cleanup_issue_events_on_commit
|
||||
from plane.app.realtime.nodedc_events import publish_nodedc_workspace_event_on_commit
|
||||
from plane.authentication.nodedc_project_memberships import (
|
||||
ensure_project_admin_membership,
|
||||
ensure_workspace_admin_project_memberships,
|
||||
)
|
||||
from plane.authentication.nodedc_workspace_policy import (
|
||||
is_nodedc_launcher_managed_workspace,
|
||||
nodedc_launcher_managed_workspace_response,
|
||||
|
|
@ -431,12 +435,32 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
|
||||
class ProjectMemberUserEndpoint(BaseAPIView):
|
||||
def get(self, request, slug, project_id):
|
||||
project_member = ProjectMember.objects.get(
|
||||
project_member = ProjectMember.objects.filter(
|
||||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
)
|
||||
).first()
|
||||
|
||||
if project_member is None:
|
||||
project = Project.objects.filter(pk=project_id, workspace__slug=slug).select_related("workspace").first()
|
||||
is_workspace_admin = WorkspaceMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
role=ROLE.ADMIN.value,
|
||||
).exists()
|
||||
if project is not None and is_workspace_admin:
|
||||
ensure_project_admin_membership(project, request.user)
|
||||
project_member = ProjectMember.objects.filter(
|
||||
project=project,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
).first()
|
||||
|
||||
if project_member is None:
|
||||
return Response({"error": "Project member not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
serializer = ProjectMemberSerializer(project_member)
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
|
@ -447,6 +471,19 @@ class UserProjectRolesEndpoint(BaseAPIView):
|
|||
use_read_replica = True
|
||||
|
||||
def get(self, request, slug):
|
||||
workspace_member = (
|
||||
WorkspaceMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
)
|
||||
.select_related("workspace")
|
||||
.first()
|
||||
)
|
||||
|
||||
if workspace_member is not None and workspace_member.role == ROLE.ADMIN.value:
|
||||
ensure_workspace_admin_project_memberships(workspace_member.workspace)
|
||||
|
||||
project_members = ProjectMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
member_id=request.user.id,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
ADMIN_ROLE = 20
|
||||
AUTO_ADMIN_COMMENT = "nodedc:workspace-admin"
|
||||
AUTO_ADMIN_CREATED_COMMENT = f"{AUTO_ADMIN_COMMENT}:created"
|
||||
AUTO_ADMIN_PREVIOUS_PREFIX = f"{AUTO_ADMIN_COMMENT}:previous:"
|
||||
|
||||
|
||||
def get_auto_admin_previous_role(comment):
|
||||
if not isinstance(comment, str) or not comment.startswith(AUTO_ADMIN_PREVIOUS_PREFIX):
|
||||
return None
|
||||
|
||||
try:
|
||||
previous_role = int(comment.replace(AUTO_ADMIN_PREVIOUS_PREFIX, "", 1))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
return previous_role if previous_role in {5, 15, ADMIN_ROLE} else None
|
||||
|
||||
|
||||
def ensure_project_admin_membership(project, user):
|
||||
from plane.db.models import ProjectMember
|
||||
|
||||
project_member = ProjectMember.objects.filter(
|
||||
project=project,
|
||||
member=user,
|
||||
deleted_at__isnull=True,
|
||||
).first()
|
||||
|
||||
if project_member is None:
|
||||
ProjectMember.objects.create(
|
||||
workspace=project.workspace,
|
||||
project=project,
|
||||
member=user,
|
||||
role=ADMIN_ROLE,
|
||||
is_active=True,
|
||||
comment=AUTO_ADMIN_CREATED_COMMENT,
|
||||
)
|
||||
return 1
|
||||
|
||||
update_fields = []
|
||||
if project_member.role != ADMIN_ROLE:
|
||||
project_member.comment = f"{AUTO_ADMIN_PREVIOUS_PREFIX}{project_member.role}"
|
||||
project_member.role = ADMIN_ROLE
|
||||
update_fields.extend(["comment", "role"])
|
||||
if not project_member.is_active:
|
||||
project_member.is_active = True
|
||||
update_fields.append("is_active")
|
||||
|
||||
if update_fields:
|
||||
update_fields.append("updated_at")
|
||||
project_member.save(update_fields=update_fields)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def revoke_auto_project_admin_memberships(workspace, user):
|
||||
from plane.db.models import ProjectMember
|
||||
|
||||
revoked = 0
|
||||
project_memberships = ProjectMember.objects.filter(
|
||||
project__workspace=workspace,
|
||||
member=user,
|
||||
role=ADMIN_ROLE,
|
||||
deleted_at__isnull=True,
|
||||
comment__startswith=AUTO_ADMIN_COMMENT,
|
||||
)
|
||||
|
||||
for project_member in project_memberships:
|
||||
previous_role = get_auto_admin_previous_role(project_member.comment)
|
||||
if project_member.comment == AUTO_ADMIN_CREATED_COMMENT or previous_role is None:
|
||||
project_member.is_active = False
|
||||
project_member.save(update_fields=["is_active", "updated_at"])
|
||||
else:
|
||||
project_member.role = previous_role
|
||||
project_member.comment = None
|
||||
project_member.is_active = True
|
||||
project_member.save(update_fields=["role", "comment", "is_active", "updated_at"])
|
||||
revoked += 1
|
||||
|
||||
return revoked
|
||||
|
||||
|
||||
def ensure_user_admin_project_memberships(workspace, user):
|
||||
from plane.db.models import Project
|
||||
|
||||
restored = 0
|
||||
for project in Project.objects.filter(workspace=workspace, deleted_at__isnull=True).select_related("workspace"):
|
||||
restored += ensure_project_admin_membership(project, user)
|
||||
return restored
|
||||
|
||||
|
||||
def ensure_workspace_admin_project_memberships(workspace, project=None):
|
||||
from plane.db.models import WorkspaceMember
|
||||
|
||||
admin_memberships = (
|
||||
WorkspaceMember.objects.filter(
|
||||
workspace=workspace,
|
||||
role=ADMIN_ROLE,
|
||||
is_active=True,
|
||||
deleted_at__isnull=True,
|
||||
member__is_bot=False,
|
||||
)
|
||||
.select_related("member")
|
||||
.order_by("created_at")
|
||||
)
|
||||
|
||||
restored = 0
|
||||
for workspace_member in admin_memberships:
|
||||
if project is not None:
|
||||
restored += ensure_project_admin_membership(project, workspace_member.member)
|
||||
else:
|
||||
restored += ensure_user_admin_project_memberships(workspace, workspace_member.member)
|
||||
return restored
|
||||
|
|
@ -9,6 +9,10 @@ from django.utils.decorators import method_decorator
|
|||
from django.views import View
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from plane.authentication.nodedc_project_memberships import (
|
||||
ensure_user_admin_project_memberships,
|
||||
revoke_auto_project_admin_memberships,
|
||||
)
|
||||
from plane.authentication.views.nodedc_logout import is_internal_logout_request_authorized
|
||||
from plane.app.realtime.issue_events import publish_assignee_cleanup_issue_events_on_commit
|
||||
from plane.app.realtime.nodedc_events import (
|
||||
|
|
@ -332,24 +336,7 @@ def serialize_project_membership(project_member, created):
|
|||
|
||||
|
||||
def restore_admin_project_memberships(workspace, user):
|
||||
restored = 0
|
||||
for project_member in ProjectMember.objects.filter(
|
||||
project__workspace=workspace,
|
||||
member=user,
|
||||
deleted_at__isnull=True,
|
||||
):
|
||||
update_fields = []
|
||||
if project_member.role != ADMIN_ROLE:
|
||||
project_member.role = ADMIN_ROLE
|
||||
update_fields.append("role")
|
||||
if not project_member.is_active:
|
||||
project_member.is_active = True
|
||||
update_fields.append("is_active")
|
||||
if update_fields:
|
||||
update_fields.append("updated_at")
|
||||
project_member.save(update_fields=update_fields)
|
||||
restored += 1
|
||||
return restored
|
||||
return ensure_user_admin_project_memberships(workspace, user)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
|
|
@ -441,6 +428,7 @@ class NodeDCInternalWorkspaceMembershipEnsureEndpoint(View):
|
|||
deleted_at__isnull=True,
|
||||
).first()
|
||||
created = membership is None
|
||||
previous_role = membership.role if membership is not None else None
|
||||
|
||||
if membership is None:
|
||||
membership = WorkspaceMember.objects.create(
|
||||
|
|
@ -463,6 +451,8 @@ class NodeDCInternalWorkspaceMembershipEnsureEndpoint(View):
|
|||
|
||||
if role == ADMIN_ROLE:
|
||||
restore_admin_project_memberships(workspace, user)
|
||||
elif previous_role == ADMIN_ROLE:
|
||||
revoke_auto_project_admin_memberships(workspace, user)
|
||||
|
||||
if set_last_workspace:
|
||||
profile, _ = Profile.objects.get_or_create(user=user)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class ProjectBasePermission(BasePermission):
|
|||
return WorkspaceMember.objects.filter(
|
||||
workspace__slug=view.workspace_slug,
|
||||
member=request.user,
|
||||
role__in=[ROLE.ADMIN.value, ROLE.MEMBER.value],
|
||||
role=ROLE.ADMIN.value,
|
||||
is_active=True,
|
||||
).exists()
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ class ProjectMemberPermission(BasePermission):
|
|||
return WorkspaceMember.objects.filter(
|
||||
workspace__slug=view.workspace_slug,
|
||||
member=request.user,
|
||||
role__in=[ROLE.ADMIN.value, ROLE.MEMBER.value],
|
||||
role=ROLE.ADMIN.value,
|
||||
is_active=True,
|
||||
).exists()
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ function AnalyticsPage({ params }: Route.ComponentProps) {
|
|||
|
||||
// permissions
|
||||
const canPerformEmptyStateActions = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
[EUserPermissions.ADMIN],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export const ExtendedProjectSidebar = observer(function ExtendedProjectSidebar()
|
|||
|
||||
// auth
|
||||
const isAuthorizedUser = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
[EUserPermissions.ADMIN],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
import { Menu } from "@headlessui/react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { PlusIcon, ProjectIcon } from "@plane/propel/icons";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
|
|
@ -16,6 +17,7 @@ import { cn, copyUrlToClipboard } from "@plane/utils";
|
|||
// hooks
|
||||
import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
// components
|
||||
import { SidebarProjectsListItem } from "@/components/workspace/sidebar/projects-list-item";
|
||||
|
||||
|
|
@ -29,6 +31,8 @@ export const ProjectsToolbarMenu = observer(function ProjectsToolbarMenu({
|
|||
const { workspaceSlug } = useParams();
|
||||
const { joinedProjectIds } = useProject();
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const canCreateProject = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
||||
|
||||
const handleCopyText = (projectId: string) =>
|
||||
copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/issues`).then(() => {
|
||||
|
|
@ -81,6 +85,7 @@ export const ProjectsToolbarMenu = observer(function ProjectsToolbarMenu({
|
|||
/>
|
||||
))}
|
||||
</div>
|
||||
{canCreateProject && (
|
||||
<div className="mt-2 border-t border-white/8 px-1 pt-2">
|
||||
<Menu.Item>
|
||||
<button
|
||||
|
|
@ -95,6 +100,7 @@ export const ProjectsToolbarMenu = observer(function ProjectsToolbarMenu({
|
|||
</button>
|
||||
</Menu.Item>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Menu>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { observer } from "mobx-react";
|
|||
import Link from "next/link";
|
||||
import { useTheme } from "next-themes";
|
||||
// plane imports
|
||||
import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { cn } from "@plane/utils";
|
||||
// assets
|
||||
|
|
@ -16,11 +16,14 @@ import ProjectDarkEmptyState from "@/app/assets/empty-state/project-settings/no-
|
|||
import ProjectLightEmptyState from "@/app/assets/empty-state/project-settings/no-projects-light.png?url";
|
||||
// hooks
|
||||
import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
|
||||
function ProjectSettingsPage() {
|
||||
// store hooks
|
||||
const { resolvedTheme } = useTheme();
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const canCreateProject = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
||||
// derived values
|
||||
const resolvedPath = resolvedTheme === "dark" ? ProjectDarkEmptyState : ProjectLightEmptyState;
|
||||
return (
|
||||
|
|
@ -35,12 +38,14 @@ function ProjectSettingsPage() {
|
|||
<Link href="https://plane.so/" target="_blank" className={cn(getButtonStyling("secondary", "base"))}>
|
||||
Learn more about projects
|
||||
</Link>
|
||||
{canCreateProject && (
|
||||
<Button
|
||||
onClick={() => toggleCreateProjectModal(true)}
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON}
|
||||
>
|
||||
Start your first project
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export const NoProjectsEmptyState = observer(function NoProjectsEmptyState() {
|
|||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const canCreateProject = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
[EUserPermissions.ADMIN],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
const isWorkspaceAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export const GlobalViewEmptyState = observer(function GlobalViewEmptyState() {
|
|||
const { toggleCreateIssueModal, toggleCreateProjectModal } = useCommandPalette();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const canCreateProject = allowPermissions([EUserWorkspaceRoles.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
||||
const hasMemberLevelPermission = allowPermissions(
|
||||
[EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
|
|
@ -41,7 +42,7 @@ export const GlobalViewEmptyState = observer(function GlobalViewEmptyState() {
|
|||
onClick: () => {
|
||||
toggleCreateProjectModal(true);
|
||||
},
|
||||
disabled: !hasMemberLevelPermission,
|
||||
disabled: !canCreateProject,
|
||||
variant: "primary",
|
||||
},
|
||||
]}
|
||||
|
|
|
|||
|
|
@ -39,10 +39,7 @@ export const WorkspaceDraftIssuesRoot = observer(function WorkspaceDraftIssuesRo
|
|||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const hasMemberLevelPermission = allowPermissions(
|
||||
[EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
const canCreateProject = allowPermissions([EUserWorkspaceRoles.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
||||
|
||||
//swr hook for fetching issue properties
|
||||
useWorkspaceIssueProperties(workspaceSlug);
|
||||
|
|
@ -77,7 +74,7 @@ export const WorkspaceDraftIssuesRoot = observer(function WorkspaceDraftIssuesRo
|
|||
onClick: () => {
|
||||
toggleCreateProjectModal(true);
|
||||
},
|
||||
disabled: !hasMemberLevelPermission,
|
||||
disabled: !canCreateProject,
|
||||
variant: "primary",
|
||||
},
|
||||
]}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export const usePowerKCreationCommandsRecord = (): Record<TPowerKCreationCommand
|
|||
// derived values
|
||||
const canCreateWorkItem = canPerformAnyCreateAction && workspaceProjectIds && workspaceProjectIds.length > 0;
|
||||
const canCreateProject = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
[EUserPermissions.ADMIN],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
const hasProjectMemberLevelPermissions = (ctx: TPowerKContext) =>
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export const ProjectCardList = observer(function ProjectCardList(props: TProject
|
|||
|
||||
// permissions
|
||||
const canPerformEmptyStateActions = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
[EUserPermissions.ADMIN],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,14 @@
|
|||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
|
||||
import { getAssetIdFromUrl, checkURLValidity } from "@plane/utils";
|
||||
// plane ui
|
||||
// helpers
|
||||
// hooks
|
||||
import useKeypress from "@/hooks/use-keypress";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
// plane web components
|
||||
import { CreateProjectForm } from "@/plane-web/components/projects/create/root";
|
||||
// plane web types
|
||||
|
|
@ -36,9 +38,11 @@ enum EProjectCreationSteps {
|
|||
|
||||
export function CreateProjectModal(props: Props) {
|
||||
const { isOpen, onClose, setToFavorite = false, workspaceSlug, data, templateId } = props;
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// states
|
||||
const [currentStep, setCurrentStep] = useState<EProjectCreationSteps>(EProjectCreationSteps.CREATE_PROJECT);
|
||||
const [createdProjectId, setCreatedProjectId] = useState<string | null>(null);
|
||||
const canCreateProject = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE, workspaceSlug);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
|
|
@ -47,6 +51,10 @@ export function CreateProjectModal(props: Props) {
|
|||
}
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && !canCreateProject) onClose();
|
||||
}, [canCreateProject, isOpen, onClose]);
|
||||
|
||||
const handleNextStep = (projectId: string) => {
|
||||
if (!projectId) return;
|
||||
setCreatedProjectId(projectId);
|
||||
|
|
@ -65,6 +73,8 @@ export function CreateProjectModal(props: Props) {
|
|||
if (isOpen) onClose();
|
||||
});
|
||||
|
||||
if (!canCreateProject) return null;
|
||||
|
||||
return (
|
||||
<ModalCore
|
||||
isOpen={isOpen}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export const ProjectsBaseHeader = observer(function ProjectsBaseHeader() {
|
|||
const pathname = usePathname();
|
||||
// auth
|
||||
const isAuthorizedUser = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
[EUserPermissions.ADMIN],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
const isArchived = pathname.includes("/archives");
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export const SidebarProjectsList = observer(function SidebarProjectsList() {
|
|||
|
||||
// auth
|
||||
const isAuthorizedUser = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
[EUserPermissions.ADMIN],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue