diff --git a/README.md b/README.md index 4098410..560b367 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,6 @@ A user can have one of the following roles: - Can CRUD user- and group-permissions for managed users and administrated groups. - `STUDENT`: The user has limited access to the system and can only manage their own resources. - \* Documents can be updated always only by the user who created them. Except the document has excplicite shared permissions with other users/groups. ## Code Formatting diff --git a/src/controllers/documentRoots.ts b/src/controllers/documentRoots.ts index 9cbff51..0c98f99 100644 --- a/src/controllers/documentRoots.ts +++ b/src/controllers/documentRoots.ts @@ -6,6 +6,7 @@ import { HTTP400Error, HTTP403Error } from '../utils/errors/Errors.js'; import Document from '../models/Document.js'; import { NoneAccess, RO_RW_DocumentRootAccess } from '../helpers/accessPolicy.js'; import { hasElevatedAccess } from '../models/User.js'; +import { Access } from '../../prisma/generated/enums.js'; export const find: RequestHandler<{ id: string }> = async (req, res, next) => { const document = await DocumentRoot.findModel((req as any).user!, req.params.id); @@ -151,10 +152,27 @@ export const update: RequestHandler<{ id: string }, any, UpdateConfig> = async ( res.status(204).send(); }; -export const permissions: RequestHandler<{ id: string }> = async (req, res, next) => { - const permissions = await DocumentRoot.getPermissions((req as any).user!, req.params.id); +export const permissions: RequestHandler = async ( + req, + res, + next +) => { + const permissions = await DocumentRoot.getPermissions((req as any).user!, req.body.documentRootIds); res.json(permissions); }; +// TODO: remove this endpoint once the permissions [POST]/documentRoots/permissions endpoint is established and clients are updated +export const singlePermissions: RequestHandler<{ id: string }> = async (req, res, next) => { + const permissions = await DocumentRoot.getPermissions((req as any).user!, [req.params.id]); + res.json( + permissions[0] ?? { + id: req.params.id, + access: Access.None_DocumentRoot, + sharedAccess: Access.None_DocumentRoot, + userPermissions: [], + groupPermissions: [] + } + ); +}; export const destroy: RequestHandler<{ id: string }> = async (req, res, next) => { const model = await DocumentRoot.deleteModel((req as any).user!, req.params.id); diff --git a/src/models/DocumentRoot.ts b/src/models/DocumentRoot.ts index acd9853..db965da 100644 --- a/src/models/DocumentRoot.ts +++ b/src/models/DocumentRoot.ts @@ -23,6 +23,8 @@ export type ApiDocumentRoot = DbDocumentRoot & { type Permissions = { id: string; + access: Access; + sharedAccess: Access; userPermissions: ApiUserPermission[]; groupPermissions: ApiGroupPermission[]; }; @@ -50,11 +52,13 @@ export interface UpdateConfig { sharedAccess?: Access; } -const prepareGroupPermission = (permission: RootGroupPermission): ApiGroupPermission => { +const prepareGroupPermission = ( + permission: Omit +): ApiGroupPermission => { return { id: permission.id, access: permission.access, groupId: permission.studentGroupId }; }; -const prepareUserPermission = (permission: RootUserPermission): ApiUserPermission => { +const prepareUserPermission = (permission: Omit): ApiUserPermission => { return { id: permission.id, access: permission.access, userId: permission.userId }; }; @@ -207,30 +211,34 @@ function DocumentRoot(db: PrismaClient['documentRoot']) { groupPermissions: model.rootGroupPermissions.map((p) => prepareGroupPermission(p)) }; }, - async getPermissions(actor: User, id: string): Promise { + async getPermissions(actor: User, ids: string[]): Promise { if (!hasElevatedAccess(actor.role)) { throw new HTTP403Error('Not authorized'); } - const userPermissions = await prisma.rootUserPermission.findMany({ - where: - actor.role === Role.ADMIN - ? { documentRootId: id } - : { documentRootId: id, user: whereStudentGroupAccess(actor.id, true) } - }); - const groupPermissions = await prisma.rootGroupPermission.findMany({ - where: - actor.role === Role.ADMIN - ? { documentRootId: id } - : { - documentRootId: id, - studentGroup: { users: { some: { userId: actor.id, isAdmin: true } } } - } + const permissions = await db.findMany({ + where: { id: { in: ids } }, + include: { + rootUserPermissions: { + select: { id: true, access: true, userId: true }, + where: + actor.role === Role.ADMIN ? {} : { user: whereStudentGroupAccess(actor.id, true) } + }, + rootGroupPermissions: { + select: { id: true, access: true, studentGroupId: true }, + where: + actor.role === Role.ADMIN + ? {} + : { studentGroup: { users: { some: { userId: actor.id, isAdmin: true } } } } + } + } }); - return { - id: id, - userPermissions: userPermissions.map(prepareUserPermission), - groupPermissions: groupPermissions.map(prepareGroupPermission) - }; + return permissions.map((p) => ({ + id: p.id, + access: p.access, + sharedAccess: p.sharedAccess, + userPermissions: p.rootUserPermissions.map(prepareUserPermission), + groupPermissions: p.rootGroupPermissions.map(prepareGroupPermission) + })); }, async deleteModel(actor: User, id: string) { const record = await this.findModel(actor, id); diff --git a/src/routes/authConfig.ts b/src/routes/authConfig.ts index 38d43a3..b17b733 100644 --- a/src/routes/authConfig.ts +++ b/src/routes/authConfig.ts @@ -51,6 +51,11 @@ const authConfig: Config = { { methods: ['PUT', 'DELETE'], minRole: Role.TEACHER } ] }, + documentRootMultiPermissions: { + path: '/documentRoots/permissions', + access: [{ methods: ['POST'], minRole: Role.TEACHER }] + }, + // TODO: remove this endpoint once the permissions [POST]/documentRoots/permissions endpoint is established and clients are updated documentRootPermissions: { path: '/documentRoots/:id/permissions', access: [{ methods: ['GET'], minRole: Role.TEACHER }] diff --git a/src/routes/event-handlers/joinRoom.handler.ts b/src/routes/event-handlers/joinRoom.handler.ts index 92f9d36..c11508f 100644 --- a/src/routes/event-handlers/joinRoom.handler.ts +++ b/src/routes/event-handlers/joinRoom.handler.ts @@ -3,11 +3,10 @@ import { ClientToServerEvents, IoClientEvent, ServerToClientEvents } from '../so import type { DefaultEventsMap, Socket } from 'socket.io'; import prisma from '../../prisma.js'; import StudentGroup from '../../models/StudentGroup.js'; -import onStreamUpdate, { onStreamDynamicRoomUpdate } from './streamUpdate.handler.js'; +import { onStreamDynamicRoomUpdate } from './streamUpdate.handler.js'; import DocumentRoot from '../../models/DocumentRoot.js'; import { highestAccess, RWAccess } from '../../helpers/accessPolicy.js'; import { Role } from '../../models/User.js'; -import Logger from '../../utils/logger.js'; type SocketType = Socket; const isDocumentRoot = (roomId: string) => { @@ -15,13 +14,13 @@ const isDocumentRoot = (roomId: string) => { }; const findDocumentRoot = (user: User, roomId: string) => { - return DocumentRoot.getPermissions(user, roomId).then((res) => { - if (!res) { + return DocumentRoot.getPermissions(user, [roomId]).then((res) => { + if (!res || res.length !== 1) { return false; } else { const access = new Set([ - ...res.groupPermissions.map((p) => p.access), - ...res.userPermissions.map((p) => p.access) + ...res[0].groupPermissions.map((p) => p.access), + ...res[0].userPermissions.map((p) => p.access) ]); const current = highestAccess(access); return RWAccess.has(current); diff --git a/src/routes/router.ts b/src/routes/router.ts index 9d84e33..1a7da43 100644 --- a/src/routes/router.ts +++ b/src/routes/router.ts @@ -33,6 +33,7 @@ import { findMany as findManyDocumentRoots, update as updateDocumentRoot, permissions as allPermissions, + singlePermissions as allPermissionsFor, findManyFor as findManyDocumentRootsFor, findMultipleFor as findMultipleDocumentRootsFor, allDocuments, @@ -99,12 +100,15 @@ router.delete('/permissions/group/:id', deleteStudentGroupPermission); router.get('/documentRoots', findManyDocumentRoots); router.get('/documentRoots/:id', findDocumentRoot); +// TODO: remove this endpoint once the permissions [POST]/documentRoots/permissions endpoint is established and clients are updated +router.get('/documentRoots/:id/permissions', allPermissionsFor); +// order matters here! /documentRoots/:id would match /documentRoots/:id/permissions if it was placed before +router.post('/documentRoots/permissions', allPermissions); router.post('/documentRoots/:id', createDocumentRoot); router.put('/documentRoots/:id', updateDocumentRoot); router.delete('/documentRoots/:id', deleteDocumentRoot); -router.get('/documentRoots/:id/permissions', allPermissions); -router.post('/documents', createDocument); +router.post('/documents', createDocument); /** * TODO: remove once /documents/multiple is established and clients are updated *