From 9ac4134198f46fcb778d52f7ebfb7397bb11110f Mon Sep 17 00:00:00 2001 From: Yannick Bungers Date: Sat, 13 Feb 2021 14:04:16 +0100 Subject: [PATCH] Add test for permission service Many tests are generated and not static like in other files. Signed-off-by: Yannick Bungers --- src/permissions/permissions.service.spec.ts | 526 ++++++++++++++++++++ 1 file changed, 526 insertions(+) create mode 100644 src/permissions/permissions.service.spec.ts diff --git a/src/permissions/permissions.service.spec.ts b/src/permissions/permissions.service.spec.ts new file mode 100644 index 000000000..b5b4eb56e --- /dev/null +++ b/src/permissions/permissions.service.spec.ts @@ -0,0 +1,526 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Test, TestingModule } from '@nestjs/testing'; +import { LoggerModule } from '../logger/logger.module'; +import { GuestPermission, PermissionsService } from './permissions.service'; +import { User } from '../users/user.entity'; +import { Note } from '../notes/note.entity'; +import { UsersModule } from '../users/users.module'; +import { NotesModule } from '../notes/notes.module'; +import { PermissionsModule } from './permissions.module'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { NoteGroupPermission } from './note-group-permission.entity'; +import { NoteUserPermission } from './note-user-permission.entity'; +import { Identity } from '../users/identity.entity'; +import { AuthToken } from '../auth/auth-token.entity'; +import { Authorship } from '../revisions/authorship.entity'; +import { AuthorColor } from '../notes/author-color.entity'; +import { Revision } from '../revisions/revision.entity'; +import { Tag } from '../notes/tag.entity'; +import { Group } from '../groups/group.entity'; + +jest.mock('../permissions/note-group-permission.entity.ts'); +jest.mock('../groups/group.entity.ts'); +jest.mock('../notes/note.entity.ts'); +jest.mock('../users/user.entity.ts'); + +describe('PermissionsService', () => { + let permissionsService: PermissionsService; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [PermissionsService], + imports: [PermissionsModule, UsersModule, LoggerModule, NotesModule], + }) + .overrideProvider(getRepositoryToken(User)) + .useValue({}) + .overrideProvider(getRepositoryToken(AuthToken)) + .useValue({}) + .overrideProvider(getRepositoryToken(Identity)) + .useValue({}) + .overrideProvider(getRepositoryToken(Authorship)) + .useValue({}) + .overrideProvider(getRepositoryToken(AuthorColor)) + .useValue({}) + .overrideProvider(getRepositoryToken(Revision)) + .useValue({}) + .overrideProvider(getRepositoryToken(Note)) + .useValue({}) + .overrideProvider(getRepositoryToken(Tag)) + .useValue({}) + .overrideProvider(getRepositoryToken(NoteGroupPermission)) + .useValue({}) + .overrideProvider(getRepositoryToken(NoteUserPermission)) + .useValue({}) + .compile(); + permissionsService = module.get(PermissionsService); + }); + + // The two users we test with: + const user2 = {} as User; + user2.id = '2'; + const user1 = {} as User; + user1.id = '1'; + + it('should be defined', () => { + expect(permissionsService).toBeDefined(); + }); + + function createNote(owner: User): Note { + const note = {} as Note; + note.userPermissions = []; + note.groupPermissions = []; + note.owner = owner; + return note; + } + + /* + * Creates the permission objects for UserPermission for two users with write and with out write permission + */ + function createNoteUserPermissionNotes(): Note[] { + const note0 = createNote(user1); + const note1 = createNote(user2); + const note2 = createNote(user2); + const note3 = createNote(user2); + const note4 = createNote(user2); + const note5 = createNote(user2); + const note6 = createNote(user2); + const note7 = createNote(user2); + const noteUserPermission1 = {} as NoteUserPermission; + noteUserPermission1.user = user1; + const noteUserPermission2 = {} as NoteUserPermission; + noteUserPermission2.user = user2; + const noteUserPermission3 = {} as NoteUserPermission; + noteUserPermission3.user = user1; + noteUserPermission3.canEdit = true; + const noteUserPermission4 = {} as NoteUserPermission; + noteUserPermission4.user = user2; + noteUserPermission4.canEdit = true; + + note1.userPermissions.push(noteUserPermission1); + + note2.userPermissions.push(noteUserPermission1); + note2.userPermissions.push(noteUserPermission2); + + note3.userPermissions.push(noteUserPermission2); + note3.userPermissions.push(noteUserPermission1); + + note4.userPermissions.push(noteUserPermission3); + + note5.userPermissions.push(noteUserPermission3); + note5.userPermissions.push(noteUserPermission4); + + note6.userPermissions.push(noteUserPermission4); + note6.userPermissions.push(noteUserPermission3); + + note7.userPermissions.push(noteUserPermission2); + + const everybody = {} as Group; + everybody.name = 'everybody'; + everybody.special = true; + const noteEverybodyRead = createNote(user1); + + const noteGroupPermissionRead = {} as NoteGroupPermission; + noteGroupPermissionRead.group = everybody; + noteGroupPermissionRead.canEdit = false; + noteGroupPermissionRead.note = noteEverybodyRead; + noteEverybodyRead.groupPermissions = [noteGroupPermissionRead]; + + const noteEverybodyWrite = createNote(user1); + + const noteGroupPermissionWrite = {} as NoteGroupPermission; + noteGroupPermissionWrite.group = everybody; + noteGroupPermissionWrite.canEdit = true; + noteGroupPermissionWrite.note = noteEverybodyWrite; + noteEverybodyWrite.groupPermissions = [noteGroupPermissionWrite]; + + return [ + note0, + note1, + note2, + note3, + note4, + note5, + note6, + note7, + noteEverybodyRead, + noteEverybodyWrite, + ]; + } + const notes = createNoteUserPermissionNotes(); + + describe('mayRead works with', () => { + it('Owner', () => { + permissionsService.guestPermission = GuestPermission.DENY; + expect(permissionsService.mayRead(user1, notes[0])).toBeTruthy(); + expect(permissionsService.mayRead(user1, notes[7])).toBeFalsy(); + }); + it('userPermission read', () => { + permissionsService.guestPermission = GuestPermission.DENY; + expect(permissionsService.mayRead(user1, notes[1])).toBeTruthy(); + expect(permissionsService.mayRead(user1, notes[2])).toBeTruthy(); + expect(permissionsService.mayRead(user1, notes[3])).toBeTruthy(); + }); + it('userPermission write', () => { + permissionsService.guestPermission = GuestPermission.DENY; + expect(permissionsService.mayRead(user1, notes[4])).toBeTruthy(); + expect(permissionsService.mayRead(user1, notes[5])).toBeTruthy(); + expect(permissionsService.mayRead(user1, notes[6])).toBeTruthy(); + expect(permissionsService.mayRead(user1, notes[7])).toBeFalsy(); + }); + + describe('guest permission', () => { + it('CREATE_ALIAS', () => { + permissionsService.guestPermission = GuestPermission.CREATE_ALIAS; + expect(permissionsService.mayRead(null, notes[8])).toBeTruthy(); + }); + it('CREATE', () => { + permissionsService.guestPermission = GuestPermission.CREATE; + expect(permissionsService.mayRead(null, notes[8])).toBeTruthy(); + }); + it('WRITE', () => { + permissionsService.guestPermission = GuestPermission.WRITE; + expect(permissionsService.mayRead(null, notes[8])).toBeTruthy(); + }); + it('READ', () => { + permissionsService.guestPermission = GuestPermission.READ; + expect(permissionsService.mayRead(null, notes[8])).toBeTruthy(); + }); + }); + }); + describe('mayWrite works with', () => { + it('Owner', () => { + permissionsService.guestPermission = GuestPermission.DENY; + expect(permissionsService.mayWrite(user1, notes[0])).toBeTruthy(); + expect(permissionsService.mayWrite(user1, notes[7])).toBeFalsy(); + }); + it('userPermission read', () => { + permissionsService.guestPermission = GuestPermission.DENY; + expect(permissionsService.mayWrite(user1, notes[1])).toBeFalsy(); + expect(permissionsService.mayWrite(user1, notes[2])).toBeFalsy(); + expect(permissionsService.mayWrite(user1, notes[3])).toBeFalsy(); + }); + it('userPermission write', () => { + permissionsService.guestPermission = GuestPermission.DENY; + expect(permissionsService.mayWrite(user1, notes[4])).toBeTruthy(); + expect(permissionsService.mayWrite(user1, notes[5])).toBeTruthy(); + expect(permissionsService.mayWrite(user1, notes[6])).toBeTruthy(); + expect(permissionsService.mayWrite(user1, notes[7])).toBeFalsy(); + }); + describe('guest permission', () => { + it('CREATE_ALIAS', () => { + permissionsService.guestPermission = GuestPermission.CREATE_ALIAS; + expect(permissionsService.mayWrite(null, notes[9])).toBeTruthy(); + }); + it('CREATE', () => { + permissionsService.guestPermission = GuestPermission.CREATE; + expect(permissionsService.mayWrite(null, notes[9])).toBeTruthy(); + }); + it('WRITE', () => { + permissionsService.guestPermission = GuestPermission.WRITE; + expect(permissionsService.mayWrite(null, notes[9])).toBeTruthy(); + }); + it('READ', () => { + permissionsService.guestPermission = GuestPermission.READ; + expect(permissionsService.mayWrite(null, notes[9])).toBeFalsy(); + }); + }); + }); + + /* + * Helper Object that arranges a list of GroupPermissions and if they allow a user to read or write a particular note. + */ + class NoteGroupPermissionWithResultForUser { + permissions: NoteGroupPermission[]; + allowsRead: boolean; + allowsWrite: boolean; + } + + /* + * Setup function to create all the groups we use in the tests. + */ + function createGroups(): { [id: string]: Group } { + const result: { [id: string]: Group } = {}; + + const everybody: Group = new Group(); + everybody.special = true; + everybody.name = 'everybody'; + result['everybody'] = everybody; + + const loggedIn = new Group(); + loggedIn.special = true; + loggedIn.name = 'loggedIn'; + result['loggedIn'] = loggedIn; + + const user1group = new Group(); + user1group.name = 'user1group'; + user1group.members = [user1]; + result['user1group'] = user1group; + + const user2group = new Group(); + user2group.name = 'user2group'; + user2group.members = [user2]; + result['user2group'] = user2group; + + const user1and2group = new Group(); + user1and2group.name = 'user1and2group'; + user1and2group.members = [user1, user2]; + result['user1and2group'] = user1and2group; + + const user2and1group = new Group(); + user2and1group.name = 'user2and1group'; + user2and1group.members = [user2, user1]; + result['user2and1group'] = user2and1group; + + return result; + } + + /* + * Create all GroupPermissions: For each group two GroupPermissions are created one with read permission and one with write permission. + */ + function createAllNoteGroupPermissions(): NoteGroupPermission[][] { + const groups = createGroups(); + + /* + * Helper function for creating GroupPermissions + */ + function createNoteGroupPermission( + group: Group, + write: boolean, + ): NoteGroupPermission { + const noteGroupPermission = new NoteGroupPermission(); + noteGroupPermission.canEdit = write; + noteGroupPermission.group = group; + return noteGroupPermission; + } + + const everybodyRead = createNoteGroupPermission(groups['everybody'], false); + const everybodyWrite = createNoteGroupPermission(groups['everybody'], true); + + const loggedInRead = createNoteGroupPermission(groups['loggedIn'], false); + const loggedInWrite = createNoteGroupPermission(groups['loggedIn'], true); + + const user1groupRead = createNoteGroupPermission( + groups['user1group'], + false, + ); + const user1groupWrite = createNoteGroupPermission( + groups['user1group'], + true, + ); + + const user2groupRead = createNoteGroupPermission( + groups['user2group'], + false, + ); + const user2groupWrite = createNoteGroupPermission( + groups['user2group'], + true, + ); + + const user1and2groupRead = createNoteGroupPermission( + groups['user1and2group'], + false, + ); + const user1and2groupWrite = createNoteGroupPermission( + groups['user1and2group'], + true, + ); + + const user2and1groupRead = createNoteGroupPermission( + groups['user2and1group'], + false, + ); + const user2and1groupWrite = createNoteGroupPermission( + groups['user2and1group'], + true, + ); + + return [ + [user1groupRead, user1and2groupRead, user2and1groupRead, null], // group0: allow user1 to read via group + [user2and1groupWrite, user1and2groupWrite, user1groupWrite, null], // group1: allow user1 to write via group + [everybodyRead, everybodyWrite, null], // group2: permissions of the special group everybody + [loggedInRead, loggedInWrite, null], // group3: permissions of the special group loggedIn + [user2groupWrite, user2groupRead, null], // group4: don't allow user1 to read or write via group + ]; + } + /* + * creates the matrix multiplication of group0 to group4 of createAllNoteGroupPermissions + */ + function createNoteGroupPermissionsCombinations( + guestPermission: GuestPermission, + ): NoteGroupPermissionWithResultForUser[] { + // for logged in users + const noteGroupPermissions = createAllNoteGroupPermissions(); + const result: NoteGroupPermissionWithResultForUser[] = []; + for (const group0 of noteGroupPermissions[0]) { + for (const group1 of noteGroupPermissions[1]) { + for (const group2 of noteGroupPermissions[2]) { + for (const group3 of noteGroupPermissions[3]) { + for (const group4 of noteGroupPermissions[4]) { + const insert = []; + let readPermission = false; + let writePermission = false; + if (group0 !== null) { + // user1 in ReadGroups + readPermission = true; + insert.push(group0); + } + if (group1 !== null) { + // user1 in WriteGroups + readPermission = true; + writePermission = true; + insert.push(group1); + } + + if (group2 !== null) { + // everybody group TODO config options + switch (guestPermission) { + case GuestPermission.CREATE_ALIAS: + case GuestPermission.CREATE: + case GuestPermission.WRITE: + writePermission = writePermission || group2.canEdit; + readPermission = true; + break; + case GuestPermission.READ: + readPermission = true; + } + insert.push(group2); + } + if (group3 !== null) { + // loggedIn users + readPermission = true; + writePermission = writePermission || group3.canEdit; + insert.push(group3); + } + if (group4 !== null) { + // user not in group + insert.push(group4); + } + result.push({ + permissions: insert, + allowsRead: readPermission, + allowsWrite: writePermission, + }); + } + } + } + } + } + return result; + } + + // inspired by https://stackoverflow.com/questions/9960908/permutations-in-javascript + function permutator( + inputArr: NoteGroupPermission[], + ): NoteGroupPermission[][] { + const results = []; + + function permute(arr, memo) { + let cur; + + for (let i = 0; i < arr.length; i++) { + cur = arr.splice(i, 1); + if (arr.length === 0) { + results.push(memo.concat(cur)); + } + permute(arr.slice(), memo.concat(cur)); + arr.splice(i, 0, cur[0]); + } + + return results; + } + + return permute(inputArr, []); + } + + // takes each set of permissions from createNoteGroupPermissionsCombinations, permute them and add them to the list + function permuteNoteGroupPermissions( + noteGroupPermissions: NoteGroupPermissionWithResultForUser[], + ): NoteGroupPermissionWithResultForUser[] { + const result: NoteGroupPermissionWithResultForUser[] = []; + for (const permission of noteGroupPermissions) { + const permutations = permutator(permission.permissions); + for (const permutation of permutations) { + result.push({ + permissions: permutation, + allowsRead: permission.allowsRead, + allowsWrite: permission.allowsWrite, + }); + } + } + return result; + } + + describe('check if groups work with', () => { + const guestPermission = GuestPermission.WRITE; + const rawPermissions = createNoteGroupPermissionsCombinations( + guestPermission, + ); + const permissions = permuteNoteGroupPermissions(rawPermissions); + let i = 0; + for (const permission of permissions) { + const note = createNote(user2); + note.groupPermissions = permission.permissions; + let permissionString = ''; + for (const perm of permission.permissions) { + permissionString += ' ' + perm.group.name + ':' + perm.canEdit; + } + it('mayWrite - test #' + i + ':' + permissionString, () => { + permissionsService.guestPermission = guestPermission; + expect(permissionsService.mayWrite(user1, note)).toEqual( + permission.allowsWrite, + ); + }); + it('mayRead - test #' + i + ':' + permissionString, () => { + permissionsService.guestPermission = guestPermission; + expect(permissionsService.mayRead(user1, note)).toEqual( + permission.allowsRead, + ); + }); + i++; + } + }); + + describe('mayCreate works for', () => { + it('logged in', () => { + permissionsService.guestPermission = GuestPermission.DENY; + expect(permissionsService.mayCreate(user1)).toBeTruthy(); + }); + it('guest denied', () => { + permissionsService.guestPermission = GuestPermission.DENY; + expect(permissionsService.mayCreate(null)).toBeFalsy(); + }); + it('guest read', () => { + permissionsService.guestPermission = GuestPermission.READ; + expect(permissionsService.mayCreate(null)).toBeFalsy(); + }); + it('guest write', () => { + permissionsService.guestPermission = GuestPermission.WRITE; + expect(permissionsService.mayCreate(null)).toBeFalsy(); + }); + it('guest create', () => { + permissionsService.guestPermission = GuestPermission.CREATE; + expect(permissionsService.mayCreate(null)).toBeTruthy(); + }); + it('guest create alias', () => { + permissionsService.guestPermission = GuestPermission.CREATE_ALIAS; + expect(permissionsService.mayCreate(null)).toBeTruthy(); + }); + }); + + describe('isOwner works', () => { + it('for positive case', () => { + permissionsService.guestPermission = GuestPermission.DENY; + expect(permissionsService.isOwner(user1, notes[0])).toBeTruthy(); + }); + it('for negative case', () => { + permissionsService.guestPermission = GuestPermission.DENY; + expect(permissionsService.isOwner(user1, notes[1])).toBeFalsy(); + }); + }); +});