From 4339f71f9cca667623e9c7e75a6ec01bb4361959 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Thu, 20 Mar 2025 16:05:25 +0530 Subject: [PATCH 1/2] [WEB-3597] fix: guest work item view access when hyper mode is enabled --- apiserver/plane/app/views/project/base.py | 1 + packages/types/src/project/projects.d.ts | 4 +--- web/core/local-db/storage.sqlite.ts | 22 +++++++++++++++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/apiserver/plane/app/views/project/base.py b/apiserver/plane/app/views/project/base.py index 2b9d65e10f7..00e1ad1eb13 100644 --- a/apiserver/plane/app/views/project/base.py +++ b/apiserver/plane/app/views/project/base.py @@ -177,6 +177,7 @@ def list(self, request, slug): "module_view", "page_view", "inbox_view", + "guest_view_all_features", "project_lead", "created_at", "updated_at", diff --git a/packages/types/src/project/projects.d.ts b/packages/types/src/project/projects.d.ts index b04b9828468..40562d362d5 100644 --- a/packages/types/src/project/projects.d.ts +++ b/packages/types/src/project/projects.d.ts @@ -25,6 +25,7 @@ export interface IPartialProject { module_view: boolean; page_view: boolean; inbox_view: boolean; + guest_view_all_features?: boolean; project_lead?: IUserLite | string | null; // Timestamps created_at?: Date; @@ -46,11 +47,8 @@ export interface IProject extends IPartialProject { default_state?: string | null; description?: string; estimate?: string | null; - guest_view_all_features?: boolean; anchor?: string | null; is_favorite?: boolean; - is_issue_type_enabled?: boolean; - is_time_tracking_enabled?: boolean; members?: string[]; network?: number; timezone?: string; diff --git a/web/core/local-db/storage.sqlite.ts b/web/core/local-db/storage.sqlite.ts index fb351add64b..c487b972760 100644 --- a/web/core/local-db/storage.sqlite.ts +++ b/web/core/local-db/storage.sqlite.ts @@ -1,7 +1,7 @@ import * as Comlink from "comlink"; import set from "lodash/set"; // plane -import { EIssueGroupBYServerToProperty } from "@plane/constants"; +import { EIssueGroupBYServerToProperty, EUserPermissions } from "@plane/constants"; import { TIssue } from "@plane/types"; // lib import { rootStore } from "@/lib/store-context"; @@ -294,6 +294,26 @@ export class Storage { } } + // Get current project details and user role for the project + const currentProject = rootStore.projectRoot.project.getProjectById(projectId); + const currentUserRole = rootStore.user.permission.projectPermissionsByWorkspaceSlugAndProjectId( + workspaceSlug, + projectId + ); + + // Return empty results if the user is a guest and the project does not have guest view all features enabled + if (currentUserRole === EUserPermissions.GUEST && currentProject?.guest_view_all_features === false) { + return { + results: [], + next_cursor: `${PAGE_SIZE}:0:0`, + prev_cursor: `${PAGE_SIZE}:0:0`, + total_results: 0, + total_count: 0, + next_page_results: false, + total_pages: 0, + }; + } + const { cursor, group_by, sub_group_by } = queries; const query = issueFilterQueryConstructor(this.workspaceSlug, projectId, queries); From 59bf311baa65a195d318f2fe8ce832c53429878c Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Thu, 20 Mar 2025 17:17:41 +0530 Subject: [PATCH 2/2] fix: only show work item created by the guest user if the guest_view_all_features is disabled --- web/core/local-db/storage.sqlite.ts | 41 +++++++------------ web/core/local-db/utils/query-sanitizer.ts.ts | 41 +++++++++++++++++++ web/core/services/issue/issue.service.ts | 8 +++- 3 files changed, 62 insertions(+), 28 deletions(-) create mode 100644 web/core/local-db/utils/query-sanitizer.ts.ts diff --git a/web/core/local-db/storage.sqlite.ts b/web/core/local-db/storage.sqlite.ts index c487b972760..e0395151527 100644 --- a/web/core/local-db/storage.sqlite.ts +++ b/web/core/local-db/storage.sqlite.ts @@ -1,8 +1,8 @@ import * as Comlink from "comlink"; import set from "lodash/set"; // plane -import { EIssueGroupBYServerToProperty, EUserPermissions } from "@plane/constants"; -import { TIssue } from "@plane/types"; +import { EIssueGroupBYServerToProperty } from "@plane/constants"; +import { TIssue, TIssueParams } from "@plane/types"; // lib import { rootStore } from "@/lib/store-context"; // services @@ -15,6 +15,7 @@ import { addIssuesBulk, syncDeletesToLocal } from "./utils/load-issues"; import { loadWorkSpaceData } from "./utils/load-workspace"; import { issueFilterCountQueryConstructor, issueFilterQueryConstructor } from "./utils/query-constructor"; import { runQuery } from "./utils/query-executor"; +import { sanitizeWorkItemQueries } from "./utils/query-sanitizer.ts"; import { createTables } from "./utils/tables"; import { clearOPFS, getGroupedIssueResults, getSubGroupedIssueResults, log, logError } from "./utils/utils"; @@ -269,7 +270,12 @@ export class Storage { return issue.updated_at; }; - getIssues = async (workspaceSlug: string, projectId: string, queries: any, config: any) => { + getIssues = async ( + workspaceSlug: string, + projectId: string, + queries: Partial> | undefined, + config: any + ) => { log("#### Queries", queries); const currentProjectStatus = this.getStatus(projectId); @@ -294,31 +300,12 @@ export class Storage { } } - // Get current project details and user role for the project - const currentProject = rootStore.projectRoot.project.getProjectById(projectId); - const currentUserRole = rootStore.user.permission.projectPermissionsByWorkspaceSlugAndProjectId( - workspaceSlug, - projectId - ); - - // Return empty results if the user is a guest and the project does not have guest view all features enabled - if (currentUserRole === EUserPermissions.GUEST && currentProject?.guest_view_all_features === false) { - return { - results: [], - next_cursor: `${PAGE_SIZE}:0:0`, - prev_cursor: `${PAGE_SIZE}:0:0`, - total_results: 0, - total_count: 0, - next_page_results: false, - total_pages: 0, - }; - } - - const { cursor, group_by, sub_group_by } = queries; + const sanitizedQueries = sanitizeWorkItemQueries(workspaceSlug, projectId, queries); + const { cursor, group_by, sub_group_by } = sanitizedQueries || {}; - const query = issueFilterQueryConstructor(this.workspaceSlug, projectId, queries); + const query = issueFilterQueryConstructor(this.workspaceSlug, projectId, sanitizedQueries); log("#### Query", query); - const countQuery = issueFilterCountQueryConstructor(this.workspaceSlug, projectId, queries); + const countQuery = issueFilterCountQueryConstructor(this.workspaceSlug, projectId, sanitizedQueries); const start = performance.now(); let issuesRaw: any[] = []; let count: any[]; @@ -333,7 +320,7 @@ export class Storage { const { total_count } = count[0]; - const [pageSize, page, offset] = cursor.split(":"); + const [pageSize, page, offset] = cursor && typeof cursor === "string" ? cursor.split(":") : []; const groupByProperty: string = EIssueGroupBYServerToProperty[group_by as keyof typeof EIssueGroupBYServerToProperty]; diff --git a/web/core/local-db/utils/query-sanitizer.ts.ts b/web/core/local-db/utils/query-sanitizer.ts.ts new file mode 100644 index 00000000000..39ee122b850 --- /dev/null +++ b/web/core/local-db/utils/query-sanitizer.ts.ts @@ -0,0 +1,41 @@ +// plane constants +import { EUserPermissions } from "@plane/constants"; +import { TIssueParams } from "@plane/types"; +// root store +import { rootStore } from "@/lib/store-context"; + +export const sanitizeWorkItemQueries = ( + workspaceSlug: string, + projectId: string, + queries: Partial> | undefined +): Partial> | undefined => { + // Get current project details and user id and role for the project + const currentProject = rootStore.projectRoot.project.getProjectById(projectId); + const currentUserId = rootStore.user.data?.id; + const currentUserRole = rootStore.user.permission.projectPermissionsByWorkspaceSlugAndProjectId( + workspaceSlug, + projectId + ); + + // Only apply this restriction for guests when guest_view_all_features is disabled + if ( + currentUserId && + currentUserRole === EUserPermissions.GUEST && + currentProject?.guest_view_all_features === false + ) { + // Sanitize the created_by filter if it doesn't exist or if it exists and includes the current user id + const existingCreatedByFilter = queries?.created_by; + const shouldApplyFilter = + !existingCreatedByFilter || + (typeof existingCreatedByFilter === "string" && existingCreatedByFilter.includes(currentUserId)); + + if (shouldApplyFilter) { + queries = { + ...queries, + created_by: currentUserId, + }; + } + } + + return queries; +}; diff --git a/web/core/services/issue/issue.service.ts b/web/core/services/issue/issue.service.ts index 203b319c7d3..ccd240bfab6 100644 --- a/web/core/services/issue/issue.service.ts +++ b/web/core/services/issue/issue.service.ts @@ -1,6 +1,7 @@ import { EIssueServiceType } from "@plane/constants"; // types import { + TIssueParams, type IIssueDisplayProperties, type TBulkOperationsPayload, type TIssue, @@ -75,7 +76,12 @@ export class IssueService extends APIService { }); } - async getIssues(workspaceSlug: string, projectId: string, queries?: any, config = {}): Promise { + async getIssues( + workspaceSlug: string, + projectId: string, + queries?: Partial>, + config = {} + ): Promise { if (getIssuesShouldFallbackToServer(queries) || this.serviceType !== EIssueServiceType.ISSUES) { return await this.getIssuesFromServer(workspaceSlug, projectId, queries, config); }