dev: revamp pages authorization (#6094)

This commit is contained in:
Aaryan Khandelwal 2024-12-02 13:59:01 +05:30 committed by GitHub
parent 9f14167ef5
commit 8c04aa6f51
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 48 additions and 30 deletions

View file

@ -114,7 +114,7 @@ class PageViewSet(BaseViewSet):
.distinct() .distinct()
) )
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) @allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def create(self, request, slug, project_id): def create(self, request, slug, project_id):
serializer = PageSerializer( serializer = PageSerializer(
data=request.data, data=request.data,
@ -134,7 +134,7 @@ class PageViewSet(BaseViewSet):
return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) @allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def partial_update(self, request, slug, project_id, pk): def partial_update(self, request, slug, project_id, pk):
try: try:
page = Page.objects.get( page = Page.objects.get(
@ -234,7 +234,7 @@ class PageViewSet(BaseViewSet):
) )
return Response(data, status=status.HTTP_200_OK) return Response(data, status=status.HTTP_200_OK)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) @allow_permission([ROLE.ADMIN], model=Page, creator=True)
def lock(self, request, slug, project_id, pk): def lock(self, request, slug, project_id, pk):
page = Page.objects.filter( page = Page.objects.filter(
pk=pk, workspace__slug=slug, projects__id=project_id pk=pk, workspace__slug=slug, projects__id=project_id
@ -244,7 +244,7 @@ class PageViewSet(BaseViewSet):
page.save() page.save()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) @allow_permission([ROLE.ADMIN], model=Page, creator=True)
def unlock(self, request, slug, project_id, pk): def unlock(self, request, slug, project_id, pk):
page = Page.objects.filter( page = Page.objects.filter(
pk=pk, workspace__slug=slug, projects__id=project_id pk=pk, workspace__slug=slug, projects__id=project_id
@ -255,7 +255,7 @@ class PageViewSet(BaseViewSet):
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) @allow_permission([ROLE.ADMIN], model=Page, creator=True)
def access(self, request, slug, project_id, pk): def access(self, request, slug, project_id, pk):
access = request.data.get("access", 0) access = request.data.get("access", 0)
page = Page.objects.filter( page = Page.objects.filter(
@ -296,7 +296,7 @@ class PageViewSet(BaseViewSet):
pages = PageSerializer(queryset, many=True).data pages = PageSerializer(queryset, many=True).data
return Response(pages, status=status.HTTP_200_OK) return Response(pages, status=status.HTTP_200_OK)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) @allow_permission([ROLE.ADMIN], model=Page, creator=True)
def archive(self, request, slug, project_id, pk): def archive(self, request, slug, project_id, pk):
page = Page.objects.get(pk=pk, workspace__slug=slug, projects__id=project_id) page = Page.objects.get(pk=pk, workspace__slug=slug, projects__id=project_id)
@ -323,7 +323,7 @@ class PageViewSet(BaseViewSet):
return Response({"archived_at": str(datetime.now())}, status=status.HTTP_200_OK) return Response({"archived_at": str(datetime.now())}, status=status.HTTP_200_OK)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) @allow_permission([ROLE.ADMIN], model=Page, creator=True)
def unarchive(self, request, slug, project_id, pk): def unarchive(self, request, slug, project_id, pk):
page = Page.objects.get(pk=pk, workspace__slug=slug, projects__id=project_id) page = Page.objects.get(pk=pk, workspace__slug=slug, projects__id=project_id)
@ -348,7 +348,7 @@ class PageViewSet(BaseViewSet):
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@allow_permission([ROLE.ADMIN], creator=True, model=Page) @allow_permission([ROLE.ADMIN], model=Page, creator=True)
def destroy(self, request, slug, project_id, pk): def destroy(self, request, slug, project_id, pk):
page = Page.objects.get(pk=pk, workspace__slug=slug, projects__id=project_id) page = Page.objects.get(pk=pk, workspace__slug=slug, projects__id=project_id)

View file

@ -13,9 +13,7 @@ import { BreadcrumbLink, Logo } from "@/components/common";
// constants // constants
import { EPageAccess } from "@/constants/page"; import { EPageAccess } from "@/constants/page";
// hooks // hooks
import { useEventTracker, useProject, useProjectPages, useUserPermissions } from "@/hooks/store"; import { useEventTracker, useProject, useProjectPages } from "@/hooks/store";
// plane web hooks
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
export const PagesListHeader = observer(() => { export const PagesListHeader = observer(() => {
// states // states
@ -26,16 +24,9 @@ export const PagesListHeader = observer(() => {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const pageType = searchParams.get("type"); const pageType = searchParams.get("type");
// store hooks // store hooks
const { allowPermissions } = useUserPermissions();
const { currentProjectDetails, loader } = useProject(); const { currentProjectDetails, loader } = useProject();
const { createPage } = useProjectPages(); const { canCurrentUserCreatePage, createPage } = useProjectPages();
const { setTrackElement } = useEventTracker(); const { setTrackElement } = useEventTracker();
// auth
const canUserCreatePage = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
EUserPermissionsLevel.PROJECT
);
// handle page create // handle page create
const handleCreatePage = async () => { const handleCreatePage = async () => {
setIsCreatingPage(true); setIsCreatingPage(true);
@ -87,7 +78,7 @@ export const PagesListHeader = observer(() => {
</Breadcrumbs> </Breadcrumbs>
</div> </div>
</Header.LeftItem> </Header.LeftItem>
{canUserCreatePage ? ( {canCurrentUserCreatePage ? (
<Header.RightItem> <Header.RightItem>
<Button variant="primary" size="sm" onClick={handleCreatePage} loading={isCreatingPage}> <Button variant="primary" size="sm" onClick={handleCreatePage} loading={isCreatingPage}>
{isCreatingPage ? "Adding" : "Add page"} {isCreatingPage ? "Adding" : "Add page"}

View file

@ -215,12 +215,15 @@ export class Page implements IPage {
*/ */
get canCurrentUserEditPage() { get canCurrentUserEditPage() {
const { workspaceSlug, projectId } = this.store.router; const { workspaceSlug, projectId } = this.store.router;
const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId( const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId(
workspaceSlug?.toString() || "", workspaceSlug?.toString() || "",
projectId?.toString() || "" projectId?.toString() || ""
); );
return this.isCurrentUserOwner || (!!currentUserProjectRole && currentUserProjectRole >= EUserPermissions.MEMBER); const isPagePublic = this.access === EPageAccess.PUBLIC;
return (
(isPagePublic && !!currentUserProjectRole && currentUserProjectRole >= EUserPermissions.MEMBER) ||
(!isPagePublic && this.isCurrentUserOwner)
);
} }
/** /**
@ -228,26 +231,35 @@ export class Page implements IPage {
*/ */
get canCurrentUserDuplicatePage() { get canCurrentUserDuplicatePage() {
const { workspaceSlug, projectId } = this.store.router; const { workspaceSlug, projectId } = this.store.router;
const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId( const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId(
workspaceSlug?.toString() || "", workspaceSlug?.toString() || "",
projectId?.toString() || "" projectId?.toString() || ""
); );
return this.isCurrentUserOwner || (!!currentUserProjectRole && currentUserProjectRole >= EUserPermissions.MEMBER); return !!currentUserProjectRole && currentUserProjectRole >= EUserPermissions.MEMBER;
} }
/** /**
* @description returns true if the current logged in user can lock the page * @description returns true if the current logged in user can lock the page
*/ */
get canCurrentUserLockPage() { get canCurrentUserLockPage() {
return this.isCurrentUserOwner; const { workspaceSlug, projectId } = this.store.router;
const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId(
workspaceSlug?.toString() || "",
projectId?.toString() || ""
);
return this.isCurrentUserOwner || currentUserProjectRole === EUserPermissions.ADMIN;
} }
/** /**
* @description returns true if the current logged in user can change the access of the page * @description returns true if the current logged in user can change the access of the page
*/ */
get canCurrentUserChangeAccess() { get canCurrentUserChangeAccess() {
return this.isCurrentUserOwner; const { workspaceSlug, projectId } = this.store.router;
const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId(
workspaceSlug?.toString() || "",
projectId?.toString() || ""
);
return this.isCurrentUserOwner || currentUserProjectRole === EUserPermissions.ADMIN;
} }
/** /**
@ -255,7 +267,6 @@ export class Page implements IPage {
*/ */
get canCurrentUserArchivePage() { get canCurrentUserArchivePage() {
const { workspaceSlug, projectId } = this.store.router; const { workspaceSlug, projectId } = this.store.router;
const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId( const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId(
workspaceSlug?.toString() || "", workspaceSlug?.toString() || "",
projectId?.toString() || "" projectId?.toString() || ""
@ -268,7 +279,6 @@ export class Page implements IPage {
*/ */
get canCurrentUserDeletePage() { get canCurrentUserDeletePage() {
const { workspaceSlug, projectId } = this.store.router; const { workspaceSlug, projectId } = this.store.router;
const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId( const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId(
workspaceSlug?.toString() || "", workspaceSlug?.toString() || "",
projectId?.toString() || "" projectId?.toString() || ""
@ -281,7 +291,6 @@ export class Page implements IPage {
*/ */
get canCurrentUserFavoritePage() { get canCurrentUserFavoritePage() {
const { workspaceSlug, projectId } = this.store.router; const { workspaceSlug, projectId } = this.store.router;
const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId( const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId(
workspaceSlug?.toString() || "", workspaceSlug?.toString() || "",
projectId?.toString() || "" projectId?.toString() || ""

View file

@ -1,11 +1,13 @@
import set from "lodash/set"; import set from "lodash/set";
import unset from "lodash/unset"; import unset from "lodash/unset";
import { makeObservable, observable, runInAction, action, reaction } from "mobx"; import { makeObservable, observable, runInAction, action, reaction, computed } from "mobx";
import { computedFn } from "mobx-utils"; import { computedFn } from "mobx-utils";
// types // types
import { TPage, TPageFilters, TPageNavigationTabs } from "@plane/types"; import { TPage, TPageFilters, TPageNavigationTabs } from "@plane/types";
// helpers // helpers
import { filterPagesByPageType, getPageName, orderPages, shouldFilterPage } from "@/helpers/page.helper"; import { filterPagesByPageType, getPageName, orderPages, shouldFilterPage } from "@/helpers/page.helper";
// plane web constants
import { EUserPermissions } from "@/plane-web/constants";
// services // services
import { ProjectPageService } from "@/services/page"; import { ProjectPageService } from "@/services/page";
// store // store
@ -24,6 +26,7 @@ export interface IProjectPageStore {
filters: TPageFilters; filters: TPageFilters;
// computed // computed
isAnyPageAvailable: boolean; isAnyPageAvailable: boolean;
canCurrentUserCreatePage: boolean;
// helper actions // helper actions
getCurrentProjectPageIds: (pageType: TPageNavigationTabs) => string[] | undefined; getCurrentProjectPageIds: (pageType: TPageNavigationTabs) => string[] | undefined;
getCurrentProjectFilteredPageIds: (pageType: TPageNavigationTabs) => string[] | undefined; getCurrentProjectFilteredPageIds: (pageType: TPageNavigationTabs) => string[] | undefined;
@ -62,6 +65,9 @@ export class ProjectPageStore implements IProjectPageStore {
data: observable, data: observable,
error: observable, error: observable,
filters: observable, filters: observable,
// computed
isAnyPageAvailable: computed,
canCurrentUserCreatePage: computed,
// helper actions // helper actions
updateFilters: action, updateFilters: action,
clearAllFilters: action, clearAllFilters: action,
@ -92,6 +98,18 @@ export class ProjectPageStore implements IProjectPageStore {
return Object.keys(this.data).length > 0; return Object.keys(this.data).length > 0;
} }
/**
* @description returns true if the current logged in user can create a page
*/
get canCurrentUserCreatePage() {
const { workspaceSlug, projectId } = this.store.router;
const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId(
workspaceSlug?.toString() || "",
projectId?.toString() || ""
);
return !!currentUserProjectRole && currentUserProjectRole >= EUserPermissions.MEMBER;
}
/** /**
* @description get the current project page ids based on the pageType * @description get the current project page ids based on the pageType
* @param {TPageNavigationTabs} pageType * @param {TPageNavigationTabs} pageType