diff --git a/apps/server/src/modules/authorization/domain/mapper/authorization-context.builder.ts b/apps/server/src/modules/authorization/domain/mapper/authorization-context.builder.ts index 14ba6714cca..5ad33af1d35 100644 --- a/apps/server/src/modules/authorization/domain/mapper/authorization-context.builder.ts +++ b/apps/server/src/modules/authorization/domain/mapper/authorization-context.builder.ts @@ -8,13 +8,13 @@ export class AuthorizationContextBuilder { return context; } - static write(requiredPermissions: Permission[]): AuthorizationContext { + public static write(requiredPermissions: Permission[]): AuthorizationContext { const context = this.build(requiredPermissions, Action.write); return context; } - static read(requiredPermissions: Permission[]): AuthorizationContext { + public static read(requiredPermissions: Permission[]): AuthorizationContext { const context = this.build(requiredPermissions, Action.read); return context; diff --git a/apps/server/src/modules/board/controller/api-test/board-copy-in-room.api.spec.ts b/apps/server/src/modules/board/controller/api-test/board-copy-in-room.api.spec.ts index d833eb510b2..5e02e65be08 100644 --- a/apps/server/src/modules/board/controller/api-test/board-copy-in-room.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/board-copy-in-room.api.spec.ts @@ -6,6 +6,7 @@ import { cleanupCollections, groupEntityFactory, roleFactory, + schoolEntityFactory, TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; @@ -49,12 +50,17 @@ describe(`board copy with room relation (api)`, () => { name: RoleName.ROOMEDITOR, permissions: [Permission.ROOM_EDIT], }); - const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); + const school = schoolEntityFactory.buildWithId(); + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({ school }); const userGroup = groupEntityFactory.buildWithId({ type: GroupEntityTypes.ROOM, users: [{ role, user: teacherUser }], }); - const roomMembership = roomMembershipEntityFactory.build({ roomId: room.id, userGroupId: userGroup.id }); + const roomMembership = roomMembershipEntityFactory.build({ + roomId: room.id, + userGroupId: userGroup.id, + schoolId: teacherUser.school.id, + }); const columnBoardNode = columnBoardEntityFactory.build({ ...columnBoardProps, context: { id: room.id, type: BoardExternalReferenceType.Room }, diff --git a/apps/server/src/modules/board/controller/api-test/board-create-in-room.api.spec.ts b/apps/server/src/modules/board/controller/api-test/board-create-in-room.api.spec.ts index bb10e3fb33c..bb439583707 100644 --- a/apps/server/src/modules/board/controller/api-test/board-create-in-room.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/board-create-in-room.api.spec.ts @@ -4,7 +4,14 @@ import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain/interface'; import { RoleName } from '@shared/domain/interface/rolename.enum'; -import { cleanupCollections, groupEntityFactory, roleFactory, TestApiClient, userFactory } from '@shared/testing'; +import { + cleanupCollections, + groupEntityFactory, + roleFactory, + schoolEntityFactory, + TestApiClient, + userFactory, +} from '@shared/testing'; import { accountFactory } from '@src/modules/account/testing'; import { GroupEntityTypes } from '@src/modules/group/entity'; import { roomMembershipEntityFactory } from '@src/modules/room-membership/testing'; @@ -42,7 +49,8 @@ describe(`create board in room (api)`, () => { describe('When request is valid', () => { describe('When user is allowed to edit the room', () => { const setup = async () => { - const user = userFactory.buildWithId(); + const school = schoolEntityFactory.buildWithId(); + const user = userFactory.buildWithId({ school }); const account = accountFactory.withUser(user).build(); const role = roleFactory.buildWithId({ name: RoleName.ROOMEDITOR, permissions: [Permission.ROOM_EDIT] }); @@ -52,9 +60,13 @@ describe(`create board in room (api)`, () => { users: [{ user, role }], }); - const room = roomEntityFactory.buildWithId(); + const room = roomEntityFactory.buildWithId({ schoolId: user.school.id }); - const roomMembership = roomMembershipEntityFactory.build({ roomId: room.id, userGroupId: userGroup.id }); + const roomMembership = roomMembershipEntityFactory.build({ + roomId: room.id, + userGroupId: userGroup.id, + schoolId: user.school.id, + }); await em.persistAndFlush([account, user, role, userGroup, room, roomMembership]); em.clear(); diff --git a/apps/server/src/modules/board/service/internal/board-context.service.spec.ts b/apps/server/src/modules/board/service/internal/board-context.service.spec.ts index 489c09f8a8f..322d9ea4361 100644 --- a/apps/server/src/modules/board/service/internal/board-context.service.spec.ts +++ b/apps/server/src/modules/board/service/internal/board-context.service.spec.ts @@ -236,6 +236,7 @@ describe(BoardContextService.name, () => { id: 'foo', roomId: columnBoard.context.id, members: [{ userId: user.id, roles: [role] }], + schoolId: user.school.id, }); const result = await service.getUsersWithBoardRoles(columnBoard); @@ -271,6 +272,7 @@ describe(BoardContextService.name, () => { id: 'foo', roomId: columnBoard.context.id, members: [{ userId: user.id, roles: [role] }], + schoolId: user.school.id, }); const result = await service.getUsersWithBoardRoles(columnBoard); @@ -306,6 +308,7 @@ describe(BoardContextService.name, () => { id: 'foo', roomId: columnBoard.context.id, members: [{ userId: user.id, roles: [role] }], + schoolId: user.school.id, }); const result = await service.getUsersWithBoardRoles(columnBoard); diff --git a/apps/server/src/modules/room-membership/authorization/room-membership.rule.spec.ts b/apps/server/src/modules/room-membership/authorization/room-membership.rule.spec.ts index 6946c83aa8d..0326bb2d02b 100644 --- a/apps/server/src/modules/room-membership/authorization/room-membership.rule.spec.ts +++ b/apps/server/src/modules/room-membership/authorization/room-membership.rule.spec.ts @@ -1,7 +1,8 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { Permission } from '@shared/domain/interface'; -import { roleDtoFactory, setupEntities, userFactory } from '@shared/testing'; import { Action, AuthorizationHelper, AuthorizationInjectionService } from '@modules/authorization'; +import { roomFactory } from '@modules/room/testing'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Permission, RoleName } from '@shared/domain/interface'; +import { roleDtoFactory, roleFactory, schoolEntityFactory, setupEntities, userFactory } from '@shared/testing'; import { RoomMembershipAuthorizable } from '../do/room-membership-authorizable.do'; import { RoomMembershipRule } from './room-membership.rule'; @@ -30,7 +31,7 @@ describe(RoomMembershipRule.name, () => { describe('when entity is applicable', () => { const setup = () => { const user = userFactory.buildWithId(); - const roomMembershipAuthorizable = new RoomMembershipAuthorizable('', []); + const roomMembershipAuthorizable = new RoomMembershipAuthorizable('', [], user.school.id); return { user, roomMembershipAuthorizable }; }; @@ -60,66 +61,135 @@ describe(RoomMembershipRule.name, () => { }); describe('hasPermission', () => { - describe('when user is viewer member of room', () => { - const setup = () => { - const user = userFactory.buildWithId(); - const roleDto = roleDtoFactory.build({ permissions: [Permission.ROOM_VIEW] }); - const roomMembershipAuthorizable = new RoomMembershipAuthorizable('', [{ roles: [roleDto], userId: user.id }]); + describe("when user's primary school is room's school", () => { + describe('when user is not member of the room', () => { + const setup = () => { + const user = userFactory.buildWithId(); + const roomMembershipAuthorizable = new RoomMembershipAuthorizable('', [], user.school.id); - return { user, roomMembershipAuthorizable }; - }; + return { user, roomMembershipAuthorizable }; + }; - it('should return "true" for read action', () => { - const { user, roomMembershipAuthorizable } = setup(); + it('should return "false" for read action', () => { + const { user, roomMembershipAuthorizable } = setup(); - const res = service.hasPermission(user, roomMembershipAuthorizable, { - action: Action.read, - requiredPermissions: [], - }); + const res = service.hasPermission(user, roomMembershipAuthorizable, { + action: Action.read, + requiredPermissions: [], + }); - expect(res).toBe(true); + expect(res).toBe(false); + }); }); - it('should return "false" for write action', () => { - const { user, roomMembershipAuthorizable } = setup(); + describe('when user has view permission for room', () => { + const setup = () => { + const user = userFactory.buildWithId(); + const roleDto = roleDtoFactory.build({ permissions: [Permission.ROOM_VIEW] }); + const roomMembershipAuthorizable = new RoomMembershipAuthorizable( + '', + [{ roles: [roleDto], userId: user.id }], + user.school.id + ); + + return { user, roomMembershipAuthorizable }; + }; + + it('should return "true" for read action', () => { + const { user, roomMembershipAuthorizable } = setup(); - const res = service.hasPermission(user, roomMembershipAuthorizable, { - action: Action.write, - requiredPermissions: [], + const res = service.hasPermission(user, roomMembershipAuthorizable, { + action: Action.read, + requiredPermissions: [], + }); + + expect(res).toBe(true); }); - expect(res).toBe(false); + it('should return "false" for write action', () => { + const { user, roomMembershipAuthorizable } = setup(); + + const res = service.hasPermission(user, roomMembershipAuthorizable, { + action: Action.write, + requiredPermissions: [], + }); + + expect(res).toBe(false); + }); }); - }); - describe('when user is not member of room', () => { - const setup = () => { - const user = userFactory.buildWithId(); - const roomMembershipAuthorizable = new RoomMembershipAuthorizable('', []); + describe('when user is not member of room', () => { + const setup = () => { + const user = userFactory.buildWithId(); + const roomMembershipAuthorizable = new RoomMembershipAuthorizable('', [], user.school.id); - return { user, roomMembershipAuthorizable }; - }; + return { user, roomMembershipAuthorizable }; + }; - it('should return "false" for read action', () => { - const { user, roomMembershipAuthorizable } = setup(); + it('should return "false" for read action', () => { + const { user, roomMembershipAuthorizable } = setup(); - const res = service.hasPermission(user, roomMembershipAuthorizable, { - action: Action.read, - requiredPermissions: [], + const res = service.hasPermission(user, roomMembershipAuthorizable, { + action: Action.read, + requiredPermissions: [], + }); + + expect(res).toBe(false); }); - expect(res).toBe(false); - }); + it('should return "false" for write action', () => { + const { user, roomMembershipAuthorizable } = setup(); - it('should return "false" for write action', () => { - const { user, roomMembershipAuthorizable } = setup(); + const res = service.hasPermission(user, roomMembershipAuthorizable, { + action: Action.write, + requiredPermissions: [], + }); + + expect(res).toBe(false); + }); + }); + }); - const res = service.hasPermission(user, roomMembershipAuthorizable, { - action: Action.write, - requiredPermissions: [], + describe("when user is guest at room's school", () => { + describe('when user has view permission for room', () => { + const setup = () => { + const otherSchool = schoolEntityFactory.buildWithId(); + const guestTeacherRole = roleFactory.buildWithId({ name: RoleName.GUESTTEACHER }); + const user = userFactory.buildWithId({ + secondarySchools: [{ school: otherSchool, role: guestTeacherRole }], + }); + const room = roomFactory.build({ schoolId: otherSchool.id }); + const roleDto = roleDtoFactory.build({ permissions: [Permission.ROOM_VIEW] }); + const roomMembershipAuthorizable = new RoomMembershipAuthorizable( + room.id, + [{ roles: [roleDto], userId: user.id }], + otherSchool.id + ); + + return { user, roomMembershipAuthorizable }; + }; + + it('should return "true" for read action', () => { + const { user, roomMembershipAuthorizable } = setup(); + + const res = service.hasPermission(user, roomMembershipAuthorizable, { + action: Action.read, + requiredPermissions: [], + }); + + expect(res).toBe(true); }); - expect(res).toBe(false); + it('should return "false" for write action', () => { + const { user, roomMembershipAuthorizable } = setup(); + + const res = service.hasPermission(user, roomMembershipAuthorizable, { + action: Action.write, + requiredPermissions: [], + }); + + expect(res).toBe(false); + }); }); }); }); diff --git a/apps/server/src/modules/room-membership/authorization/room-membership.rule.ts b/apps/server/src/modules/room-membership/authorization/room-membership.rule.ts index cfcd11c33af..3336e93892f 100644 --- a/apps/server/src/modules/room-membership/authorization/room-membership.rule.ts +++ b/apps/server/src/modules/room-membership/authorization/room-membership.rule.ts @@ -1,7 +1,7 @@ +import { Action, AuthorizationContext, AuthorizationInjectionService, Rule } from '@modules/authorization'; import { Injectable } from '@nestjs/common'; import { User } from '@shared/domain/entity'; import { Permission } from '@shared/domain/interface'; -import { AuthorizationInjectionService, Action, AuthorizationContext, Rule } from '@modules/authorization'; import { RoomMembershipAuthorizable } from '../do/room-membership-authorizable.do'; @Injectable() @@ -17,6 +17,14 @@ export class RoomMembershipRule implements Rule { } public hasPermission(user: User, object: RoomMembershipAuthorizable, context: AuthorizationContext): boolean { + const primarySchoolId = user.school.id; + const secondarySchools = user.secondarySchools ?? []; + const secondarySchoolIds = secondarySchools.map(({ school }) => school.id); + + if (![primarySchoolId, ...secondarySchoolIds].includes(object.schoolId)) { + return false; + } + const { action } = context; const permissionsThisUserHas = object.members .filter((member) => member.userId === user.id) diff --git a/apps/server/src/modules/room-membership/do/room-membership-authorizable.do.ts b/apps/server/src/modules/room-membership/do/room-membership-authorizable.do.ts index 61821fa4b82..dbd969a84da 100644 --- a/apps/server/src/modules/room-membership/do/room-membership-authorizable.do.ts +++ b/apps/server/src/modules/room-membership/do/room-membership-authorizable.do.ts @@ -12,10 +12,13 @@ export class RoomMembershipAuthorizable implements AuthorizableObject { public readonly roomId: EntityId; + public readonly schoolId: EntityId; + public readonly members: UserWithRoomRoles[]; - public constructor(roomId: EntityId, members: UserWithRoomRoles[]) { + constructor(roomId: EntityId, members: UserWithRoomRoles[], schoolId: EntityId) { this.members = members; this.roomId = roomId; + this.schoolId = schoolId; } } diff --git a/apps/server/src/modules/room-membership/service/room-membership.service.spec.ts b/apps/server/src/modules/room-membership/service/room-membership.service.spec.ts index 6f6b39139b2..98763e33358 100644 --- a/apps/server/src/modules/room-membership/service/room-membership.service.spec.ts +++ b/apps/server/src/modules/room-membership/service/room-membership.service.spec.ts @@ -371,6 +371,7 @@ describe('RoomMembershipService', () => { it('should return empty RoomMembershipAuthorizable when roomMembership not exists', async () => { const roomId = 'nonexistent'; roomMembershipRepo.findByRoomId.mockResolvedValue(null); + roomService.getSingleRoom.mockResolvedValue(roomFactory.build({ id: roomId })); const result = await service.getRoomMembershipAuthorizable(roomId); diff --git a/apps/server/src/modules/room-membership/service/room-membership.service.ts b/apps/server/src/modules/room-membership/service/room-membership.service.ts index 59fa6167c6d..8baa23dd643 100644 --- a/apps/server/src/modules/room-membership/service/room-membership.service.ts +++ b/apps/server/src/modules/room-membership/service/room-membership.service.ts @@ -51,7 +51,8 @@ export class RoomMembershipService { private buildRoomMembershipAuthorizable( roomId: EntityId, group: Group, - roleSet: RoleDto[] + roleSet: RoleDto[], + schoolId: EntityId ): RoomMembershipAuthorizable { const members = group.users.map((groupUser): UserWithRoomRoles => { const roleDto = roleSet.find((role) => role.id === groupUser.roleId); @@ -62,7 +63,7 @@ export class RoomMembershipService { }; }); - const roomMembershipAuthorizable = new RoomMembershipAuthorizable(roomId, members); + const roomMembershipAuthorizable = new RoomMembershipAuthorizable(roomId, members, schoolId); return roomMembershipAuthorizable; } @@ -120,7 +121,7 @@ export class RoomMembershipService { .map((item) => { const group = groupPage.data.find((g) => g.id === item.userGroupId); if (!group) return null; - return this.buildRoomMembershipAuthorizable(item.roomId, group, roleSet); + return this.buildRoomMembershipAuthorizable(item.roomId, group, roleSet, item.schoolId); }) .filter((item): item is RoomMembershipAuthorizable => item !== null); @@ -130,7 +131,8 @@ export class RoomMembershipService { public async getRoomMembershipAuthorizable(roomId: EntityId): Promise { const roomMembership = await this.roomMembershipRepo.findByRoomId(roomId); if (roomMembership === null) { - return new RoomMembershipAuthorizable(roomId, []); + const room = await this.roomService.getSingleRoom(roomId); + return new RoomMembershipAuthorizable(roomId, [], room.schoolId); } const group = await this.groupService.findById(roomMembership.userGroupId); const roleSet = await this.roleService.findByIds(group.users.map((groupUser) => groupUser.roleId)); @@ -144,7 +146,7 @@ export class RoomMembershipService { }; }); - const roomMembershipAuthorizable = new RoomMembershipAuthorizable(roomId, members); + const roomMembershipAuthorizable = new RoomMembershipAuthorizable(roomId, members, roomMembership.schoolId); return roomMembershipAuthorizable; } diff --git a/apps/server/src/modules/room/api/test/room-add-members.api.spec.ts b/apps/server/src/modules/room/api/test/room-add-members.api.spec.ts index a1ed8579853..ad8f5e6a3b7 100644 --- a/apps/server/src/modules/room/api/test/room-add-members.api.spec.ts +++ b/apps/server/src/modules/room/api/test/room-add-members.api.spec.ts @@ -65,7 +65,11 @@ describe('Room Controller (API)', () => { externalSource: undefined, }); - const roomMemberships = roomMembershipEntityFactory.build({ userGroupId: userGroupEntity.id, roomId: room.id }); + const roomMemberships = roomMembershipEntityFactory.build({ + userGroupId: userGroupEntity.id, + roomId: room.id, + schoolId: school.id, + }); await em.persistAndFlush([ room, roomMemberships, @@ -87,7 +91,9 @@ describe('Room Controller (API)', () => { describe('when the user is not authenticated', () => { it('should return a 401 error', async () => { const { room } = await setupRoomWithMembers(); + const response = await testApiClient.patch(`/${room.id}/members/add`); + expect(response.status).toBe(HttpStatus.UNAUTHORIZED); }); }); @@ -96,7 +102,9 @@ describe('Room Controller (API)', () => { const setupLoggedInUser = async () => { const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); await em.persistAndFlush([teacherAccount, teacherUser]); + const loggedInClient = await testApiClient.login(teacherAccount); + return { loggedInClient }; }; diff --git a/apps/server/src/modules/room/api/test/room-delete.api.spec.ts b/apps/server/src/modules/room/api/test/room-delete.api.spec.ts index 22f74c7edc8..4e8be194dfe 100644 --- a/apps/server/src/modules/room/api/test/room-delete.api.spec.ts +++ b/apps/server/src/modules/room/api/test/room-delete.api.spec.ts @@ -10,6 +10,7 @@ import { cleanupCollections, groupEntityFactory, roleFactory, + schoolEntityFactory, } from '@shared/testing'; import { RoomMembershipEntity } from '@src/modules/room-membership'; import { roomMembershipEntityFactory } from '@src/modules/room-membership/testing/room-membership-entity.factory'; @@ -99,12 +100,17 @@ describe('Room Controller (API)', () => { name: RoleName.ROOMEDITOR, permissions: [Permission.ROOM_EDIT], }); - const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); + const school = schoolEntityFactory.buildWithId(); + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({ school }); const userGroup = groupEntityFactory.buildWithId({ type: GroupEntityTypes.ROOM, users: [{ role, user: teacherUser }], }); - const roomMembership = roomMembershipEntityFactory.build({ roomId: room.id, userGroupId: userGroup.id }); + const roomMembership = roomMembershipEntityFactory.build({ + roomId: room.id, + userGroupId: userGroup.id, + schoolId: teacherUser.school.id, + }); await em.persistAndFlush([room, roomMembership, teacherAccount, teacherUser, userGroup, role]); em.clear(); diff --git a/apps/server/src/modules/room/api/test/room-get-boards.api.spec.ts b/apps/server/src/modules/room/api/test/room-get-boards.api.spec.ts index 4f1646ec708..2f5dc502c70 100644 --- a/apps/server/src/modules/room/api/test/room-get-boards.api.spec.ts +++ b/apps/server/src/modules/room/api/test/room-get-boards.api.spec.ts @@ -6,6 +6,7 @@ import { cleanupCollections, groupEntityFactory, roleFactory, + schoolEntityFactory, TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; @@ -94,11 +95,12 @@ describe('Room Controller (API)', () => { describe('when the user has the required permissions', () => { const setup = async () => { - const room = roomEntityFactory.build(); + const school = schoolEntityFactory.buildWithId(); + const room = roomEntityFactory.build({ schoolId: school.id }); const board = columnBoardEntityFactory.build({ context: { type: BoardExternalReferenceType.Room, id: room.id }, }); - const { studentAccount, studentUser } = UserAndAccountTestFactory.buildStudent(); + const { studentAccount, studentUser } = UserAndAccountTestFactory.buildStudent({ school }); const role = roleFactory.buildWithId({ name: RoleName.ROOMVIEWER, permissions: [Permission.ROOM_VIEW], @@ -109,7 +111,11 @@ describe('Room Controller (API)', () => { organization: studentUser.school, externalSource: undefined, }); - const roomMembership = roomMembershipEntityFactory.build({ userGroupId: userGroupEntity.id, roomId: room.id }); + const roomMembership = roomMembershipEntityFactory.build({ + userGroupId: userGroupEntity.id, + roomId: room.id, + schoolId: school.id, + }); await em.persistAndFlush([room, board, studentAccount, studentUser, role, userGroupEntity, roomMembership]); em.clear(); diff --git a/apps/server/src/modules/room/api/test/room-get.api.spec.ts b/apps/server/src/modules/room/api/test/room-get.api.spec.ts index 719889d82ec..b0aa9afcc68 100644 --- a/apps/server/src/modules/room/api/test/room-get.api.spec.ts +++ b/apps/server/src/modules/room/api/test/room-get.api.spec.ts @@ -8,6 +8,7 @@ import { cleanupCollections, groupEntityFactory, roleFactory, + schoolEntityFactory, } from '@shared/testing'; import { GroupEntityTypes } from '@modules/group/entity/group.entity'; import { roomMembershipEntityFactory } from '@src/modules/room-membership/testing'; @@ -92,8 +93,9 @@ describe('Room Controller (API)', () => { describe('when the user has the required permissions', () => { const setup = async () => { - const room = roomEntityFactory.build(); - const { studentAccount, studentUser } = UserAndAccountTestFactory.buildStudent(); + const school = schoolEntityFactory.buildWithId(); + const room = roomEntityFactory.build({ schoolId: school.id }); + const { studentAccount, studentUser } = UserAndAccountTestFactory.buildStudent({ school }); const role = roleFactory.buildWithId({ name: RoleName.ROOMVIEWER, permissions: [Permission.ROOM_VIEW], @@ -104,7 +106,11 @@ describe('Room Controller (API)', () => { organization: studentUser.school, externalSource: undefined, }); - const roomMembership = roomMembershipEntityFactory.build({ userGroupId: userGroupEntity.id, roomId: room.id }); + const roomMembership = roomMembershipEntityFactory.build({ + userGroupId: userGroupEntity.id, + roomId: room.id, + schoolId: school.id, + }); await em.persistAndFlush([room, studentAccount, studentUser, role, userGroupEntity, roomMembership]); em.clear(); diff --git a/apps/server/src/modules/room/api/test/room-index.api.spec.ts b/apps/server/src/modules/room/api/test/room-index.api.spec.ts index cbb68d0f38c..162fb503a1b 100644 --- a/apps/server/src/modules/room/api/test/room-index.api.spec.ts +++ b/apps/server/src/modules/room/api/test/room-index.api.spec.ts @@ -9,6 +9,7 @@ import { cleanupCollections, groupEntityFactory, roleFactory, + schoolEntityFactory, } from '@shared/testing'; import { GroupEntityTypes } from '@modules/group/entity/group.entity'; import { roomMembershipEntityFactory } from '@src/modules/room-membership/testing/room-membership-entity.factory'; @@ -128,8 +129,9 @@ describe('Room Controller (API)', () => { describe('when the user has the required permissions', () => { const setup = async () => { - const rooms = roomEntityFactory.buildListWithId(2); - const { studentAccount, studentUser } = UserAndAccountTestFactory.buildStudent(); + const school = schoolEntityFactory.buildWithId(); + const rooms = roomEntityFactory.buildListWithId(2, { schoolId: school.id }); + const { studentAccount, studentUser } = UserAndAccountTestFactory.buildStudent({ school }); const role = roleFactory.buildWithId({ name: RoleName.ROOMVIEWER, permissions: [Permission.ROOM_VIEW], @@ -141,7 +143,7 @@ describe('Room Controller (API)', () => { externalSource: undefined, }); const roomMemberships = rooms.map((room) => - roomMembershipEntityFactory.build({ userGroupId: userGroupEntity.id, roomId: room.id }) + roomMembershipEntityFactory.build({ userGroupId: userGroupEntity.id, roomId: room.id, schoolId: school.id }) ); await em.persistAndFlush([...rooms, ...roomMemberships, studentAccount, studentUser, userGroupEntity]); em.clear(); diff --git a/apps/server/src/modules/room/api/test/room-members.api.spec.ts b/apps/server/src/modules/room/api/test/room-members.api.spec.ts index c509e59c41b..2543d750b9e 100644 --- a/apps/server/src/modules/room/api/test/room-members.api.spec.ts +++ b/apps/server/src/modules/room/api/test/room-members.api.spec.ts @@ -9,6 +9,7 @@ import { cleanupCollections, groupEntityFactory, roleFactory, + schoolEntityFactory, userFactory, } from '@shared/testing'; import { GroupEntityTypes } from '@modules/group/entity/group.entity'; @@ -47,8 +48,9 @@ describe('Room Controller (API)', () => { describe('GET /rooms/:roomId/members', () => { const setupRoomWithMembers = async () => { + const school = schoolEntityFactory.buildWithId(); const room = roomEntityFactory.buildWithId(); - const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({ school }); const editRole = roleFactory.buildWithId({ name: RoleName.ROOMEDITOR, permissions: [Permission.ROOM_VIEW, Permission.ROOM_EDIT], @@ -57,8 +59,8 @@ describe('Room Controller (API)', () => { name: RoleName.ROOMVIEWER, permissions: [Permission.ROOM_VIEW], }); - const students = userFactory.buildList(2); - const teachers = userFactory.buildList(2); + const students = userFactory.buildList(2, { school }); + const teachers = userFactory.buildList(2, { school }); const userGroupEntity = groupEntityFactory.buildWithId({ users: [ { role: editRole, user: teacherUser }, @@ -71,7 +73,11 @@ describe('Room Controller (API)', () => { organization: teacherUser.school, externalSource: undefined, }); - const roomMemberships = roomMembershipEntityFactory.build({ userGroupId: userGroupEntity.id, roomId: room.id }); + const roomMemberships = roomMembershipEntityFactory.build({ + userGroupId: userGroupEntity.id, + roomId: room.id, + schoolId: school.id, + }); await em.persistAndFlush([ room, roomMemberships, diff --git a/apps/server/src/modules/room/api/test/room-remove-members.api.spec.ts b/apps/server/src/modules/room/api/test/room-remove-members.api.spec.ts index d87d0e68314..3810a9f4f39 100644 --- a/apps/server/src/modules/room/api/test/room-remove-members.api.spec.ts +++ b/apps/server/src/modules/room/api/test/room-remove-members.api.spec.ts @@ -9,6 +9,7 @@ import { cleanupCollections, groupEntityFactory, roleFactory, + schoolEntityFactory, } from '@shared/testing'; import { GroupEntityTypes } from '@modules/group/entity/group.entity'; import { roomMembershipEntityFactory } from '@src/modules/room-membership/testing/room-membership-entity.factory'; @@ -57,9 +58,10 @@ describe('Room Controller (API)', () => { }; const setupRoomWithMembers = async () => { - const room = roomEntityFactory.buildWithId(); + const school = schoolEntityFactory.buildWithId(); + const room = roomEntityFactory.buildWithId({ schoolId: school.id }); - const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({ school }); const { teacherUser: inRoomEditor2 } = UserAndAccountTestFactory.buildTeacher({ school: teacherUser.school }); const { teacherUser: inRoomEditor3 } = UserAndAccountTestFactory.buildTeacher({ school: teacherUser.school }); const { teacherUser: inRoomViewer } = UserAndAccountTestFactory.buildTeacher({ school: teacherUser.school }); @@ -81,7 +83,11 @@ describe('Room Controller (API)', () => { externalSource: undefined, }); - const roomMemberships = roomMembershipEntityFactory.build({ userGroupId: userGroupEntity.id, roomId: room.id }); + const roomMemberships = roomMembershipEntityFactory.build({ + userGroupId: userGroupEntity.id, + roomId: room.id, + schoolId: school.id, + }); await em.persistAndFlush([...Object.values(users), room, roomMemberships, teacherAccount, userGroupEntity]); em.clear(); diff --git a/apps/server/src/modules/room/api/test/room-update.api.spec.ts b/apps/server/src/modules/room/api/test/room-update.api.spec.ts index 782c23961d4..bc505ddc6aa 100644 --- a/apps/server/src/modules/room/api/test/room-update.api.spec.ts +++ b/apps/server/src/modules/room/api/test/room-update.api.spec.ts @@ -8,6 +8,7 @@ import { cleanupCollections, groupEntityFactory, roleFactory, + schoolEntityFactory, } from '@shared/testing'; import { roomMembershipEntityFactory } from '@src/modules/room-membership/testing'; import { ServerTestModule, serverConfig, type ServerConfig } from '@modules/server'; @@ -94,19 +95,25 @@ describe('Room Controller (API)', () => { describe('when the user has the required permissions', () => { const setup = async () => { + const school = schoolEntityFactory.buildWithId(); const room = roomEntityFactory.build({ startDate: new Date('2024-10-01'), endDate: new Date('2024-10-20'), + schoolId: school.id, }); const role = roleFactory.buildWithId({ name: RoleName.ROOMEDITOR, permissions: [Permission.ROOM_EDIT], }); - const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({ school }); const userGroup = groupEntityFactory.buildWithId({ users: [{ role, user: teacherUser }], }); - const roomMembership = roomMembershipEntityFactory.build({ roomId: room.id, userGroupId: userGroup.id }); + const roomMembership = roomMembershipEntityFactory.build({ + roomId: room.id, + userGroupId: userGroup.id, + schoolId: school.id, + }); await em.persistAndFlush([room, roomMembership, teacherAccount, teacherUser, userGroup, role]); em.clear(); diff --git a/apps/server/src/modules/sharing/controller/api-test/sharing-import-room-board-token.api.spec.ts b/apps/server/src/modules/sharing/controller/api-test/sharing-import-room-board-token.api.spec.ts index 799a1bcca32..c63c27bf6d5 100644 --- a/apps/server/src/modules/sharing/controller/api-test/sharing-import-room-board-token.api.spec.ts +++ b/apps/server/src/modules/sharing/controller/api-test/sharing-import-room-board-token.api.spec.ts @@ -7,6 +7,7 @@ import { cleanupCollections, groupEntityFactory, roleFactory, + schoolEntityFactory, TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; @@ -48,17 +49,22 @@ describe('Sharing Controller (API)', () => { describe('POST /sharetoken/:token/import', () => { const setup = async () => { - const room = roomEntityFactory.buildWithId(); + const school = schoolEntityFactory.buildWithId(); + const room = roomEntityFactory.buildWithId({ schoolId: school.id }); const role = roleFactory.buildWithId({ name: RoleName.ROOMEDITOR, permissions: [Permission.ROOM_EDIT], }); - const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({ school }); const userGroup = groupEntityFactory.buildWithId({ type: GroupEntityTypes.ROOM, users: [{ role, user: teacherUser }], }); - const roomMembership = roomMembershipEntityFactory.build({ roomId: room.id, userGroupId: userGroup.id }); + const roomMembership = roomMembershipEntityFactory.build({ + roomId: room.id, + userGroupId: userGroup.id, + schoolId: school.id, + }); const board = columnBoardEntityFactory.build({ context: { id: room.id, type: BoardExternalReferenceType.Room }, }); diff --git a/apps/server/src/modules/sharing/controller/share-token.controller.ts b/apps/server/src/modules/sharing/controller/share-token.controller.ts index d85937cbb62..04e9ec01b8c 100644 --- a/apps/server/src/modules/sharing/controller/share-token.controller.ts +++ b/apps/server/src/modules/sharing/controller/share-token.controller.ts @@ -35,7 +35,7 @@ export class ShareTokenController { @ApiResponse({ status: 403, type: ForbiddenException }) @ApiResponse({ status: 500, type: InternalServerErrorException }) @Post() - async createShareToken( + public async createShareToken( @CurrentUser() currentUser: ICurrentUser, @Body() body: ShareTokenBodyParams ): Promise { @@ -62,7 +62,7 @@ export class ShareTokenController { @ApiResponse({ status: 404, type: NotFoundException }) @ApiResponse({ status: 500, type: InternalServerErrorException }) @Get(':token') - async lookupShareToken( + public async lookupShareToken( @CurrentUser() currentUser: ICurrentUser, @Param() urlParams: ShareTokenUrlParams ): Promise { @@ -81,7 +81,7 @@ export class ShareTokenController { @ApiResponse({ status: 501, type: NotImplementedException }) @Post(':token/import') @RequestTimeout('INCOMING_REQUEST_TIMEOUT_COPY_API') - async importShareToken( + public async importShareToken( @CurrentUser() currentUser: ICurrentUser, @Param() urlParams: ShareTokenUrlParams, @Body() body: ShareTokenImportBodyParams diff --git a/apps/server/src/modules/sharing/uc/share-token.uc.ts b/apps/server/src/modules/sharing/uc/share-token.uc.ts index 9cd1a9f5c7e..a9498d0b2a1 100644 --- a/apps/server/src/modules/sharing/uc/share-token.uc.ts +++ b/apps/server/src/modules/sharing/uc/share-token.uc.ts @@ -52,7 +52,7 @@ export class ShareTokenUC { this.logger.setContext(ShareTokenUC.name); } - async createShareToken( + public async createShareToken( userId: EntityId, payload: ShareTokenPayload, options?: { schoolExclusive?: boolean; expiresInDays?: number } @@ -80,7 +80,7 @@ export class ShareTokenUC { return shareToken; } - async lookupShareToken(userId: EntityId, token: string): Promise { + public async lookupShareToken(userId: EntityId, token: string): Promise { this.logger.debug({ action: 'lookupShareToken', userId, token }); const { shareToken, parentName } = await this.shareTokenService.lookupTokenWithParentName(token); @@ -102,7 +102,7 @@ export class ShareTokenUC { return shareTokenInfo; } - async importShareToken( + public async importShareToken( userId: EntityId, token: string, newName: string, @@ -282,14 +282,14 @@ export class ShareTokenUC { ); } - private async checkSchoolReadPermission(user: User, schoolId: EntityId) { + private async checkSchoolReadPermission(user: User, schoolId: EntityId): Promise { const school = await this.schoolService.getSchoolById(schoolId); const authorizationContext = AuthorizationContextBuilder.read([]); this.authorizationService.checkPermission(user, school, authorizationContext); } - private async checkContextReadPermission(userId: EntityId, context: ShareTokenContext) { + private async checkContextReadPermission(userId: EntityId, context: ShareTokenContext): Promise { const user = await this.authorizationService.getUserWithPermissions(userId); if (context.contextType === ShareTokenContextType.School) { diff --git a/apps/server/src/modules/tldraw-client/service/drawing-element-adapter.service.ts b/apps/server/src/modules/tldraw-client/service/drawing-element-adapter.service.ts index baa16703a94..1acced96d98 100644 --- a/apps/server/src/modules/tldraw-client/service/drawing-element-adapter.service.ts +++ b/apps/server/src/modules/tldraw-client/service/drawing-element-adapter.service.ts @@ -5,6 +5,11 @@ import { LegacyLogger } from '@src/core/logger'; import { firstValueFrom } from 'rxjs'; import { TldrawClientConfig } from '../interface'; +type ApiKeyHeader = { + 'X-Api-Key': string; + Accept: string; +}; + @Injectable() export class DrawingElementAdapterService { constructor( @@ -15,7 +20,7 @@ export class DrawingElementAdapterService { this.logger.setContext(DrawingElementAdapterService.name); } - async deleteDrawingBinData(parentId: string): Promise { + public async deleteDrawingBinData(parentId: string): Promise { const baseUrl = this.configService.get('TLDRAW_ADMIN_API_CLIENT_BASE_URL'); const endpointUrl = '/api/tldraw-document'; const tldrawDocumentEndpoint = new URL(endpointUrl, baseUrl).toString(); @@ -23,13 +28,13 @@ export class DrawingElementAdapterService { await firstValueFrom(this.httpService.delete(`${tldrawDocumentEndpoint}/${parentId}`, this.defaultHeaders())); } - private apiKeyHeader() { + private apiKeyHeader(): ApiKeyHeader { const apiKey = this.configService.get('TLDRAW_ADMIN_API_CLIENT_API_KEY'); return { 'X-Api-Key': apiKey, Accept: 'Application/json' }; } - private defaultHeaders() { + private defaultHeaders(): { headers: ApiKeyHeader } { return { headers: this.apiKeyHeader(), }; diff --git a/apps/server/src/modules/tldraw/controller/api-test/tldraw.controller.401.api.spec.ts b/apps/server/src/modules/tldraw/controller/api-test/tldraw.controller.401.api.spec.ts index 09d5d81cbb4..88b24bce8b9 100644 --- a/apps/server/src/modules/tldraw/controller/api-test/tldraw.controller.401.api.spec.ts +++ b/apps/server/src/modules/tldraw/controller/api-test/tldraw.controller.401.api.spec.ts @@ -37,7 +37,7 @@ describe('tldraw controller (api)', () => { return { drawingItemData }; }; - it('should return status 401 for delete', async () => { + it('should return status 204 for delete', async () => { const { drawingItemData } = await setup(); const response = await testApiClient.delete(`${drawingItemData.docName}`);