Skip to content
Merged
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
1 change: 1 addition & 0 deletions apiserver/plane/app/views/project/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ def list(self, request, slug):
"inbox_view",
"guest_view_all_features",
"project_lead",
"network",
"created_at",
"updated_at",
"created_by",
Expand Down
24 changes: 17 additions & 7 deletions apiserver/plane/app/views/project/invite.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@
# Module imports
from .base import BaseViewSet, BaseAPIView
from plane.app.serializers import ProjectMemberInviteSerializer

from plane.app.permissions import allow_permission, ROLE

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


class ProjectInvitationsViewset(BaseViewSet):
Expand Down Expand Up @@ -128,6 +128,7 @@ def get_queryset(self):
.select_related("workspace", "workspace__owner", "project")
)

@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
def create(self, request, slug):
project_ids = request.data.get("project_ids", [])

Expand All @@ -136,11 +137,20 @@ def create(self, request, slug):
member=request.user, workspace__slug=slug, is_active=True
)

if workspace_member.role not in [ROLE.ADMIN.value, ROLE.MEMBER.value]:
return Response(
{"error": "You do not have permission to join the project"},
status=status.HTTP_403_FORBIDDEN,
)
# Get all the projects
projects = Project.objects.filter(
id__in=project_ids, workspace__slug=slug
).only("id", "network")
# Check if user has permission to join each project
for project in projects:
if (
project.network == ProjectNetwork.SECRET.value
and workspace_member.role != ROLE.ADMIN.value
):
return Response(
{"error": "Only workspace admins can join private project"},
status=status.HTTP_403_FORBIDDEN,
)

workspace_role = workspace_member.role
workspace = workspace_member.workspace
Expand Down
10 changes: 10 additions & 0 deletions apiserver/plane/db/models/project.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Python imports
import pytz
from uuid import uuid4
from enum import Enum

# Django imports
from django.conf import settings
Expand All @@ -17,6 +18,15 @@
ROLE_CHOICES = ((20, "Admin"), (15, "Member"), (5, "Guest"))


class ProjectNetwork(Enum):
SECRET = 0
PUBLIC = 2

@classmethod
def choices(cls):
return [(0, "Secret"), (2, "Public")]


def get_default_props():
return {
"filters": {
Expand Down
6 changes: 6 additions & 0 deletions packages/types/src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ export enum EUserPermissions {

export type TUserPermissions = EUserPermissions.ADMIN | EUserPermissions.MEMBER | EUserPermissions.GUEST;

// project network
export enum EProjectNetwork {
PRIVATE = 0,
PUBLIC = 2,
}

// project pages
export enum EPageAccess {
PUBLIC = 0,
Expand Down
2 changes: 1 addition & 1 deletion packages/types/src/project/projects.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface IPartialProject {
inbox_view: boolean;
guest_view_all_features?: boolean;
project_lead?: IUserLite | string | null;
network?: number;
// Timestamps
created_at?: Date;
updated_at?: Date;
Expand All @@ -50,7 +51,6 @@ export interface IProject extends IPartialProject {
anchor?: string | null;
is_favorite?: boolean;
members?: string[];
network?: number;
timezone?: string;
}

Expand Down
15 changes: 13 additions & 2 deletions web/core/layouts/auth-layout/project-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import useSWR from "swr";
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// components
import { EProjectNetwork } from "@plane/types/src/enums";
import { JoinProject } from "@/components/auth-screens";
import { LogoSpinner } from "@/components/common";
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
Expand Down Expand Up @@ -70,6 +71,11 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
workspaceSlug.toString(),
projectId?.toString()
);
const isWorkspaceAdmin = allowPermissions(
[EUserPermissions.ADMIN],
EUserPermissionsLevel.WORKSPACE,
workspaceSlug.toString()
);

// Initialize module timeline chart
useEffect(() => {
Expand Down Expand Up @@ -168,10 +174,15 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
);

// check if the user don't have permission to access the project
if (projectExists && projectId && hasPermissionToCurrentProject === false) return <JoinProject />;
if (
((projectExists?.network && projectExists?.network !== EProjectNetwork.PRIVATE) || isWorkspaceAdmin) &&
projectId &&
hasPermissionToCurrentProject === false
)
return <JoinProject />;

// check if the project info is not found.
if (loader === "loaded" && !projectExists && projectId && !!hasPermissionToCurrentProject === false)
if (loader === "loaded" && projectId && !!hasPermissionToCurrentProject === false)
return (
<div className="grid h-screen place-items-center bg-custom-background-100">
<DetailedEmptyState
Expand Down