/* * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import { ConfigModule } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { AuthToken } from '../auth/auth-token.entity'; import { Author } from '../authors/author.entity'; import appConfigMock from '../config/mock/app.config.mock'; import { Group } from '../groups/group.entity'; import { SpecialGroup } from '../groups/groups.special'; import { Identity } from '../identity/identity.entity'; import { LoggerModule } from '../logger/logger.module'; import { Alias } from '../notes/alias.entity'; import { Note } from '../notes/note.entity'; import { NotesModule } from '../notes/notes.module'; import { Tag } from '../notes/tag.entity'; import { Edit } from '../revisions/edit.entity'; import { Revision } from '../revisions/revision.entity'; import { Session } from '../users/session.entity'; import { User } from '../users/user.entity'; import { UsersModule } from '../users/users.module'; import { NoteGroupPermission } from './note-group-permission.entity'; import { NoteUserPermission } from './note-user-permission.entity'; import { PermissionsModule } from './permissions.module'; import { GuestPermission, PermissionsService } from './permissions.service'; describe('PermissionsService', () => { let permissionsService: PermissionsService; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [PermissionsService], imports: [ PermissionsModule, UsersModule, LoggerModule, NotesModule, ConfigModule.forRoot({ isGlobal: true, load: [appConfigMock], }), ], }) .overrideProvider(getRepositoryToken(User)) .useValue({}) .overrideProvider(getRepositoryToken(AuthToken)) .useValue({}) .overrideProvider(getRepositoryToken(Identity)) .useValue({}) .overrideProvider(getRepositoryToken(Edit)) .useValue({}) .overrideProvider(getRepositoryToken(Revision)) .useValue({}) .overrideProvider(getRepositoryToken(Note)) .useValue({}) .overrideProvider(getRepositoryToken(Tag)) .useValue({}) .overrideProvider(getRepositoryToken(NoteGroupPermission)) .useValue({}) .overrideProvider(getRepositoryToken(NoteUserPermission)) .useValue({}) .overrideProvider(getRepositoryToken(Group)) .useValue({}) .overrideProvider(getRepositoryToken(Session)) .useValue({}) .overrideProvider(getRepositoryToken(Author)) .useValue({}) .overrideProvider(getRepositoryToken(Alias)) .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 = SpecialGroup.EVERYONE; 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', async () => { permissionsService.guestPermission = GuestPermission.DENY; expect(await permissionsService.mayRead(user1, notes[0])).toBeTruthy(); expect(await permissionsService.mayRead(user1, notes[7])).toBeFalsy(); }); it('userPermission read', async () => { permissionsService.guestPermission = GuestPermission.DENY; expect(await permissionsService.mayRead(user1, notes[1])).toBeTruthy(); expect(await permissionsService.mayRead(user1, notes[2])).toBeTruthy(); expect(await permissionsService.mayRead(user1, notes[3])).toBeTruthy(); }); it('userPermission write', async () => { permissionsService.guestPermission = GuestPermission.DENY; expect(await permissionsService.mayRead(user1, notes[4])).toBeTruthy(); expect(await permissionsService.mayRead(user1, notes[5])).toBeTruthy(); expect(await permissionsService.mayRead(user1, notes[6])).toBeTruthy(); expect(await permissionsService.mayRead(user1, notes[7])).toBeFalsy(); }); describe('guest permission', () => { it('CREATE_ALIAS', async () => { permissionsService.guestPermission = GuestPermission.CREATE_ALIAS; expect(await permissionsService.mayRead(null, notes[8])).toBeTruthy(); }); it('CREATE', async () => { permissionsService.guestPermission = GuestPermission.CREATE; expect(await permissionsService.mayRead(null, notes[8])).toBeTruthy(); }); it('WRITE', async () => { permissionsService.guestPermission = GuestPermission.WRITE; expect(await permissionsService.mayRead(null, notes[8])).toBeTruthy(); }); it('READ', async () => { permissionsService.guestPermission = GuestPermission.READ; expect(await permissionsService.mayRead(null, notes[8])).toBeTruthy(); }); }); }); describe('mayWrite works with', () => { it('Owner', async () => { permissionsService.guestPermission = GuestPermission.DENY; expect(await permissionsService.mayWrite(user1, notes[0])).toBeTruthy(); expect(await permissionsService.mayWrite(user1, notes[7])).toBeFalsy(); }); it('userPermission read', async () => { permissionsService.guestPermission = GuestPermission.DENY; expect(await permissionsService.mayWrite(user1, notes[1])).toBeFalsy(); expect(await permissionsService.mayWrite(user1, notes[2])).toBeFalsy(); expect(await permissionsService.mayWrite(user1, notes[3])).toBeFalsy(); }); it('userPermission write', async () => { permissionsService.guestPermission = GuestPermission.DENY; expect(await permissionsService.mayWrite(user1, notes[4])).toBeTruthy(); expect(await permissionsService.mayWrite(user1, notes[5])).toBeTruthy(); expect(await permissionsService.mayWrite(user1, notes[6])).toBeTruthy(); expect(await permissionsService.mayWrite(user1, notes[7])).toBeFalsy(); }); describe('guest permission', () => { it('CREATE_ALIAS', async () => { permissionsService.guestPermission = GuestPermission.CREATE_ALIAS; expect(await permissionsService.mayWrite(null, notes[9])).toBeTruthy(); }); it('CREATE', async () => { permissionsService.guestPermission = GuestPermission.CREATE; expect(await permissionsService.mayWrite(null, notes[9])).toBeTruthy(); }); it('WRITE', async () => { permissionsService.guestPermission = GuestPermission.WRITE; expect(await permissionsService.mayWrite(null, notes[9])).toBeTruthy(); }); it('READ', async () => { permissionsService.guestPermission = GuestPermission.READ; expect(await 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 = Group.create( SpecialGroup.EVERYONE, SpecialGroup.EVERYONE, true, ) as Group; result[SpecialGroup.EVERYONE] = everybody; const loggedIn = Group.create( SpecialGroup.LOGGED_IN, SpecialGroup.LOGGED_IN, true, ) as Group; result[SpecialGroup.LOGGED_IN] = loggedIn; const user1group = Group.create('user1group', 'user1group', false) as Group; user1group.members = Promise.resolve([user1]); result['user1group'] = user1group; const user2group = Group.create('user2group', 'user2group', false) as Group; user2group.members = Promise.resolve([user2]); result['user2group'] = user2group; const user1and2group = Group.create( 'user1and2group', 'user1and2group', false, ) as Group; user1and2group.members = Promise.resolve([user1, user2]); result['user1and2group'] = user1and2group; const user2and1group = Group.create( 'user2and1group', 'user2and1group', false, ) as Group; user2and1group.members = Promise.resolve([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 { return NoteGroupPermission.create(group, {} as Note, write); } const everybodyRead = createNoteGroupPermission( groups[SpecialGroup.EVERYONE], false, ); const everybodyWrite = createNoteGroupPermission( groups[SpecialGroup.EVERYONE], true, ); const loggedInRead = createNoteGroupPermission( groups[SpecialGroup.LOGGED_IN], false, ); const loggedInWrite = createNoteGroupPermission( groups[SpecialGroup.LOGGED_IN], 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: NoteGroupPermission[] = []; 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: NoteGroupPermission[][] = []; function permute( arr: NoteGroupPermission[], memo: NoteGroupPermission[], ): NoteGroupPermission[][] { let cur: NoteGroupPermission[]; 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}:${String(perm.canEdit)}`; } it(`mayWrite - test #${i}:${permissionString}`, async () => { permissionsService.guestPermission = guestPermission; expect(await permissionsService.mayWrite(user1, note)).toEqual( permission.allowsWrite, ); }); it(`mayRead - test #${i}:${permissionString}`, async () => { permissionsService.guestPermission = guestPermission; expect(await 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(); }); }); });