diff --git a/apps/server/src/modules/board/service/column-board.service.ts b/apps/server/src/modules/board/service/column-board.service.ts index 0c244998036..da7e2bea707 100644 --- a/apps/server/src/modules/board/service/column-board.service.ts +++ b/apps/server/src/modules/board/service/column-board.service.ts @@ -43,6 +43,8 @@ const DEFAULT_TEACHER_PERMISSIONS = [ Permission.BOARD_ELEMENT_CREATE, Permission.BOARD_ELEMENT_MOVE, + Permission.BOARD_ELEMENT_DELETE, + Permission.BOARD_ELEMENT_UPDATE, ]; const DEFAULT_SUBSTITUTE_TEACHER_PERMISSIONS = DEFAULT_TEACHER_PERMISSIONS; diff --git a/apps/server/src/modules/board/service/content-element.service.ts b/apps/server/src/modules/board/service/content-element.service.ts index 6c6caca31a7..9ee5b9c29d8 100644 --- a/apps/server/src/modules/board/service/content-element.service.ts +++ b/apps/server/src/modules/board/service/content-element.service.ts @@ -3,14 +3,17 @@ import { AnyBoardDo, AnyContentElementDo, Card, + ColumnBoard, ContentElementFactory, ContentElementType, EntityId, isAnyContentElement, + Permission, PermissionContextEntity, SubmissionItem, + UserDelta, } from '@shared/domain'; -import { PermissionContextRepo } from '@shared/repo'; +import { CourseRepo, PermissionContextRepo } from '@shared/repo'; import { ObjectId } from 'bson'; import { AnyElementContentBody } from '../controller/dto'; import { BoardDoRepo } from '../repo'; @@ -23,7 +26,8 @@ export class ContentElementService { private readonly boardDoRepo: BoardDoRepo, private readonly boardDoService: BoardDoService, private readonly contentElementFactory: ContentElementFactory, - private readonly permissionCtxRepo: PermissionContextRepo + private readonly permissionCtxRepo: PermissionContextRepo, + private readonly courseRepo: CourseRepo ) {} async findById(elementId: EntityId): Promise { @@ -45,12 +49,34 @@ export class ContentElementService { } async pocCreateElementPermissionCtx(element: AnyContentElementDo, parentContext: PermissionContextEntity) { - const permissionCtxEntity = new PermissionContextEntity({ - name: 'Element permission context', - parentContext, - contextReference: new ObjectId(element.id), - }); - await this.permissionCtxRepo.save(permissionCtxEntity); + if (element instanceof SubmissionItem) { + // NOTE: this will be simplified once we have user groups + const rootId = (await this.boardDoRepo.getAncestorIds(element))[0]; + const columnBoard = await this.boardDoRepo.findByClassAndId(ColumnBoard, rootId); + const course = await this.courseRepo.findById(columnBoard.context.id); + const updatedStudentsPermissions = course.students.getItems().map((student) => { + return { + userId: student.id, + includedPermissions: [Permission.BOARD_ELEMENT_CAN_SUBMIT], + excludedPermissions: [], + }; + }); + + const permissionCtxEntity = new PermissionContextEntity({ + name: 'Element permission context', + parentContext, + contextReference: new ObjectId(element.id), + userDelta: new UserDelta(updatedStudentsPermissions), + }); + await this.permissionCtxRepo.save(permissionCtxEntity); + } else { + const permissionCtxEntity = new PermissionContextEntity({ + name: 'Element permission context', + parentContext, + contextReference: new ObjectId(element.id), + }); + await this.permissionCtxRepo.save(permissionCtxEntity); + } } async create(parent: Card | SubmissionItem, type: ContentElementType): Promise { diff --git a/apps/server/src/modules/board/service/submission-item.service.ts b/apps/server/src/modules/board/service/submission-item.service.ts index 0fc67ba694f..53d697a3a7a 100644 --- a/apps/server/src/modules/board/service/submission-item.service.ts +++ b/apps/server/src/modules/board/service/submission-item.service.ts @@ -1,15 +1,61 @@ import { ObjectId } from 'bson'; import { Injectable, NotFoundException, UnprocessableEntityException } from '@nestjs/common'; -import { EntityId, isSubmissionContainerElement, SubmissionContainerElement, SubmissionItem } from '@shared/domain'; +import { + ColumnBoard, + EntityId, + isSubmissionContainerElement, + Permission, + PermissionContextEntity, + SubmissionContainerElement, + SubmissionItem, + UserDelta, +} from '@shared/domain'; import { ValidationError } from '@shared/common'; +import { CourseRepo, PermissionContextRepo } from '@shared/repo'; import { BoardDoRepo } from '../repo'; import { BoardDoService } from './board-do.service'; @Injectable() export class SubmissionItemService { - constructor(private readonly boardDoRepo: BoardDoRepo, private readonly boardDoService: BoardDoService) {} + constructor( + private readonly boardDoRepo: BoardDoRepo, + private readonly boardDoService: BoardDoService, + private readonly permissionCtxRepo: PermissionContextRepo, + private readonly courseRepo: CourseRepo + ) {} + + private async pocCreateSubmissionItemPermissionCtx( + userId: EntityId, + submissionContainer: SubmissionContainerElement, + submissionItemId: EntityId + ) { + // NOTE: this will be simplified once we have user groups + const parentContext = await this.permissionCtxRepo.findByContextReference(submissionContainer.id); + + const rootId = (await this.boardDoRepo.getAncestorIds(submissionContainer))[0]; + const columnBoard = await this.boardDoRepo.findByClassAndId(ColumnBoard, rootId); + const course = await this.courseRepo.findById(columnBoard.context.id); + const revokeStudentsPermissions = course.students + .getItems() + .filter((student) => student.id !== userId) + .map((student) => { + return { + userId: student.id, + includedPermissions: [], + excludedPermissions: [Permission.BOARD_ELEMENT_CAN_SUBMIT, Permission.BOARD_READ], + }; + }); + + const permissionCtxEntity = new PermissionContextEntity({ + name: 'Element permission context', + parentContext, + contextReference: new ObjectId(submissionItemId), + userDelta: new UserDelta(revokeStudentsPermissions), + }); + await this.permissionCtxRepo.save(permissionCtxEntity); + } async findById(id: EntityId): Promise { const element = await this.boardDoRepo.findById(id); @@ -35,7 +81,7 @@ export class SubmissionItemService { }); submissionContainer.addChild(submissionItem); - + await this.pocCreateSubmissionItemPermissionCtx(userId, submissionContainer, submissionItem.id); await this.boardDoRepo.save(submissionContainer.children, submissionContainer); return submissionItem; diff --git a/apps/server/src/modules/board/uc/element.uc.ts b/apps/server/src/modules/board/uc/element.uc.ts index 0c060ab7a62..641de0ff9dd 100644 --- a/apps/server/src/modules/board/uc/element.uc.ts +++ b/apps/server/src/modules/board/uc/element.uc.ts @@ -5,6 +5,7 @@ import { EntityId, isSubmissionContainerElement, isSubmissionItem, + Permission, SubmissionItem, UserRoleEnum, } from '@shared/domain'; @@ -35,14 +36,20 @@ export class ElementUc extends BaseUc { elementId: EntityId, content: AnyElementContentBody ): Promise { - const element = await this.getElementWithWritePermission(userId, elementId); + await this.pocCheckPermission(userId, elementId, [Permission.BOARD_ELEMENT_UPDATE]); + + const element = await this.elementService.findById(elementId); + // const element = await this.getElementWithWritePermission(userId, elementId); await this.elementService.update(element, content); return element; } async deleteElement(userId: EntityId, elementId: EntityId): Promise { - const element = await this.getElementWithWritePermission(userId, elementId); + await this.pocCheckPermission(userId, elementId, [Permission.BOARD_ELEMENT_DELETE]); + + const element = await this.elementService.findById(elementId); + // const element = await this.getElementWithWritePermission(userId, elementId); await this.elementService.delete(element); } diff --git a/apps/server/src/shared/domain/entity/permission-context.entity.ts b/apps/server/src/shared/domain/entity/permission-context.entity.ts index 71d03a93b24..de9bd5caebb 100644 --- a/apps/server/src/shared/domain/entity/permission-context.entity.ts +++ b/apps/server/src/shared/domain/entity/permission-context.entity.ts @@ -83,4 +83,24 @@ export class PermissionContextEntity extends BaseEntityWithTimestamps { return [...new Set(finalPermissions)]; } + + public async resolveFullPermissionMatrix(): Promise> { + const parent = await this.parentContext; + let parentPermissionMatrix: Map = new Map(); + if (parent) { + parentPermissionMatrix = await parent.resolveFullPermissionMatrix(); + } + + const permissionMatrix = new Map(parentPermissionMatrix); + + Object.entries(this.userDelta).forEach(([userId, { includedPermissions, excludedPermissions }]) => { + const parentPermissions = parentPermissionMatrix.get(userId) ?? []; + const permissions = includedPermissions + .concat(parentPermissions) + .filter((permission) => !excludedPermissions.includes(permission)); + permissionMatrix.set(userId, permissions); + }); + + return permissionMatrix; + } } diff --git a/apps/server/src/shared/domain/interface/permission.enum.ts b/apps/server/src/shared/domain/interface/permission.enum.ts index 507b15f1cf3..ea592eedbea 100644 --- a/apps/server/src/shared/domain/interface/permission.enum.ts +++ b/apps/server/src/shared/domain/interface/permission.enum.ts @@ -19,7 +19,10 @@ export enum Permission { BOARD_CARD_MOVE = 'BOARD_CARD_MOVE', BOARD_ELEMENT_CREATE = 'BOARD_ELEMENT_CREATE', + BOARD_ELEMENT_UPDATE = 'BOARD_ELEMENT_UPDATE', + BOARD_ELEMENT_DELETE = 'BOARD_ELEMENT_DELETE', BOARD_ELEMENT_MOVE = 'BOARD_ELEMENT_MOVE', + BOARD_ELEMENT_CAN_SUBMIT = 'BOARD_SUBMISSION_CAN_SUBMIT', /** POC END: BOARD PERMISSIONS */ ACCOUNT_CREATE = 'ACCOUNT_CREATE', diff --git a/apps/server/src/shared/repo/permission-context/permission-context.repo.ts b/apps/server/src/shared/repo/permission-context/permission-context.repo.ts index d0fd632214d..a13416fc38c 100644 --- a/apps/server/src/shared/repo/permission-context/permission-context.repo.ts +++ b/apps/server/src/shared/repo/permission-context/permission-context.repo.ts @@ -1,6 +1,6 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; -import { EntityId, IPermissionContextProperties, PermissionContextEntity } from '@shared/domain'; +import { EntityId, PermissionContextEntity } from '@shared/domain'; import { BaseRepo } from '../base.repo'; // TODO: add test