diff --git a/src/hooks/useEntityPermissions.tsx b/src/hooks/useEntityPermissions.tsx index 4cf580df..0c0574d6 100644 --- a/src/hooks/useEntityPermissions.tsx +++ b/src/hooks/useEntityPermissions.tsx @@ -3,6 +3,7 @@ import { User } from "@/interfaces/user"; import { RolePermission } from "@/resources/entityPermissions"; import { mapBy } from "@/utils"; import { doesEntityAllow, findAllowedEntities, findAllowedEntitiesSomePermissions } from "@/utils/permissions"; +import { isAdmin } from "@/utils/users"; import { useMemo, useState } from "react"; export const useAllowedEntities = (user: User, entities: EntityWithRoles[], permission: RolePermission) => { @@ -15,7 +16,9 @@ export const useAllowedEntitiesSomePermissions = (user: User, entities: EntityWi return allowedEntityIds } -export const useEntityPermission = (user: User, entity: EntityWithRoles, permission: RolePermission) => { - const isAllowed = useMemo(() => doesEntityAllow(user, entity, permission), [user, entity, permission]) - return isAllowed +export const useEntityPermission = (user: User, entity?: EntityWithRoles, permission?: RolePermission) => { + if (isAdmin(user)) return true + if (!entity || !permission) return false + + return doesEntityAllow(user, entity, permission) } diff --git a/src/pages/(admin)/Lists/UserList.tsx b/src/pages/(admin)/Lists/UserList.tsx index 91d6af3b..20a40770 100644 --- a/src/pages/(admin)/Lists/UserList.tsx +++ b/src/pages/(admin)/Lists/UserList.tsx @@ -49,15 +49,19 @@ export default function UserList({ const isAdmin = useMemo(() => ["admin", "developer"].includes(user?.type), [user?.type]) + const entitiesViewStudents = useAllowedEntities(user, entities, "view_students") const entitiesEditStudents = useAllowedEntities(user, entities, "edit_students") const entitiesDeleteStudents = useAllowedEntities(user, entities, "delete_students") + const entitiesViewTeachers = useAllowedEntities(user, entities, "view_teachers") const entitiesEditTeachers = useAllowedEntities(user, entities, "edit_teachers") const entitiesDeleteTeachers = useAllowedEntities(user, entities, "delete_teachers") + const entitiesViewCorporates = useAllowedEntities(user, entities, "view_corporates") const entitiesEditCorporates = useAllowedEntities(user, entities, "edit_corporates") const entitiesDeleteCorporates = useAllowedEntities(user, entities, "delete_corporates") + const entitiesViewMasterCorporates = useAllowedEntities(user, entities, "view_mastercorporates") const entitiesEditMasterCorporates = useAllowedEntities(user, entities, "edit_mastercorporates") const entitiesDeleteMasterCorporates = useAllowedEntities(user, entities, "delete_mastercorporates") @@ -74,7 +78,23 @@ export default function UserList({ if (today.add(1, "months").isAfter(momentDate)) return "!text-mti-orange-light"; }; - const displayUsers = useMemo(() => filters.length > 0 ? filters.reduce((d, f) => d.filter(f), users) : users, [filters, users]) + const allowedUsers = useMemo(() => users.filter((u) => { + if (isAdmin) return true + if (u.id === user?.id) return false + + switch (u.type) { + case "student": return mapBy((u.entities || []), 'id').some((id) => mapBy(entitiesViewStudents, 'id').includes(id)) + case "teacher": return mapBy((u.entities || []), 'id').some((id) => mapBy(entitiesViewTeachers, 'id').includes(id)) + case 'corporate': return mapBy((u.entities || []), 'id').some((id) => mapBy(entitiesViewCorporates, 'id').includes(id)) + case 'mastercorporate': return mapBy((u.entities || []), 'id').some((id) => mapBy(entitiesViewMasterCorporates, 'id').includes(id)) + default: return false + } + }) + , [entitiesViewCorporates, entitiesViewMasterCorporates, entitiesViewStudents, entitiesViewTeachers, isAdmin, user?.id, users]) + + const displayUsers = useMemo(() => + filters.length > 0 ? filters.reduce((d, f) => d.filter(f), allowedUsers) : allowedUsers, + [filters, allowedUsers]) const deleteAccount = (user: User) => { if (!confirm(`Are you sure you want to delete ${user.name}'s account?`)) return; @@ -149,6 +169,7 @@ export default function UserList({ const canEditUser = (u: User) => isAdmin || u.entities.some(e => mapBy(getEditPermission(u.type), 'id').includes(e.id)) + const canDeleteUser = (u: User) => isAdmin || u.entities.some(e => mapBy(getDeletePermission(u.type), 'id').includes(e.id)) diff --git a/src/pages/assignments/[id].tsx b/src/pages/assignments/[id].tsx index 84e19b2e..3508e019 100644 --- a/src/pages/assignments/[id].tsx +++ b/src/pages/assignments/[id].tsx @@ -50,7 +50,10 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res, params}) if (!assignment) return redirect("/assignments") const entity = await getEntityWithRoles(assignment.entity || "") - if (!entity) return redirect("/assignments") + if (!entity){ + const users = await getUsers() + return {props: serialize({user, users, assignment})}; + } if (!doesEntityAllow(user, entity, 'view_assignments')) return redirect("/assignments") @@ -63,7 +66,7 @@ interface Props { user: User; users: User[]; assignment: Assignment; - entity: EntityWithRoles + entity?: EntityWithRoles } export default function AssignmentView({user, users, entity, assignment}: Props) { diff --git a/src/pages/assignments/index.tsx b/src/pages/assignments/index.tsx index 33f7f793..98fcf22a 100644 --- a/src/pages/assignments/index.tsx +++ b/src/pages/assignments/index.tsx @@ -70,6 +70,7 @@ interface Props { export default function AssignmentsPage({assignments, corporateAssignments, entities, user, users, groups}: Props) { const entitiesAllowCreate = useAllowedEntities(user, entities, 'create_assignment') const entitiesAllowEdit = useAllowedEntities(user, entities, 'edit_assignment') + const entitiesAllowArchive = useAllowedEntities(user, entities, 'archive_assignment') const activeAssignments = useMemo(() => assignments.filter(activeAssignmentFilter), [assignments]); const plannedAssignments = useMemo(() => assignments.filter(futureAssignmentFilter), [assignments]); @@ -177,7 +178,7 @@ export default function AssignmentsPage({assignments, corporateAssignments, enti onClick={() => router.push(`/assignments/${a.id}`)} key={a.id} allowDownload - allowArchive + allowArchive={mapBy(entitiesAllowArchive, 'id').includes(a.entity || "")} allowExcelDownload /> ))} @@ -197,7 +198,7 @@ export default function AssignmentsPage({assignments, corporateAssignments, enti onClick={() => router.push(`/assignments/${a.id}`)} key={a.id} allowDownload - allowArchive + allowArchive={mapBy(entitiesAllowArchive, 'id').includes(a.entity || "")} allowExcelDownload /> ))} diff --git a/src/pages/dashboard/corporate.tsx b/src/pages/dashboard/corporate.tsx index 16aa1b0e..b2966c63 100644 --- a/src/pages/dashboard/corporate.tsx +++ b/src/pages/dashboard/corporate.tsx @@ -16,6 +16,7 @@ import { checkAccess } from "@/utils/permissions"; import { calculateAverageLevel, calculateBandScore } from "@/utils/score"; import { groupByExam } from "@/utils/stats"; import { getStatsByUsers } from "@/utils/stats.be"; +import { filterAllowedUsers } from "@/utils/users.be"; import { getEntitiesUsers } from "@/utils/users.be"; import { withIronSessionSsr } from "iron-session/next"; import { uniqBy } from "lodash"; @@ -52,9 +53,9 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { if (!checkAccess(user, ["admin", "developer", "corporate"])) return redirect("/") const entityIDS = mapBy(user.entities, "id") || []; - - const users = await getEntitiesUsers(entityIDS); const entities = await getEntitiesWithRoles(entityIDS); + const users = await filterAllowedUsers(user, entities) + const assignments = await getEntitiesAssignments(entityIDS); const stats = await getStatsByUsers(users.map((u) => u.id)); const groups = await getGroupsByEntities(entityIDS); diff --git a/src/pages/dashboard/mastercorporate.tsx b/src/pages/dashboard/mastercorporate.tsx index 5d9000c8..bb8c7948 100644 --- a/src/pages/dashboard/mastercorporate.tsx +++ b/src/pages/dashboard/mastercorporate.tsx @@ -12,10 +12,11 @@ import { requestUser } from "@/utils/api"; import { getEntitiesAssignments } from "@/utils/assignments.be"; import { getEntitiesWithRoles } from "@/utils/entities.be"; import { getGroupsByEntities } from "@/utils/groups.be"; -import { checkAccess } from "@/utils/permissions"; +import { checkAccess, findAllowedEntities } from "@/utils/permissions"; import { calculateAverageLevel, calculateBandScore } from "@/utils/score"; import { groupByExam } from "@/utils/stats"; import { getStatsByUsers } from "@/utils/stats.be"; +import { filterAllowedUsers } from "@/utils/users.be"; import { getEntitiesUsers } from "@/utils/users.be"; import { withIronSessionSsr } from "iron-session/next"; import { uniqBy } from "lodash"; @@ -55,9 +56,9 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { return redirect("/") const entityIDS = mapBy(user.entities, "id") || []; - - const users = await getEntitiesUsers(entityIDS); const entities = await getEntitiesWithRoles(entityIDS); + const users = await filterAllowedUsers(user, entities) + const assignments = await getEntitiesAssignments(entityIDS); const stats = await getStatsByUsers(users.map((u) => u.id)); const groups = await getGroupsByEntities(entityIDS); diff --git a/src/pages/dashboard/teacher.tsx b/src/pages/dashboard/teacher.tsx index 5931b150..36afb919 100644 --- a/src/pages/dashboard/teacher.tsx +++ b/src/pages/dashboard/teacher.tsx @@ -11,7 +11,7 @@ import { dateSorter, filterBy, mapBy, redirect, serialize } from "@/utils"; import { getEntitiesAssignments } from "@/utils/assignments.be"; import { getEntitiesWithRoles } from "@/utils/entities.be"; import { getGroupsByEntities } from "@/utils/groups.be"; -import { checkAccess } from "@/utils/permissions"; +import { checkAccess, findAllowedEntities } from "@/utils/permissions"; import { calculateAverageLevel, calculateBandScore } from "@/utils/score"; import { groupByExam } from "@/utils/stats"; import { getStatsByUsers } from "@/utils/stats.be"; @@ -24,6 +24,8 @@ import { useMemo } from "react"; import { BsClipboard2Data, BsEnvelopePaper, BsPaperclip, BsPeople, BsPersonFill } from "react-icons/bs"; import { ToastContainer } from "react-toastify"; import { requestUser } from "@/utils/api"; +import { useAllowedEntities } from "@/hooks/useEntityPermissions"; +import { filterAllowedUsers } from "@/utils/users.be"; interface Props { user: User; @@ -42,9 +44,9 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { return redirect("/") const entityIDS = mapBy(user.entities, "id") || []; - - const users = await getEntitiesUsers(entityIDS); const entities = await getEntitiesWithRoles(entityIDS); + const users = await filterAllowedUsers(user, entities) + const assignments = await getEntitiesAssignments(entityIDS); const stats = await getStatsByUsers(users.map((u) => u.id)); const groups = await getGroupsByEntities(entityIDS); diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts index c86ff1bf..71059a57 100644 --- a/src/utils/permissions.ts +++ b/src/utils/permissions.ts @@ -52,7 +52,7 @@ export function findAllowedEntitiesSomePermissions(user: User, entities: EntityW export function doesEntityAllow(user: User, entity: EntityWithRoles, permission: RolePermission) { if (["admin", "developer"].includes(user?.type)) return true - const userEntity = findBy(user.entities, 'id', entity.id) + const userEntity = findBy(user.entities, 'id', entity?.id) if (!userEntity) return false const role = findBy(entity.roles, 'id', userEntity.role) diff --git a/src/utils/users.be.ts b/src/utils/users.be.ts index 8685cd8d..b684a1b5 100644 --- a/src/utils/users.be.ts +++ b/src/utils/users.be.ts @@ -3,9 +3,11 @@ import { getGroupsForUser, getParticipantGroups, getUserGroups, getUsersGroups } import { uniq } from "lodash"; import { getUserCodes } from "./codes.be"; import client from "@/lib/mongodb"; -import { WithEntities } from "@/interfaces/entity"; +import { EntityWithRoles, WithEntities } from "@/interfaces/entity"; import { getEntity } from "./entities.be"; import { getRole } from "./roles.be"; +import { findAllowedEntities } from "./permissions"; +import { mapBy } from "."; const db = client.db(process.env.MONGODB_DB); @@ -138,3 +140,17 @@ export async function getUserBalance(user: User) { codes.filter((x) => !participants.includes(x.userId || "") && !corporateUsers.map((u) => u.id).includes(x.userId || "")).length ); } + +export const filterAllowedUsers = async (user: User, entities: EntityWithRoles[]) => { + const studentsAllowedEntities = findAllowedEntities(user, entities, 'view_students') + const teachersAllowedEntities = findAllowedEntities(user, entities, 'view_teachers') + const corporateAllowedEntities = findAllowedEntities(user, entities, 'view_corporates') + const masterCorporateAllowedEntities = findAllowedEntities(user, entities, 'view_mastercorporates') + + const students = await getEntitiesUsers(mapBy(studentsAllowedEntities, 'id'), {type: "student"}) + const teachers = await getEntitiesUsers(mapBy(teachersAllowedEntities, 'id'), {type: "teacher"}) + const corporates = await getEntitiesUsers(mapBy(corporateAllowedEntities, 'id'), {type: "corporate"}) + const masterCorporates = await getEntitiesUsers(mapBy(masterCorporateAllowedEntities, 'id'), {type: "mastercorporate"}) + + return [...students, ...teachers, ...corporates, ...masterCorporates] +} diff --git a/src/utils/users.ts b/src/utils/users.ts index dae4fd80..659c0bd0 100644 --- a/src/utils/users.ts +++ b/src/utils/users.ts @@ -1,8 +1,11 @@ -import { WithLabeledEntities } from "@/interfaces/entity"; +import { EntityWithRoles, WithLabeledEntities } from "@/interfaces/entity"; import { Group, User } from "@/interfaces/user"; import { getUserCompanyName, USER_TYPE_LABELS } from "@/resources/user"; import { capitalize } from "lodash"; import moment from "moment"; +import { mapBy } from "."; +import { findAllowedEntities } from "./permissions"; +import { getEntitiesUsers } from "./users.be"; export interface UserListRow { name: string;