hedgedoc/src/permissions/permissions.service.spec.ts
David Mehren 0c5fdf4201
refactor(group): lazy-load relations
Signed-off-by: David Mehren <git@herrmehren.de>
2021-11-16 19:05:28 +01:00

562 lines
20 KiB
TypeScript

/*
* 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>(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();
});
});
});