From 664a97bf6f0f9b9905597ad7a4cb295db29b2b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= <103562092+MarvinOehlerkingCap@users.noreply.github.com> Date: Mon, 23 Oct 2023 15:44:35 +0200 Subject: [PATCH] N21-1395 Fix authorization not working for CTL board elements after authorization refactoring (#4491) --- .../modules/tool/common/common-tool.module.ts | 25 +- .../common/mapper/context-type.mapper.spec.ts | 11 - .../tool/common/mapper/context-type.mapper.ts | 13 - .../src/modules/tool/common/mapper/index.ts | 2 +- .../mapper/tool-status-response.mapper.ts | 2 +- .../tool/common/uc/tool-permission-helper.ts | 43 ++- .../common/uc/tool-permissions-helper.spec.ts | 129 +++++--- .../api-test/tool-context.api.spec.ts | 12 +- .../controller/tool-context.controller.ts | 4 +- .../context-external-tool-request.mapper.ts | 5 +- ...ontext-external-tool-validation.service.ts | 5 +- .../uc/context-external-tool.uc.spec.ts | 291 ++++++++++++++---- .../uc/context-external-tool.uc.ts | 62 +++- .../src/modules/tool/tool-api.module.ts | 12 +- .../board/board-do-authorizable.factory.ts | 14 + ...ts => external-tool-element.do.factory.ts} | 0 .../factory/domainobject/board/index.ts | 2 +- 17 files changed, 429 insertions(+), 203 deletions(-) delete mode 100644 apps/server/src/modules/tool/common/mapper/context-type.mapper.spec.ts delete mode 100644 apps/server/src/modules/tool/common/mapper/context-type.mapper.ts create mode 100644 apps/server/src/shared/testing/factory/domainobject/board/board-do-authorizable.factory.ts rename apps/server/src/shared/testing/factory/domainobject/board/{external-tool.do.factory.ts => external-tool-element.do.factory.ts} (100%) diff --git a/apps/server/src/modules/tool/common/common-tool.module.ts b/apps/server/src/modules/tool/common/common-tool.module.ts index f56f595a99c..57375c67e96 100644 --- a/apps/server/src/modules/tool/common/common-tool.module.ts +++ b/apps/server/src/modules/tool/common/common-tool.module.ts @@ -1,28 +1,13 @@ -import { forwardRef, Module } from '@nestjs/common'; +import { LegacySchoolModule } from '@modules/legacy-school'; +import { Module } from '@nestjs/common'; import { ContextExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; -import { AuthorizationModule } from '@modules/authorization'; -import { LegacySchoolModule } from '@modules/legacy-school'; -import { LearnroomModule } from '@modules/learnroom'; import { CommonToolService, CommonToolValidationService } from './service'; -import { ToolPermissionHelper } from './uc/tool-permission-helper'; @Module({ - imports: [LoggerModule, forwardRef(() => AuthorizationModule), LegacySchoolModule, LearnroomModule], + imports: [LoggerModule, LegacySchoolModule], // TODO: make deletion of entities cascading, adjust ExternalToolService.deleteExternalTool and remove the repos from here - providers: [ - CommonToolService, - CommonToolValidationService, - ToolPermissionHelper, - SchoolExternalToolRepo, - ContextExternalToolRepo, - ], - exports: [ - CommonToolService, - CommonToolValidationService, - ToolPermissionHelper, - SchoolExternalToolRepo, - ContextExternalToolRepo, - ], + providers: [CommonToolService, CommonToolValidationService, SchoolExternalToolRepo, ContextExternalToolRepo], + exports: [CommonToolService, CommonToolValidationService, SchoolExternalToolRepo, ContextExternalToolRepo], }) export class CommonToolModule {} diff --git a/apps/server/src/modules/tool/common/mapper/context-type.mapper.spec.ts b/apps/server/src/modules/tool/common/mapper/context-type.mapper.spec.ts deleted file mode 100644 index b05f50fc46c..00000000000 --- a/apps/server/src/modules/tool/common/mapper/context-type.mapper.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AuthorizableReferenceType } from '@modules/authorization/domain'; -import { ToolContextType } from '../enum'; -import { ContextTypeMapper } from './context-type.mapper'; - -describe('context-type.mapper', () => { - it('should map ToolContextType.COURSE to AuthorizableReferenceType.Course', () => { - const mappedCourse = ContextTypeMapper.mapContextTypeToAllowedAuthorizationEntityType(ToolContextType.COURSE); - - expect(mappedCourse).toEqual(AuthorizableReferenceType.Course); - }); -}); diff --git a/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts b/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts deleted file mode 100644 index 3ae6902c232..00000000000 --- a/apps/server/src/modules/tool/common/mapper/context-type.mapper.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { AuthorizableReferenceType } from '@modules/authorization/domain/'; -import { ToolContextType } from '../enum'; - -const typeMapping: Record = { - [ToolContextType.COURSE]: AuthorizableReferenceType.Course, - [ToolContextType.BOARD_ELEMENT]: AuthorizableReferenceType.BoardNode, -}; - -export class ContextTypeMapper { - static mapContextTypeToAllowedAuthorizationEntityType(type: ToolContextType): AuthorizableReferenceType { - return typeMapping[type]; - } -} diff --git a/apps/server/src/modules/tool/common/mapper/index.ts b/apps/server/src/modules/tool/common/mapper/index.ts index 3da6b0fa28b..b6be27cdc1f 100644 --- a/apps/server/src/modules/tool/common/mapper/index.ts +++ b/apps/server/src/modules/tool/common/mapper/index.ts @@ -1 +1 @@ -export * from './context-type.mapper'; +export * from './tool-status-response.mapper'; diff --git a/apps/server/src/modules/tool/common/mapper/tool-status-response.mapper.ts b/apps/server/src/modules/tool/common/mapper/tool-status-response.mapper.ts index c199fc6f307..0c16ca50e9a 100644 --- a/apps/server/src/modules/tool/common/mapper/tool-status-response.mapper.ts +++ b/apps/server/src/modules/tool/common/mapper/tool-status-response.mapper.ts @@ -1,4 +1,4 @@ -import { ToolConfigurationStatusResponse } from '../../context-external-tool/controller/dto/tool-configuration-status.response'; +import { ToolConfigurationStatusResponse } from '../../context-external-tool/controller/dto'; import { ToolConfigurationStatus } from '../enum'; export const statusMapping: Record = { diff --git a/apps/server/src/modules/tool/common/uc/tool-permission-helper.ts b/apps/server/src/modules/tool/common/uc/tool-permission-helper.ts index 542903bc4be..525c8c5d3b6 100644 --- a/apps/server/src/modules/tool/common/uc/tool-permission-helper.ts +++ b/apps/server/src/modules/tool/common/uc/tool-permission-helper.ts @@ -1,11 +1,13 @@ -import { forwardRef, Inject, Injectable } from '@nestjs/common'; -import { Course, EntityId, LegacySchoolDo, User } from '@shared/domain'; -import { AuthorizationContext, AuthorizationService } from '@modules/authorization'; -import { LegacySchoolService } from '@modules/legacy-school'; +import { AuthorizationContext, AuthorizationService, ForbiddenLoggableException } from '@modules/authorization'; +import { AuthorizableReferenceType } from '@modules/authorization/domain'; +import { BoardDoAuthorizableService, ContentElementService } from '@modules/board'; import { CourseService } from '@modules/learnroom'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { BoardDoAuthorizable, Course, EntityId, LegacySchoolDo, User } from '@shared/domain'; import { ContextExternalTool } from '../../context-external-tool/domain'; import { SchoolExternalTool } from '../../school-external-tool/domain'; -// import { ContextTypeMapper } from '../mapper'; +import { ToolContextType } from '../enum'; @Injectable() export class ToolPermissionHelper { @@ -15,7 +17,9 @@ export class ToolPermissionHelper { // invalid dependency on this place it is in UC layer in a other module // loading of ressources should be part of service layer // if it must resolve different loadings based on the request it can be added in own service and use in UC - private readonly courseService: CourseService + private readonly courseService: CourseService, + private readonly boardElementService: ContentElementService, + private readonly boardService: BoardDoAuthorizableService ) {} // TODO build interface to get contextDO by contextType @@ -24,19 +28,24 @@ export class ToolPermissionHelper { contextExternalTool: ContextExternalTool, context: AuthorizationContext ): Promise { - // loading of ressources should be part of the UC -> unnessasary awaits - const [authorizableUser, course]: [User, Course] = await Promise.all([ - this.authorizationService.getUserWithPermissions(userId), - this.courseService.findById(contextExternalTool.contextRef.id), - ]); + const authorizableUser = await this.authorizationService.getUserWithPermissions(userId); - if (contextExternalTool.id) { - this.authorizationService.checkPermission(authorizableUser, contextExternalTool, context); - } + this.authorizationService.checkPermission(authorizableUser, contextExternalTool, context); + + if (contextExternalTool.contextRef.type === ToolContextType.COURSE) { + // loading of ressources should be part of the UC -> unnessasary awaits + const course: Course = await this.courseService.findById(contextExternalTool.contextRef.id); + + this.authorizationService.checkPermission(authorizableUser, course, context); + } else if (contextExternalTool.contextRef.type === ToolContextType.BOARD_ELEMENT) { + const boardElement = await this.boardElementService.findById(contextExternalTool.contextRef.id); - // const type = ContextTypeMapper.mapContextTypeToAllowedAuthorizationEntityType(contextExternalTool.contextRef.type); - // no different types possible until it is fixed. - this.authorizationService.checkPermission(authorizableUser, course, context); + const board: BoardDoAuthorizable = await this.boardService.getBoardAuthorizable(boardElement); + + this.authorizationService.checkPermission(authorizableUser, board, context); + } else { + throw new ForbiddenLoggableException(userId, AuthorizableReferenceType.ContextExternalToolEntity, context); + } } public async ensureSchoolPermissions( diff --git a/apps/server/src/modules/tool/common/uc/tool-permissions-helper.spec.ts b/apps/server/src/modules/tool/common/uc/tool-permissions-helper.spec.ts index ad697a94694..aace35579db 100644 --- a/apps/server/src/modules/tool/common/uc/tool-permissions-helper.spec.ts +++ b/apps/server/src/modules/tool/common/uc/tool-permissions-helper.spec.ts @@ -1,29 +1,41 @@ -import { Test, TestingModule } from '@nestjs/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { + AuthorizationContext, + AuthorizationContextBuilder, + AuthorizationService, + ForbiddenLoggableException, +} from '@modules/authorization'; +import { AuthorizableReferenceType } from '@modules/authorization/domain'; +import { BoardDoAuthorizableService, ContentElementService } from '@modules/board'; +import { CourseService } from '@modules/learnroom'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { ForbiddenException } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { BoardDoAuthorizable, ExternalToolElement, LegacySchoolDo, Permission } from '@shared/domain'; import { contextExternalToolFactory, courseFactory, + externalToolElementFactory, legacySchoolDoFactory, schoolExternalToolFactory, setupEntities, userFactory, } from '@shared/testing'; -import { Permission, LegacySchoolDo } from '@shared/domain'; -import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; -import { LegacySchoolService } from '@modules/legacy-school'; -import { ForbiddenException } from '@nestjs/common'; -import { CourseService } from '@modules/learnroom'; -import { ContextExternalTool } from '../../context-external-tool/domain'; -import { ToolPermissionHelper } from './tool-permission-helper'; +import { boardDoAuthorizableFactory } from '@shared/testing/factory/domainobject/board/board-do-authorizable.factory'; +import { ContextExternalTool, ContextRef } from '../../context-external-tool/domain'; import { SchoolExternalTool } from '../../school-external-tool/domain'; +import { ToolContextType } from '../enum'; +import { ToolPermissionHelper } from './tool-permission-helper'; describe('ToolPermissionHelper', () => { let module: TestingModule; let helper: ToolPermissionHelper; let authorizationService: DeepMocked; - let courseService: DeepMocked; let schoolService: DeepMocked; + let courseService: DeepMocked; + let contentElementService: DeepMocked; + let boardDoAuthorizableService: DeepMocked; beforeAll(async () => { await setupEntities(); @@ -34,21 +46,31 @@ describe('ToolPermissionHelper', () => { provide: AuthorizationService, useValue: createMock(), }, + { + provide: LegacySchoolService, + useValue: createMock(), + }, { provide: CourseService, useValue: createMock(), }, { - provide: LegacySchoolService, - useValue: createMock(), + provide: ContentElementService, + useValue: createMock(), + }, + { + provide: BoardDoAuthorizableService, + useValue: createMock(), }, ], }).compile(); helper = module.get(ToolPermissionHelper); authorizationService = module.get(AuthorizationService); - courseService = module.get(CourseService); schoolService = module.get(LegacySchoolService); + courseService = module.get(CourseService); + contentElementService = module.get(ContentElementService); + boardDoAuthorizableService = module.get(BoardDoAuthorizableService); }); afterAll(async () => { @@ -60,16 +82,20 @@ describe('ToolPermissionHelper', () => { }); describe('ensureContextPermissions', () => { - describe('when context external tool with id is given', () => { + describe('when a context external tool for context "course" is given', () => { const setup = () => { const user = userFactory.buildWithId(); const course = courseFactory.buildWithId(); - const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId(); + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + contextRef: new ContextRef({ + id: course.id, + type: ToolContextType.COURSE, + }), + }); const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]); - courseService.findById.mockResolvedValueOnce(course); authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); - authorizationService.checkPermission.mockReturnValueOnce().mockReturnValueOnce(); + courseService.findById.mockResolvedValueOnce(course); return { user, @@ -88,70 +114,97 @@ describe('ToolPermissionHelper', () => { expect(authorizationService.checkPermission).toHaveBeenNthCalledWith(1, user, contextExternalTool, context); expect(authorizationService.checkPermission).toHaveBeenNthCalledWith(2, user, course, context); }); + }); - it('should return undefined', async () => { - const { user, contextExternalTool, context } = setup(); + describe('when a context external tool for context "board element" is given', () => { + const setup = () => { + const user = userFactory.buildWithId(); + const externalToolElement: ExternalToolElement = externalToolElementFactory.build(); + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + contextRef: new ContextRef({ + id: externalToolElement.id, + type: ToolContextType.BOARD_ELEMENT, + }), + }); + const board: BoardDoAuthorizable = boardDoAuthorizableFactory.build(); + const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]); - const result = await helper.ensureContextPermissions(user.id, contextExternalTool, context); + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); + contentElementService.findById.mockResolvedValueOnce(externalToolElement); + boardDoAuthorizableService.getBoardAuthorizable.mockResolvedValueOnce(board); - expect(result).toBeUndefined(); + return { + user, + board, + contextExternalTool, + context, + }; + }; + + it('should check permission for context external tool', async () => { + const { user, board, contextExternalTool, context } = setup(); + + await helper.ensureContextPermissions(user.id, contextExternalTool, context); + + expect(authorizationService.checkPermission).toHaveBeenCalledTimes(2); + expect(authorizationService.checkPermission).toHaveBeenNthCalledWith(1, user, contextExternalTool, context); + expect(authorizationService.checkPermission).toHaveBeenNthCalledWith(2, user, board, context); }); }); - describe('when context external tool without id is given', () => { + describe('when the context external tool has an unkown context', () => { const setup = () => { const user = userFactory.buildWithId(); - const course = courseFactory.buildWithId(); - const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build(); + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + contextRef: { + type: 'unknown type' as unknown as ToolContextType, + }, + }); const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]); - courseService.findById.mockResolvedValueOnce(course); authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); return { user, - course, contextExternalTool, context, }; }; - it('should check permission for context external tool', async () => { - const { user, course, contextExternalTool, context } = setup(); - - await helper.ensureContextPermissions(user.id, contextExternalTool, context); + it('should throw a forbidden loggable exception', async () => { + const { user, contextExternalTool, context } = setup(); - expect(authorizationService.checkPermission).toHaveBeenCalledTimes(1); - expect(authorizationService.checkPermission).toHaveBeenCalledWith(user, course, context); + await expect(helper.ensureContextPermissions(user.id, contextExternalTool, context)).rejects.toThrowError( + new ForbiddenLoggableException(user.id, AuthorizableReferenceType.ContextExternalToolEntity, context) + ); }); }); describe('when user is unauthorized', () => { const setup = () => { const user = userFactory.buildWithId(); - const course = courseFactory.buildWithId(); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId(); const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_USER]); + const error = new ForbiddenException(); - courseService.findById.mockResolvedValueOnce(course); authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); authorizationService.checkPermission.mockImplementationOnce(() => { - throw new ForbiddenException(); + throw error; }); return { user, - course, contextExternalTool, context, + error, }; }; - it('should check permission for context external tool', async () => { - const { user, contextExternalTool, context } = setup(); + it('should check permission for context external tool and fail', async () => { + const { user, contextExternalTool, context, error } = setup(); await expect(helper.ensureContextPermissions(user.id, contextExternalTool, context)).rejects.toThrowError( - new ForbiddenException() + error ); }); }); diff --git a/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts b/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts index e2dbe6ec9ac..eb570130c4e 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts @@ -1,4 +1,5 @@ import { EntityManager, MikroORM } from '@mikro-orm/core'; +import { ServerTestModule } from '@modules/server'; import { HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Account, Course, Permission, SchoolEntity, User } from '@shared/domain'; @@ -15,18 +16,17 @@ import { UserAndAccountTestFactory, userFactory, } from '@shared/testing'; -import { ServerTestModule } from '@modules/server'; import { ObjectId } from 'bson'; import { CustomParameterScope, ToolContextType } from '../../../common/enum'; +import { ExternalToolEntity } from '../../../external-tool/entity'; +import { CustomParameterEntryResponse } from '../../../school-external-tool/controller/dto'; import { SchoolExternalToolEntity } from '../../../school-external-tool/entity'; +import { ContextExternalToolEntity, ContextExternalToolType } from '../../entity'; import { ContextExternalToolPostParams, ContextExternalToolResponse, ContextExternalToolSearchListResponse, } from '../dto'; -import { ContextExternalToolEntity, ContextExternalToolType } from '../../entity'; -import { ExternalToolEntity } from '../../../external-tool/entity'; -import { CustomParameterEntryResponse } from '../../../school-external-tool/controller/dto'; describe('ToolContextController (API)', () => { let app: INestApplication; @@ -116,8 +116,8 @@ describe('ToolContextController (API)', () => { describe('when user is not authorized for the requested context', () => { const setup = async () => { - const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); const school = schoolFactory.build(); + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({ school }); const course = courseFactory.build({ teachers: [teacherUser] }); const otherCourse = courseFactory.build(); const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.build({ @@ -130,7 +130,7 @@ describe('ToolContextController (API)', () => { em.clear(); const postParams: ContextExternalToolPostParams = { - schoolToolId: school.id, + schoolToolId: schoolExternalToolEntity.id, contextId: otherCourse.id, contextType: ToolContextType.COURSE, parameters: [], diff --git a/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts b/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts index a80ad3f234f..20d8b96f795 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/tool-context.controller.ts @@ -1,3 +1,4 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; import { ApiCreatedResponse, @@ -12,7 +13,6 @@ import { } from '@nestjs/swagger'; import { ValidationError } from '@shared/common'; import { LegacyLogger } from '@src/core/logger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { ContextExternalTool } from '../domain'; import { ContextExternalToolRequestMapper, ContextExternalToolResponseMapper } from '../mapper'; import { ContextExternalToolUc } from '../uc'; @@ -50,6 +50,7 @@ export class ToolContextController { const createdTool: ContextExternalTool = await this.contextExternalToolUc.createContextExternalTool( currentUser.userId, + currentUser.schoolId, contextExternalTool ); @@ -152,6 +153,7 @@ export class ToolContextController { const updatedTool: ContextExternalTool = await this.contextExternalToolUc.updateContextExternalTool( currentUser.userId, + currentUser.schoolId, params.contextExternalToolId, contextExternalTool ); diff --git a/apps/server/src/modules/tool/context-external-tool/mapper/context-external-tool-request.mapper.ts b/apps/server/src/modules/tool/context-external-tool/mapper/context-external-tool-request.mapper.ts index 45f912bf52c..951559afbcc 100644 --- a/apps/server/src/modules/tool/context-external-tool/mapper/context-external-tool-request.mapper.ts +++ b/apps/server/src/modules/tool/context-external-tool/mapper/context-external-tool-request.mapper.ts @@ -1,12 +1,11 @@ -import { ContextExternalToolPostParams } from '../controller/dto'; +import { CustomParameterEntry } from '../../common/domain'; import { CustomParameterEntryParam } from '../../school-external-tool/controller/dto'; +import { ContextExternalToolPostParams } from '../controller/dto'; import { ContextExternalToolDto } from '../uc/dto/context-external-tool.types'; -import { CustomParameterEntry } from '../../common/domain'; export class ContextExternalToolRequestMapper { static mapContextExternalToolRequest(request: ContextExternalToolPostParams): ContextExternalToolDto { return { - id: '', schoolToolRef: { schoolToolId: request.schoolToolId, }, diff --git a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.ts b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.ts index 3777273d18e..2cce83a08b2 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.ts @@ -6,7 +6,6 @@ import { ExternalToolService } from '../../external-tool/service'; import { SchoolExternalTool } from '../../school-external-tool/domain'; import { SchoolExternalToolService } from '../../school-external-tool/service'; import { ContextExternalTool } from '../domain'; -import { ContextExternalToolDto } from '../uc/dto/context-external-tool.types'; import { ContextExternalToolService } from './context-external-tool.service'; @Injectable() @@ -18,9 +17,7 @@ export class ContextExternalToolValidationService { private readonly commonToolValidationService: CommonToolValidationService ) {} - async validate(toValidate: ContextExternalToolDto): Promise { - const contextExternalTool: ContextExternalTool = new ContextExternalTool(toValidate); - + async validate(contextExternalTool: ContextExternalTool): Promise { await this.checkDuplicateInContext(contextExternalTool); const loadedSchoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById( diff --git a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts index 648ccb491fa..0fcca404049 100644 --- a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.spec.ts @@ -1,18 +1,20 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; -import { ForbiddenException, UnprocessableEntityException } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { EntityId, Permission, User } from '@shared/domain'; -import { contextExternalToolFactory, setupEntities, userFactory } from '@shared/testing'; -import { LegacyLogger } from '@src/core/logger'; import { Action, AuthorizationContextBuilder, AuthorizationService, ForbiddenLoggableException, } from '@modules/authorization'; +import { AuthorizableReferenceType } from '@modules/authorization/domain'; +import { ForbiddenException, UnprocessableEntityException } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { EntityId, Permission, User } from '@shared/domain'; +import { contextExternalToolFactory, schoolExternalToolFactory, setupEntities, userFactory } from '@shared/testing'; import { ToolContextType } from '../../common/enum'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; +import { SchoolExternalTool } from '../../school-external-tool/domain'; +import { SchoolExternalToolService } from '../../school-external-tool/service'; import { ContextExternalTool } from '../domain'; import { ContextExternalToolService, ContextExternalToolValidationService } from '../service'; import { ContextExternalToolUc } from './context-external-tool.uc'; @@ -21,6 +23,7 @@ describe('ContextExternalToolUc', () => { let module: TestingModule; let uc: ContextExternalToolUc; + let schoolExternalToolService: DeepMocked; let contextExternalToolService: DeepMocked; let contextExternalToolValidationService: DeepMocked; let toolPermissionHelper: DeepMocked; @@ -31,6 +34,10 @@ describe('ContextExternalToolUc', () => { module = await Test.createTestingModule({ providers: [ ContextExternalToolUc, + { + provide: SchoolExternalToolService, + useValue: createMock(), + }, { provide: ContextExternalToolService, useValue: createMock(), @@ -39,10 +46,6 @@ describe('ContextExternalToolUc', () => { provide: ContextExternalToolValidationService, useValue: createMock(), }, - { - provide: LegacyLogger, - useValue: createMock(), - }, { provide: ToolPermissionHelper, useValue: createMock(), @@ -55,6 +58,7 @@ describe('ContextExternalToolUc', () => { }).compile(); uc = module.get(ContextExternalToolUc); + schoolExternalToolService = module.get(SchoolExternalToolService); contextExternalToolService = module.get(ContextExternalToolService); contextExternalToolValidationService = module.get(ContextExternalToolValidationService); toolPermissionHelper = module.get(ToolPermissionHelper); @@ -73,36 +77,46 @@ describe('ContextExternalToolUc', () => { describe('when contextExternalTool is given and user has permission ', () => { const setup = () => { const userId: EntityId = 'userId'; + const schoolId: EntityId = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId, + }); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ displayName: 'Course', + schoolToolRef: { + schoolToolId: schoolExternalTool.id, + schoolId, + }, contextRef: { id: 'contextId', type: ToolContextType.COURSE, }, }); - toolPermissionHelper.ensureContextPermissions.mockResolvedValue(Promise.resolve()); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); contextExternalToolService.saveContextExternalTool.mockResolvedValue(contextExternalTool); return { contextExternalTool, userId, + schoolId, }; }; it('should call contextExternalToolService', async () => { - const { contextExternalTool, userId } = setup(); + const { contextExternalTool, userId, schoolId } = setup(); - await uc.createContextExternalTool(userId, contextExternalTool); + await uc.createContextExternalTool(userId, schoolId, contextExternalTool); expect(contextExternalToolService.saveContextExternalTool).toHaveBeenCalledWith(contextExternalTool); }); it('should call contextExternalToolService to ensure permissions', async () => { - const { contextExternalTool, userId } = setup(); + const { contextExternalTool, userId, schoolId } = setup(); - await uc.createContextExternalTool(userId, contextExternalTool); + await uc.createContextExternalTool(userId, schoolId, contextExternalTool); expect(toolPermissionHelper.ensureContextPermissions).toHaveBeenCalledWith( userId, @@ -112,28 +126,82 @@ describe('ContextExternalToolUc', () => { }); it('should call contextExternalToolValidationService', async () => { - const { contextExternalTool, userId } = setup(); + const { contextExternalTool, userId, schoolId } = setup(); - await uc.createContextExternalTool(userId, contextExternalTool); + await uc.createContextExternalTool(userId, schoolId, contextExternalTool); expect(contextExternalToolValidationService.validate).toHaveBeenCalledWith(contextExternalTool); }); it('should return the saved object', async () => { - const { contextExternalTool, userId } = setup(); + const { contextExternalTool, userId, schoolId } = setup(); - const result = await uc.createContextExternalTool(userId, contextExternalTool); + const result = await uc.createContextExternalTool(userId, schoolId, contextExternalTool); expect(result).toEqual(contextExternalTool); }); }); + describe('when the user is from a different school than the school external tool', () => { + const setup = () => { + const userId: EntityId = 'userId'; + const schoolId: EntityId = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId, + }); + + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + displayName: 'Course', + schoolToolRef: { + schoolToolId: schoolExternalTool.id, + schoolId, + }, + contextRef: { + id: 'contextId', + type: ToolContextType.COURSE, + }, + }); + + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); + + return { + contextExternalTool, + userId, + }; + }; + + it('should return UnprocessableEntity and not save', async () => { + const { contextExternalTool, userId } = setup(); + + const func = () => uc.createContextExternalTool(userId, new ObjectId().toHexString(), contextExternalTool); + + await expect(func).rejects.toThrow( + new ForbiddenLoggableException( + userId, + AuthorizableReferenceType.ContextExternalToolEntity, + AuthorizationContextBuilder.write([Permission.CONTEXT_TOOL_ADMIN]) + ) + ); + expect(contextExternalToolService.saveContextExternalTool).not.toHaveBeenCalled(); + }); + }); + describe('when the user does not have permission', () => { const setup = () => { const userId: EntityId = 'userId'; + const schoolId: EntityId = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId, + }); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ displayName: 'Course', + schoolToolRef: { + schoolToolId: schoolExternalTool.id, + schoolId, + }, contextRef: { id: 'contextId', type: ToolContextType.COURSE, @@ -142,19 +210,21 @@ describe('ContextExternalToolUc', () => { const error = new ForbiddenException(); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); toolPermissionHelper.ensureContextPermissions.mockRejectedValue(error); return { contextExternalTool, userId, + schoolId, error, }; }; it('should return forbidden and not save', async () => { - const { contextExternalTool, userId, error } = setup(); + const { contextExternalTool, userId, error, schoolId } = setup(); - const func = () => uc.createContextExternalTool(userId, contextExternalTool); + const func = () => uc.createContextExternalTool(userId, schoolId, contextExternalTool); await expect(func).rejects.toThrow(error); expect(contextExternalToolService.saveContextExternalTool).not.toHaveBeenCalled(); @@ -164,9 +234,18 @@ describe('ContextExternalToolUc', () => { describe('when the validation fails', () => { const setup = () => { const userId: EntityId = 'userId'; + const schoolId: EntityId = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId, + }); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ displayName: 'Course', + schoolToolRef: { + schoolToolId: schoolExternalTool.id, + schoolId, + }, contextRef: { id: 'contextId', type: ToolContextType.COURSE, @@ -175,19 +254,21 @@ describe('ContextExternalToolUc', () => { const error = new UnprocessableEntityException(); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); contextExternalToolValidationService.validate.mockRejectedValue(error); return { contextExternalTool, userId, + schoolId, error, }; }; it('should return UnprocessableEntity and not save', async () => { - const { contextExternalTool, userId, error } = setup(); + const { contextExternalTool, userId, error, schoolId } = setup(); - const func = () => uc.createContextExternalTool(userId, contextExternalTool); + const func = () => uc.createContextExternalTool(userId, schoolId, contextExternalTool); await expect(func).rejects.toThrow(error); expect(contextExternalToolService.saveContextExternalTool).not.toHaveBeenCalled(); @@ -199,40 +280,48 @@ describe('ContextExternalToolUc', () => { describe('when contextExternalTool is given and user has permission ', () => { const setup = () => { const userId: EntityId = 'userId'; + const schoolId: EntityId = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId, + }); - const contextExternalToolId = new ObjectId().toHexString(); - const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId( - { - displayName: 'Course', - contextRef: { - id: 'contextId', - type: ToolContextType.COURSE, - }, + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + displayName: 'Course', + schoolToolRef: { + schoolToolId: schoolExternalTool.id, + schoolId, }, - contextExternalToolId - ); + contextRef: { + id: 'contextId', + type: ToolContextType.COURSE, + }, + }); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); contextExternalToolService.saveContextExternalTool.mockResolvedValue(contextExternalTool); + contextExternalToolService.findById.mockResolvedValueOnce(contextExternalTool); return { contextExternalTool, - contextExternalToolId, + contextExternalToolId: contextExternalTool.id as string, userId, + schoolId, }; }; it('should call contextExternalToolService', async () => { - const { contextExternalTool, contextExternalToolId, userId } = setup(); + const { contextExternalTool, userId, schoolId, contextExternalToolId } = setup(); - await uc.updateContextExternalTool(userId, contextExternalToolId, contextExternalTool); + await uc.updateContextExternalTool(userId, schoolId, contextExternalToolId, contextExternalTool); expect(contextExternalToolService.saveContextExternalTool).toHaveBeenCalledWith(contextExternalTool); }); it('should call contextExternalToolService to ensure permissions', async () => { - const { contextExternalTool, contextExternalToolId, userId } = setup(); + const { contextExternalTool, userId, schoolId, contextExternalToolId } = setup(); - await uc.updateContextExternalTool(userId, contextExternalToolId, contextExternalTool); + await uc.updateContextExternalTool(userId, schoolId, contextExternalToolId, contextExternalTool); expect(toolPermissionHelper.ensureContextPermissions).toHaveBeenCalledWith( userId, @@ -242,54 +331,114 @@ describe('ContextExternalToolUc', () => { }); it('should call contextExternalToolValidationService', async () => { - const { contextExternalTool, contextExternalToolId, userId } = setup(); + const { contextExternalTool, userId, schoolId, contextExternalToolId } = setup(); - await uc.updateContextExternalTool(userId, contextExternalToolId, contextExternalTool); + await uc.updateContextExternalTool(userId, schoolId, contextExternalToolId, contextExternalTool); expect(contextExternalToolValidationService.validate).toHaveBeenCalledWith(contextExternalTool); }); it('should return the saved object', async () => { - const { contextExternalTool, contextExternalToolId, userId } = setup(); + const { contextExternalTool, userId, schoolId, contextExternalToolId } = setup(); - const result = await uc.updateContextExternalTool(userId, contextExternalToolId, contextExternalTool); + const result = await uc.updateContextExternalTool(userId, schoolId, contextExternalToolId, contextExternalTool); expect(result).toEqual(contextExternalTool); }); }); - describe('when the user does not have permission', () => { + describe('when the user is from a different school than the school external tool', () => { const setup = () => { const userId: EntityId = 'userId'; + const schoolId: EntityId = new ObjectId().toHexString(); - const contextExternalToolId = new ObjectId().toHexString(); - const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId( - { - displayName: 'Course', - contextRef: { - id: 'contextId', - type: ToolContextType.COURSE, - }, + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId, + }); + + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + displayName: 'Course', + schoolToolRef: { + schoolToolId: schoolExternalTool.id, + schoolId, + }, + contextRef: { + id: 'contextId', + type: ToolContextType.COURSE, }, - contextExternalToolId + }); + + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); + + return { + contextExternalTool, + contextExternalToolId: contextExternalTool.id as string, + userId, + }; + }; + + it('should return UnprocessableEntity and not save', async () => { + const { contextExternalTool, userId, contextExternalToolId } = setup(); + + const func = () => + uc.updateContextExternalTool( + userId, + new ObjectId().toHexString(), + contextExternalToolId, + contextExternalTool + ); + + await expect(func).rejects.toThrow( + new ForbiddenLoggableException( + userId, + AuthorizableReferenceType.ContextExternalToolEntity, + AuthorizationContextBuilder.write([Permission.CONTEXT_TOOL_ADMIN]) + ) ); + expect(contextExternalToolService.saveContextExternalTool).not.toHaveBeenCalled(); + }); + }); + + describe('when the user does not have permission', () => { + const setup = () => { + const userId: EntityId = 'userId'; + const schoolId: EntityId = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId, + }); + + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + displayName: 'Course', + schoolToolRef: { + schoolToolId: schoolExternalTool.id, + schoolId, + }, + contextRef: { + id: 'contextId', + type: ToolContextType.COURSE, + }, + }); const error = new ForbiddenException(); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); + contextExternalToolService.findById.mockResolvedValueOnce(contextExternalTool); toolPermissionHelper.ensureContextPermissions.mockRejectedValue(error); return { contextExternalTool, - contextExternalToolId, + contextExternalToolId: contextExternalTool.id as string, userId, + schoolId, error, }; }; it('should return forbidden and not save', async () => { - const { contextExternalTool, contextExternalToolId, userId, error } = setup(); + const { contextExternalTool, userId, error, schoolId, contextExternalToolId } = setup(); - const func = () => uc.updateContextExternalTool(userId, contextExternalToolId, contextExternalTool); + const func = () => uc.updateContextExternalTool(userId, schoolId, contextExternalToolId, contextExternalTool); await expect(func).rejects.toThrow(error); expect(contextExternalToolService.saveContextExternalTool).not.toHaveBeenCalled(); @@ -299,35 +448,43 @@ describe('ContextExternalToolUc', () => { describe('when the validation fails', () => { const setup = () => { const userId: EntityId = 'userId'; + const schoolId: EntityId = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + schoolId, + }); - const contextExternalToolId = new ObjectId().toHexString(); - const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId( - { - displayName: 'Course', - contextRef: { - id: 'contextId', - type: ToolContextType.COURSE, - }, + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + displayName: 'Course', + schoolToolRef: { + schoolToolId: schoolExternalTool.id, + schoolId, }, - contextExternalToolId - ); + contextRef: { + id: 'contextId', + type: ToolContextType.COURSE, + }, + }); const error = new UnprocessableEntityException(); + schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); + contextExternalToolService.findById.mockResolvedValueOnce(contextExternalTool); contextExternalToolValidationService.validate.mockRejectedValue(error); return { contextExternalTool, - contextExternalToolId, + contextExternalToolId: contextExternalTool.id as string, userId, + schoolId, error, }; }; it('should return UnprocessableEntity and not save', async () => { - const { contextExternalTool, contextExternalToolId, userId, error } = setup(); + const { contextExternalTool, userId, error, schoolId, contextExternalToolId } = setup(); - const func = () => uc.updateContextExternalTool(userId, contextExternalToolId, contextExternalTool); + const func = () => uc.updateContextExternalTool(userId, schoolId, contextExternalToolId, contextExternalTool); await expect(func).rejects.toThrow(error); expect(contextExternalToolService.saveContextExternalTool).not.toHaveBeenCalled(); diff --git a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts index d29cc1454d4..587ecb01c64 100644 --- a/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts +++ b/apps/server/src/modules/tool/context-external-tool/uc/context-external-tool.uc.ts @@ -1,9 +1,16 @@ +import { + AuthorizationContext, + AuthorizationContextBuilder, + AuthorizationService, + ForbiddenLoggableException, +} from '@modules/authorization'; +import { AuthorizableReferenceType } from '@modules/authorization/domain'; import { Injectable } from '@nestjs/common'; import { EntityId, Permission, User } from '@shared/domain'; -import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; import { ToolContextType } from '../../common/enum'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; +import { SchoolExternalTool } from '../../school-external-tool/domain'; +import { SchoolExternalToolService } from '../../school-external-tool/service'; import { ContextExternalTool, ContextRef } from '../domain'; import { ContextExternalToolService, ContextExternalToolValidationService } from '../service'; import { ContextExternalToolDto } from './dto/context-external-tool.types'; @@ -12,22 +19,32 @@ import { ContextExternalToolDto } from './dto/context-external-tool.types'; export class ContextExternalToolUc { constructor( private readonly toolPermissionHelper: ToolPermissionHelper, + private readonly schoolExternalToolService: SchoolExternalToolService, private readonly contextExternalToolService: ContextExternalToolService, private readonly contextExternalToolValidationService: ContextExternalToolValidationService, - private readonly authorizationService: AuthorizationService, - private readonly logger: LegacyLogger + private readonly authorizationService: AuthorizationService ) {} async createContextExternalTool( userId: EntityId, + schoolId: EntityId, contextExternalToolDto: ContextExternalToolDto ): Promise { - const contextExternalTool = new ContextExternalTool(contextExternalToolDto); const context: AuthorizationContext = AuthorizationContextBuilder.write([Permission.CONTEXT_TOOL_ADMIN]); + const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById( + contextExternalToolDto.schoolToolRef.schoolToolId + ); + + if (schoolExternalTool.schoolId !== schoolId) { + throw new ForbiddenLoggableException(userId, AuthorizableReferenceType.ContextExternalToolEntity, context); + } + + contextExternalToolDto.schoolToolRef.schoolId = schoolId; + const contextExternalTool = new ContextExternalTool(contextExternalToolDto); await this.toolPermissionHelper.ensureContextPermissions(userId, contextExternalTool, context); - await this.contextExternalToolValidationService.validate(contextExternalToolDto); + await this.contextExternalToolValidationService.validate(contextExternalTool); const createdTool: ContextExternalTool = await this.contextExternalToolService.saveContextExternalTool( contextExternalTool @@ -38,27 +55,38 @@ export class ContextExternalToolUc { async updateContextExternalTool( userId: EntityId, + schoolId: EntityId, contextExternalToolId: EntityId, contextExternalToolDto: ContextExternalToolDto ): Promise { - const contextExternalTool: ContextExternalTool = new ContextExternalTool(contextExternalToolDto); + const context: AuthorizationContext = AuthorizationContextBuilder.write([Permission.CONTEXT_TOOL_ADMIN]); + const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById( + contextExternalToolDto.schoolToolRef.schoolToolId + ); + + if (schoolExternalTool.schoolId !== schoolId) { + throw new ForbiddenLoggableException(userId, AuthorizableReferenceType.ContextExternalToolEntity, context); + } - await this.toolPermissionHelper.ensureContextPermissions( - userId, - contextExternalTool, - AuthorizationContextBuilder.write([Permission.CONTEXT_TOOL_ADMIN]) + let contextExternalTool: ContextExternalTool = await this.contextExternalToolService.findById( + contextExternalToolId ); - const updated: ContextExternalTool = new ContextExternalTool({ - ...contextExternalTool, - id: contextExternalToolId, + contextExternalTool = new ContextExternalTool({ + ...contextExternalToolDto, + id: contextExternalTool.id, }); + contextExternalTool.schoolToolRef.schoolId = schoolId; + + await this.toolPermissionHelper.ensureContextPermissions(userId, contextExternalTool, context); - await this.contextExternalToolValidationService.validate(updated); + await this.contextExternalToolValidationService.validate(contextExternalTool); - const saved: ContextExternalTool = await this.contextExternalToolService.saveContextExternalTool(updated); + const updatedTool: ContextExternalTool = await this.contextExternalToolService.saveContextExternalTool( + contextExternalTool + ); - return saved; + return updatedTool; } public async deleteContextExternalTool(userId: EntityId, contextExternalToolId: EntityId): Promise { diff --git a/apps/server/src/modules/tool/tool-api.module.ts b/apps/server/src/modules/tool/tool-api.module.ts index 8513eaed3fc..b8d12a16006 100644 --- a/apps/server/src/modules/tool/tool-api.module.ts +++ b/apps/server/src/modules/tool/tool-api.module.ts @@ -1,10 +1,13 @@ -import { Module } from '@nestjs/common'; -import { LtiToolRepo } from '@shared/repo'; -import { LoggerModule } from '@src/core/logger'; import { AuthorizationModule } from '@modules/authorization'; import { LegacySchoolModule } from '@modules/legacy-school'; import { UserModule } from '@modules/user'; +import { Module } from '@nestjs/common'; +import { LtiToolRepo } from '@shared/repo'; +import { LoggerModule } from '@src/core/logger'; +import { BoardModule } from '../board'; +import { LearnroomModule } from '../learnroom'; import { CommonToolModule } from './common'; +import { ToolPermissionHelper } from './common/uc/tool-permission-helper'; import { ToolContextController } from './context-external-tool/controller'; import { ToolReferenceController } from './context-external-tool/controller/tool-reference.controller'; import { ContextExternalToolUc, ToolReferenceUc } from './context-external-tool/uc'; @@ -29,6 +32,8 @@ import { ToolModule } from './tool.module'; LoggerModule, LegacySchoolModule, ToolConfigModule, + LearnroomModule, + BoardModule, ], controllers: [ ToolLaunchController, @@ -51,6 +56,7 @@ import { ToolModule } from './tool.module'; ContextExternalToolUc, ToolLaunchUc, ToolReferenceUc, + ToolPermissionHelper, ], }) export class ToolApiModule {} diff --git a/apps/server/src/shared/testing/factory/domainobject/board/board-do-authorizable.factory.ts b/apps/server/src/shared/testing/factory/domainobject/board/board-do-authorizable.factory.ts new file mode 100644 index 00000000000..f774ba97bb4 --- /dev/null +++ b/apps/server/src/shared/testing/factory/domainobject/board/board-do-authorizable.factory.ts @@ -0,0 +1,14 @@ +import { BoardDoAuthorizable, BoardDoAuthorizableProps, UserRoleEnum } from '@shared/domain/domainobject/board'; +import { ObjectId } from 'bson'; +import { DomainObjectFactory } from '../domain-object.factory'; + +export const boardDoAuthorizableFactory = DomainObjectFactory.define( + BoardDoAuthorizable, + () => { + return { + id: new ObjectId().toHexString(), + users: [], + requiredUserRole: UserRoleEnum.STUDENT, + }; + } +); diff --git a/apps/server/src/shared/testing/factory/domainobject/board/external-tool.do.factory.ts b/apps/server/src/shared/testing/factory/domainobject/board/external-tool-element.do.factory.ts similarity index 100% rename from apps/server/src/shared/testing/factory/domainobject/board/external-tool.do.factory.ts rename to apps/server/src/shared/testing/factory/domainobject/board/external-tool-element.do.factory.ts diff --git a/apps/server/src/shared/testing/factory/domainobject/board/index.ts b/apps/server/src/shared/testing/factory/domainobject/board/index.ts index 9a6cdf84839..802dcf744f3 100644 --- a/apps/server/src/shared/testing/factory/domainobject/board/index.ts +++ b/apps/server/src/shared/testing/factory/domainobject/board/index.ts @@ -1,7 +1,7 @@ export * from './card.do.factory'; export * from './column-board.do.factory'; export * from './column.do.factory'; -export * from './external-tool.do.factory'; +export * from './external-tool-element.do.factory'; export * from './file-element.do.factory'; export * from './link-element.do.factory'; export * from './rich-text-element.do.factory';