From 98f0925bbed2dbc06a67bf481656e8c0e3dff959 Mon Sep 17 00:00:00 2001 From: cbolles Date: Tue, 27 Feb 2024 12:39:16 -0500 Subject: [PATCH 1/3] Begin addressing problem by querying for projects based on user access --- packages/server/src/project/project.resolver.ts | 6 +----- packages/server/src/project/project.service.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/server/src/project/project.resolver.ts b/packages/server/src/project/project.resolver.ts index 1bce80af..76903aac 100644 --- a/packages/server/src/project/project.resolver.ts +++ b/packages/server/src/project/project.resolver.ts @@ -64,15 +64,11 @@ export class ProjectResolver { return true; } - // TODO: Handle the ability to get project based on user access @Query(() => [Project]) async getProjects( @OrganizationContext() organization: Organization, @TokenContext() user: TokenPayload ): Promise { - if (!(await this.enforcer.enforce(user.user_id, ProjectPermissions.READ, organization._id))) { - throw new UnauthorizedException('User does not have permission to read projects'); - } - return this.projectService.findAll(organization._id); + return this.projectService.findAllForUser(user, organization._id); } } diff --git a/packages/server/src/project/project.service.ts b/packages/server/src/project/project.service.ts index a0e60a10..13ede628 100644 --- a/packages/server/src/project/project.service.ts +++ b/packages/server/src/project/project.service.ts @@ -5,6 +5,9 @@ import { Project, ProjectDocument } from './project.model'; import { ProjectCreate } from './dtos/create.dto'; import { CASBIN_PROVIDER } from '../permission/casbin.provider'; import * as casbin from 'casbin'; +import { TokenPayload } from 'src/jwt/token.dto'; +import { ProjectPermissions } from 'src/permission/permissions/project'; +import { Roles } from 'src/permission/permissions/roles'; @Injectable() export class ProjectService { @@ -42,4 +45,17 @@ export class ProjectService { async delete(project: Project): Promise { await this.projectModel.deleteOne({ _id: project._id }); } + + async findAllForUser(user: TokenPayload, organization: string): Promise { + const projects = await this.findAll(organization); + const allowedProjects: Project[] = []; + for (const project of projects) { + const hasAccess = await this.enforcer.hasPolicy(user.user_id, ProjectPermissions.READ, project._id.toString()); + console.log(hasAccess); + if (hasAccess) { + allowedProjects.push(project); + } + } + return allowedProjects; + } } From ae2319f73527aff5731f2ca6fe87d1890c7c52e9 Mon Sep 17 00:00:00 2001 From: cbolles Date: Tue, 27 Feb 2024 12:49:08 -0500 Subject: [PATCH 2/3] Working project access based on project admin access --- packages/client/src/components/SideBar.component.tsx | 1 + packages/server/src/project/project.service.ts | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/components/SideBar.component.tsx b/packages/client/src/components/SideBar.component.tsx index 6b466b46..c957b35e 100644 --- a/packages/client/src/components/SideBar.component.tsx +++ b/packages/client/src/components/SideBar.component.tsx @@ -28,6 +28,7 @@ export const SideBar: FC = ({ open, drawerWidth }) => { useEffect(() => { if (rolesQueryResults.data) { setPermission(rolesQueryResults.data.getRoles); + console.log(rolesQueryResults.data); } }, [rolesQueryResults.data]); diff --git a/packages/server/src/project/project.service.ts b/packages/server/src/project/project.service.ts index 13ede628..6531707a 100644 --- a/packages/server/src/project/project.service.ts +++ b/packages/server/src/project/project.service.ts @@ -50,8 +50,7 @@ export class ProjectService { const projects = await this.findAll(organization); const allowedProjects: Project[] = []; for (const project of projects) { - const hasAccess = await this.enforcer.hasPolicy(user.user_id, ProjectPermissions.READ, project._id.toString()); - console.log(hasAccess); + const hasAccess = await this.enforcer.enforce(user.user_id, ProjectPermissions.READ, project._id.toString()); if (hasAccess) { allowedProjects.push(project); } From 90740a158411e48201d7b7f2cfa874ea1a578fd3 Mon Sep 17 00:00:00 2001 From: cbolles Date: Tue, 27 Feb 2024 13:33:12 -0500 Subject: [PATCH 3/3] Add concept of project viewer to bridge the gap between study access and process access --- packages/client/src/components/SideBar.component.tsx | 1 - packages/server/src/permission/permission.service.ts | 9 ++++++++- packages/server/src/permission/permissions/project.ts | 4 +++- packages/server/src/permission/permissions/roles.ts | 2 ++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/client/src/components/SideBar.component.tsx b/packages/client/src/components/SideBar.component.tsx index c957b35e..6b466b46 100644 --- a/packages/client/src/components/SideBar.component.tsx +++ b/packages/client/src/components/SideBar.component.tsx @@ -28,7 +28,6 @@ export const SideBar: FC = ({ open, drawerWidth }) => { useEffect(() => { if (rolesQueryResults.data) { setPermission(rolesQueryResults.data.getRoles); - console.log(rolesQueryResults.data); } }, [rolesQueryResults.data]); diff --git a/packages/server/src/permission/permission.service.ts b/packages/server/src/permission/permission.service.ts index 0df5c274..3e55d9f0 100644 --- a/packages/server/src/permission/permission.service.ts +++ b/packages/server/src/permission/permission.service.ts @@ -114,7 +114,7 @@ export class PermissionService { async grantStudyAdmin(study: Study, user: string, isAdmin: boolean, requestingUser: TokenPayload): Promise { // Make sure the target user is not a project admin - const isProjectAdmin = await this.enforcer.enforce(user, Roles.PROJECT_ADMIN, study._id); + const isProjectAdmin = await this.enforcer.enforce(user, Roles.PROJECT_ADMIN, study._id.toString()); if (isProjectAdmin) { throw new UnauthorizedException('Target user is an owner'); } @@ -127,8 +127,13 @@ export class PermissionService { // Otherwise grant the permissions if (isAdmin) { await this.enforcer.addPolicy(user, Roles.STUDY_ADMIN, study._id.toString()); + await this.enforcer.addPolicy(user, Roles.PROJECT_VIEWER, study.project); } else { await this.enforcer.removePolicy(user, Roles.STUDY_ADMIN, study._id.toString()); + // If the user isn't a contributor, then also remove project PROJECT_VIEWER + if (!(await this.enforcer.enforce(user, Roles.CONTRIBUTOR, study._id.toString()))) { + await this.enforcer.removePolicy(user, Roles.PROJECT_VIEWER, study.project); + } } return true; @@ -154,8 +159,10 @@ export class PermissionService { // Otherwise grant the permissions if (isContributor) { await this.enforcer.addPolicy(user, Roles.CONTRIBUTOR, study._id.toString()); + await this.enforcer.addPolicy(user, Roles.PROJECT_VIEWER, study.project); } else { await this.enforcer.removePolicy(user, Roles.CONTRIBUTOR, study._id.toString()); + await this.enforcer.removePolicy(user, Roles.PROJECT_VIEWER, study.project); } return true; diff --git a/packages/server/src/permission/permissions/project.ts b/packages/server/src/permission/permissions/project.ts index b4f56cd0..1521e086 100644 --- a/packages/server/src/permission/permissions/project.ts +++ b/packages/server/src/permission/permissions/project.ts @@ -19,10 +19,12 @@ export const roleToProjectPermissions: string[][] = [ [Roles.PROJECT_ADMIN, ProjectPermissions.UPDATE], [Roles.OWNER, ProjectPermissions.GRANT_ADMIN], + // PROJECT_VIEWER permissions + [Roles.PROJECT_VIEWER, ProjectPermissions.READ] + // STUDY_ADMIN permissions // CONTRIBUTOR permissions - [Roles.CONTRIBUTOR, ProjectPermissions.READ] // TRAINED_CONTRIBUTOR permissions ]; diff --git a/packages/server/src/permission/permissions/roles.ts b/packages/server/src/permission/permissions/roles.ts index 10586534..40b56a5c 100644 --- a/packages/server/src/permission/permissions/roles.ts +++ b/packages/server/src/permission/permissions/roles.ts @@ -1,6 +1,7 @@ export enum Roles { OWNER = 'owner', PROJECT_ADMIN = 'project_admin', + PROJECT_VIEWER = 'project_viewer', STUDY_ADMIN = 'study_admin', TRAINED_CONTRIBUTOR = 'trained_contributor', CONTRIBUTOR = 'contributor' @@ -13,5 +14,6 @@ export enum Roles { export const roleHierarchy: string[][] = [ [Roles.OWNER, Roles.PROJECT_ADMIN], [Roles.PROJECT_ADMIN, Roles.STUDY_ADMIN], + [Roles.PROJECT_ADMIN, Roles.PROJECT_VIEWER], [Roles.STUDY_ADMIN, Roles.CONTRIBUTOR] ];