feat(permission): use new HD_GUEST_ACCESS config

Co-authored-by: Tilman Vatteroth <git@tilmanvatteroth.de>
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
Signed-off-by: Philip Molares <philip.molares@udo.edu>
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
Philip Molares 2022-08-28 23:51:15 +02:00 committed by Yannick Bungers
parent 754e725b7f
commit fd949a77b8
9 changed files with 264 additions and 178 deletions

View file

@ -11,9 +11,7 @@ export enum GuestAccess {
CREATE = 'create',
}
export function getGuestAccessOrdinal(
guestAccess: GuestAccess,
): number {
export function getGuestAccessOrdinal(guestAccess: GuestAccess): number {
switch (guestAccess) {
case GuestAccess.DENY:
return 0;

View file

@ -7,9 +7,8 @@ import { ConfigFactoryKeyHost, registerAs } from '@nestjs/config';
import { ConfigFactory } from '@nestjs/config/dist/interfaces';
import { DefaultAccessPermission } from '../default-access-permission.enum';
import { DefaultAccessPermission } from '../default-access-permission.enum';
import { NoteConfig } from '../note.config';
import { GuestAccess } from '../guest_access.enum';
import { NoteConfig } from '../note.config';
export function createDefaultMockNoteConfig(): NoteConfig {
return {
@ -21,6 +20,7 @@ export function createDefaultMockNoteConfig(): NoteConfig {
loggedIn: DefaultAccessPermission.WRITE,
},
},
guestAccess: GuestAccess.CREATE,
};
}

View file

@ -14,6 +14,7 @@ import {
} from 'class-validator';
import { URL } from 'url';
import { GuestAccess } from '../config/guest_access.enum';
import { ServerVersion } from '../monitoring/server-status.dto';
import { BaseDto } from '../utils/base.dto.';
@ -140,10 +141,10 @@ export class IframeCommunicationDto extends BaseDto {
export class FrontendConfigDto extends BaseDto {
/**
* Is anonymous usage of the instance allowed?
* Maximum access level for guest users
*/
@IsBoolean()
allowAnonymous: boolean;
@IsString()
guestAccess: GuestAccess;
/**
* Are users allowed to register on this instance?

View file

@ -13,6 +13,7 @@ import { CustomizationConfig } from '../config/customization.config';
import { DefaultAccessPermission } from '../config/default-access-permission.enum';
import { ExternalServicesConfig } from '../config/external-services.config';
import { GitlabScope, GitlabVersion } from '../config/gitlab.enum';
import { GuestAccess } from '../config/guest_access.enum';
import { Loglevel } from '../config/loglevel.enum';
import { NoteConfig } from '../config/note.config';
import { LoggerModule } from '../logger/logger.module';
@ -192,7 +193,7 @@ describe('FrontendConfigService', () => {
return {
forbiddenNoteIds: [],
maxDocumentLength: 200,
guestAccess: true,
guestAccess: GuestAccess.CREATE,
permissions: {
default: {
everyone: DefaultAccessPermission.READ,
@ -358,7 +359,7 @@ describe('FrontendConfigService', () => {
const noteConfig: NoteConfig = {
forbiddenNoteIds: [],
maxDocumentLength: maxDocumentLength,
guestAccess: true,
guestAccess: GuestAccess.CREATE,
permissions: {
default: {
everyone: DefaultAccessPermission.READ,
@ -392,7 +393,7 @@ describe('FrontendConfigService', () => {
const service = module.get(FrontendConfigService);
const config = await service.getFrontendConfig();
expect(config.allowRegister).toEqual(enableRegister);
expect(config.allowAnonymous).toEqual(noteConfig.guestAccess);
expect(config.guestAccess).toEqual(noteConfig.guestAccess);
expect(config.branding.name).toEqual(customName);
expect(config.branding.logo).toEqual(
customLogo ? new URL(customLogo) : undefined,

View file

@ -46,7 +46,7 @@ export class FrontendConfigService {
async getFrontendConfig(): Promise<FrontendConfigDto> {
return {
allowAnonymous: this.noteConfig.guestAccess,
guestAccess: this.noteConfig.guestAccess,
allowRegister: this.authConfig.local.enableRegister,
authProviders: this.getAuthProviders(),
branding: this.getBranding(),

View file

@ -6,7 +6,12 @@
import { ConfigModule, ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { DataSource, EntityManager, Repository } from 'typeorm';
import {
DataSource,
EntityManager,
FindOptionsWhere,
Repository,
} from 'typeorm';
import { AuthToken } from '../auth/auth-token.entity';
import { Author } from '../authors/author.entity';
@ -80,10 +85,7 @@ describe('NotesService', () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
.mockImplementation(async (note: Note): Promise<Note> => note);
jest
.spyOn(groupRepo, 'findOne')
.mockResolvedValueOnce(everyone as Group)
.mockResolvedValueOnce(loggedin as Group);
mockGroupRepo();
const note = await service.createNote(content, null);
const revisions = await note.revisions;
revisions[0].edits = Promise.resolve([
@ -146,6 +148,20 @@ describe('NotesService', () => {
return [note, user, group];
}
function mockGroupRepo() {
jest.spyOn(groupRepo, 'findOne').mockReset();
jest.spyOn(groupRepo, 'findOne').mockImplementation((args) => {
const groupName = (args.where as FindOptionsWhere<Group>).name;
if (groupName === loggedin.name) {
return Promise.resolve(loggedin as Group);
} else if (groupName === everyone.name) {
return Promise.resolve(everyone as Group);
} else {
return Promise.resolve(null);
}
});
}
beforeEach(async () => {
/**
* We need to have *one* userRepo for both the providers array and
@ -255,10 +271,8 @@ describe('NotesService', () => {
const config = module.get<ConfigService>(ConfigService);
const noteConfig = config.get<NoteConfig>('noteConfig') as NoteConfig;
forbiddenNoteId = noteConfig.forbiddenNoteIds[0];
everyoneDefaultAccessPermission =
noteConfig.permissions.accessDefault.everyone;
loggedinDefaultAccessPermission =
noteConfig.permissions.accessDefault.loggedIn;
everyoneDefaultAccessPermission = noteConfig.permissions.default.everyone;
loggedinDefaultAccessPermission = noteConfig.permissions.default.loggedIn;
service = module.get<NotesService>(NotesService);
noteRepo = module.get<Repository<Note>>(getRepositoryToken(Note));
revisionRepo = module.get<Repository<Revision>>(
@ -340,10 +354,7 @@ describe('NotesService', () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
.mockImplementation(async (note: Note): Promise<Note> => note);
jest
.spyOn(groupRepo, 'findOne')
.mockResolvedValueOnce(everyone as Group)
.mockResolvedValueOnce(loggedin as Group);
mockGroupRepo();
});
it('without alias, without owner', async () => {
const newNote = await service.createNote(content, null);
@ -355,13 +366,17 @@ describe('NotesService', () => {
const groupPermissions = await newNote.groupPermissions;
expect(groupPermissions).toHaveLength(2);
expect(groupPermissions[0].canEdit).toEqual(
everyoneDefaultAccessPermission === DefaultAccessPermission.WRITE,
everyoneDefaultAccessPermission !== DefaultAccessPermission.WRITE,
);
expect((await groupPermissions[0].group).name).toEqual(
SpecialGroup.EVERYONE,
);
expect(groupPermissions[0].group.name).toEqual(SpecialGroup.EVERYONE);
expect(groupPermissions[1].canEdit).toEqual(
loggedinDefaultAccessPermission === DefaultAccessPermission.WRITE,
);
expect(groupPermissions[1].group.name).toEqual(SpecialGroup.LOGGED_IN);
expect((await groupPermissions[1].group).name).toEqual(
SpecialGroup.LOGGED_IN,
);
expect(await newNote.tags).toHaveLength(0);
expect(await newNote.owner).toBeNull();
expect(await newNote.aliases).toHaveLength(0);
@ -379,11 +394,15 @@ describe('NotesService', () => {
expect(groupPermissions[0].canEdit).toEqual(
everyoneDefaultAccessPermission === DefaultAccessPermission.WRITE,
);
expect(groupPermissions[0].group.name).toEqual(SpecialGroup.EVERYONE);
expect((await groupPermissions[0].group).name).toEqual(
SpecialGroup.EVERYONE,
);
expect(groupPermissions[1].canEdit).toEqual(
loggedinDefaultAccessPermission === DefaultAccessPermission.WRITE,
);
expect(groupPermissions[1].group.name).toEqual(SpecialGroup.LOGGED_IN);
expect((await groupPermissions[1].group).name).toEqual(
SpecialGroup.LOGGED_IN,
);
expect(await newNote.tags).toHaveLength(0);
expect(await newNote.owner).toEqual(user);
expect(await newNote.aliases).toHaveLength(0);
@ -398,13 +417,17 @@ describe('NotesService', () => {
const groupPermissions = await newNote.groupPermissions;
expect(groupPermissions).toHaveLength(2);
expect(groupPermissions[0].canEdit).toEqual(
everyoneDefaultAccessPermission === DefaultAccessPermission.WRITE,
everyoneDefaultAccessPermission !== DefaultAccessPermission.WRITE,
);
expect((await groupPermissions[0].group).name).toEqual(
SpecialGroup.EVERYONE,
);
expect(groupPermissions[0].group.name).toEqual(SpecialGroup.EVERYONE);
expect(groupPermissions[1].canEdit).toEqual(
loggedinDefaultAccessPermission === DefaultAccessPermission.WRITE,
);
expect(groupPermissions[1].group.name).toEqual(SpecialGroup.LOGGED_IN);
expect((await groupPermissions[1].group).name).toEqual(
SpecialGroup.LOGGED_IN,
);
expect(await newNote.tags).toHaveLength(0);
expect(await newNote.owner).toBeNull();
expect(await newNote.aliases).toHaveLength(1);
@ -422,11 +445,15 @@ describe('NotesService', () => {
expect(groupPermissions[0].canEdit).toEqual(
everyoneDefaultAccessPermission === DefaultAccessPermission.WRITE,
);
expect(groupPermissions[0].group.name).toEqual(SpecialGroup.EVERYONE);
expect((await groupPermissions[0].group).name).toEqual(
SpecialGroup.EVERYONE,
);
expect(groupPermissions[1].canEdit).toEqual(
loggedinDefaultAccessPermission === DefaultAccessPermission.WRITE,
);
expect(groupPermissions[1].group.name).toEqual(SpecialGroup.LOGGED_IN);
expect((await groupPermissions[1].group).name).toEqual(
SpecialGroup.LOGGED_IN,
);
expect(await newNote.tags).toHaveLength(0);
expect(await newNote.owner).toEqual(user);
expect(await newNote.aliases).toHaveLength(1);
@ -435,27 +462,24 @@ describe('NotesService', () => {
describe('with other', () => {
beforeEach(
() =>
(noteMockConfig.permissions.accessDefault.everyone =
(noteMockConfig.permissions.default.everyone =
DefaultAccessPermission.NONE),
);
it('default permissions', async () => {
jest.spyOn(groupRepo, 'findOne').mockReset();
jest
.spyOn(groupRepo, 'findOne')
.mockResolvedValueOnce(loggedin as Group);
mockGroupRepo();
const newNote = await service.createNote(content, user, alias);
const revisions = await newNote.revisions;
expect(revisions).toHaveLength(1);
expect(revisions[0].content).toEqual(content);
expect(await newNote.historyEntries).toHaveLength(1);
expect((await newNote.historyEntries)[0].user).toEqual(user);
expect(await (await newNote.historyEntries)[0].user).toEqual(user);
expect(await newNote.userPermissions).toHaveLength(0);
const groupPermissions = await newNote.groupPermissions;
expect(groupPermissions).toHaveLength(1);
expect(groupPermissions[0].canEdit).toEqual(
loggedinDefaultAccessPermission === DefaultAccessPermission.WRITE,
);
expect(groupPermissions[0].group.name).toEqual(
expect((await groupPermissions[0].group).name).toEqual(
SpecialGroup.LOGGED_IN,
);
expect(await newNote.tags).toHaveLength(0);
@ -473,10 +497,7 @@ describe('NotesService', () => {
});
it('alias is already used', async () => {
jest
.spyOn(groupRepo, 'findOne')
.mockResolvedValueOnce(everyone as Group)
.mockResolvedValueOnce(loggedin as Group);
mockGroupRepo();
jest.spyOn(noteRepo, 'save').mockImplementationOnce(async () => {
throw new Error();
});
@ -495,10 +516,7 @@ describe('NotesService', () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
.mockImplementation(async (note: Note): Promise<Note> => note);
jest
.spyOn(groupRepo, 'findOne')
.mockResolvedValueOnce(everyone as Group)
.mockResolvedValueOnce(loggedin as Group);
mockGroupRepo();
const newNote = await service.createNote(content, null);
const revisions = await newNote.revisions;
jest.spyOn(revisionRepo, 'findOne').mockResolvedValueOnce(revisions[0]);

View file

@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Optional } from '@mrdrogdrog/optional';
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@ -14,8 +15,8 @@ import {
ForbiddenIdError,
NotInDBError,
} from '../errors/errors';
import { Group } from '../groups/group.entity';
import { GroupsService } from '../groups/groups.service';
import { SpecialGroup } from '../groups/groups.special';
import { HistoryEntry } from '../history/history-entry.entity';
import { ConsoleLoggerService } from '../logger/console-logger.service';
import { NoteGroupPermission } from '../permissions/note-group-permission.entity';
@ -63,13 +64,6 @@ export class NotesService {
async getUserNotes(user: User): Promise<Note[]> {
const notes = await this.noteRepository
.createQueryBuilder('note')
.leftJoinAndSelect('note.aliases', 'alias')
.leftJoinAndSelect('note.owner', 'owner')
.leftJoinAndSelect('note.groupPermissions', 'group_permission')
.leftJoinAndSelect('group_permission.group', 'group')
.leftJoinAndSelect('note.userPermissions', 'user_permission')
.leftJoinAndSelect('user_permission.user', 'user')
.leftJoinAndSelect('note.tags', 'tag')
.where('note.owner = :user', { user: user.id })
.getMany();
if (notes === null) {

View file

@ -10,10 +10,16 @@ import { DataSource, EntityManager, Repository } from 'typeorm';
import { AuthToken } from '../auth/auth-token.entity';
import { Author } from '../authors/author.entity';
import { DefaultAccessPermission } from '../config/default-access-permission.enum';
import { GuestAccess } from '../config/guest_access.enum';
import appConfigMock from '../config/mock/app.config.mock';
import authConfigMock from '../config/mock/auth.config.mock';
import databaseConfigMock from '../config/mock/database.config.mock';
import noteConfigMock from '../config/mock/note.config.mock';
import {
createDefaultMockNoteConfig,
registerNoteConfig,
} from '../config/mock/note.config.mock';
import { NoteConfig } from '../config/note.config';
import { PermissionsUpdateInconsistentError } from '../errors/errors';
import { Group } from '../groups/group.entity';
import { GroupsModule } from '../groups/groups.module';
@ -36,7 +42,7 @@ 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';
import { PermissionsService } from './permissions.service';
describe('PermissionsService', () => {
let service: PermissionsService;
@ -44,6 +50,7 @@ describe('PermissionsService', () => {
let noteRepo: Repository<Note>;
let userRepo: Repository<User>;
let groupRepo: Repository<Group>;
const noteMockConfig: NoteConfig = createDefaultMockNoteConfig();
beforeAll(async () => {
/**
@ -98,7 +105,7 @@ describe('PermissionsService', () => {
appConfigMock,
databaseConfigMock,
authConfigMock,
noteConfigMock,
registerNoteConfig(noteMockConfig),
],
}),
GroupsModule,
@ -199,8 +206,11 @@ describe('PermissionsService', () => {
const everybody = {} as Group;
everybody.name = SpecialGroup.EVERYONE;
everybody.special = true;
const noteEverybodyRead = createNote(user1);
const noteEverybodyNone = createNote(user1);
noteEverybodyNone.groupPermissions = Promise.resolve([]);
const noteEverybodyRead = createNote(user1);
const noteGroupPermissionRead = {} as NoteGroupPermission;
noteGroupPermissionRead.group = Promise.resolve(everybody);
noteGroupPermissionRead.canEdit = false;
@ -210,7 +220,6 @@ describe('PermissionsService', () => {
]);
const noteEverybodyWrite = createNote(user1);
const noteGroupPermissionWrite = {} as NoteGroupPermission;
noteGroupPermissionWrite.group = Promise.resolve(everybody);
noteGroupPermissionWrite.canEdit = true;
@ -230,23 +239,21 @@ describe('PermissionsService', () => {
note7,
noteEverybodyRead,
noteEverybodyWrite,
noteEverybodyNone,
];
}
describe('mayRead works with', () => {
it('Owner', async () => {
service.guestPermission = GuestPermission.DENY;
expect(await service.mayRead(user1, notes[0])).toBeTruthy();
expect(await service.mayRead(user1, notes[7])).toBeFalsy();
});
it('userPermission read', async () => {
service.guestPermission = GuestPermission.DENY;
expect(await service.mayRead(user1, notes[1])).toBeTruthy();
expect(await service.mayRead(user1, notes[2])).toBeTruthy();
expect(await service.mayRead(user1, notes[3])).toBeTruthy();
});
it('userPermission write', async () => {
service.guestPermission = GuestPermission.DENY;
expect(await service.mayRead(user1, notes[4])).toBeTruthy();
expect(await service.mayRead(user1, notes[5])).toBeTruthy();
expect(await service.mayRead(user1, notes[6])).toBeTruthy();
@ -254,61 +261,155 @@ describe('PermissionsService', () => {
});
describe('guest permission', () => {
it('CREATE_ALIAS', async () => {
service.guestPermission = GuestPermission.CREATE_ALIAS;
beforeEach(() => {
noteMockConfig.permissions.default.loggedIn =
DefaultAccessPermission.WRITE;
noteMockConfig.permissions.default.everyone =
DefaultAccessPermission.WRITE;
});
describe('with guest access deny', () => {
beforeEach(() => {
noteMockConfig.guestAccess = GuestAccess.DENY;
});
it('guest permission none', async () => {
expect(await service.mayRead(null, notes[10])).toBeFalsy();
});
it('guest permission read', async () => {
expect(await service.mayRead(null, notes[8])).toBeFalsy();
});
it('guest permission write', async () => {
expect(await service.mayRead(null, notes[9])).toBeFalsy();
});
});
describe('with guest access read', () => {
beforeEach(() => {
noteMockConfig.guestAccess = GuestAccess.READ;
});
it('guest permission none', async () => {
expect(await service.mayRead(null, notes[10])).toBeFalsy();
});
it('guest permission read', async () => {
expect(await service.mayRead(null, notes[8])).toBeTruthy();
});
it('CREATE', async () => {
service.guestPermission = GuestPermission.CREATE;
it('guest permission write', async () => {
expect(await service.mayRead(null, notes[9])).toBeTruthy();
});
});
describe('with guest access write', () => {
beforeEach(() => {
noteMockConfig.guestAccess = GuestAccess.WRITE;
});
it('guest permission none', async () => {
expect(await service.mayRead(null, notes[10])).toBeFalsy();
});
it('guest permission read', async () => {
expect(await service.mayRead(null, notes[8])).toBeTruthy();
});
it('WRITE', async () => {
service.guestPermission = GuestPermission.WRITE;
it('guest permission write', async () => {
expect(await service.mayRead(null, notes[9])).toBeTruthy();
});
});
describe('with guest access create', () => {
beforeEach(() => {
noteMockConfig.guestAccess = GuestAccess.CREATE;
});
it('guest permission none', async () => {
expect(await service.mayRead(null, notes[10])).toBeFalsy();
});
it('guest permission read', async () => {
expect(await service.mayRead(null, notes[8])).toBeTruthy();
});
it('READ', async () => {
service.guestPermission = GuestPermission.READ;
expect(await service.mayRead(null, notes[8])).toBeTruthy();
it('guest permission write', async () => {
expect(await service.mayRead(null, notes[9])).toBeTruthy();
});
});
});
});
describe('mayWrite works with', () => {
it('Owner', async () => {
service.guestPermission = GuestPermission.DENY;
expect(await service.mayWrite(user1, notes[0])).toBeTruthy();
expect(await service.mayWrite(user1, notes[7])).toBeFalsy();
});
it('userPermission read', async () => {
service.guestPermission = GuestPermission.DENY;
expect(await service.mayWrite(user1, notes[1])).toBeFalsy();
expect(await service.mayWrite(user1, notes[2])).toBeFalsy();
expect(await service.mayWrite(user1, notes[3])).toBeFalsy();
});
it('userPermission write', async () => {
service.guestPermission = GuestPermission.DENY;
expect(await service.mayWrite(user1, notes[4])).toBeTruthy();
expect(await service.mayWrite(user1, notes[5])).toBeTruthy();
expect(await service.mayWrite(user1, notes[6])).toBeTruthy();
expect(await service.mayWrite(user1, notes[7])).toBeFalsy();
});
describe('guest permission', () => {
it('CREATE_ALIAS', async () => {
service.guestPermission = GuestPermission.CREATE_ALIAS;
expect(await service.mayWrite(null, notes[9])).toBeTruthy();
beforeEach(() => {
noteMockConfig.permissions.default.loggedIn =
DefaultAccessPermission.WRITE;
noteMockConfig.permissions.default.everyone =
DefaultAccessPermission.WRITE;
});
it('CREATE', async () => {
service.guestPermission = GuestPermission.CREATE;
expect(await service.mayWrite(null, notes[9])).toBeTruthy();
describe('with guest access deny', () => {
beforeEach(() => {
noteMockConfig.guestAccess = GuestAccess.DENY;
});
it('WRITE', async () => {
service.guestPermission = GuestPermission.WRITE;
expect(await service.mayWrite(null, notes[9])).toBeTruthy();
it('guest permission none', async () => {
expect(await service.mayWrite(null, notes[10])).toBeFalsy();
});
it('READ', async () => {
service.guestPermission = GuestPermission.READ;
it('guest permission read', async () => {
expect(await service.mayWrite(null, notes[8])).toBeFalsy();
});
it('guest permission write', async () => {
expect(await service.mayWrite(null, notes[9])).toBeFalsy();
});
});
describe('with guest access read', () => {
beforeEach(() => {
noteMockConfig.guestAccess = GuestAccess.READ;
});
it('guest permission none', async () => {
expect(await service.mayWrite(null, notes[10])).toBeFalsy();
});
it('guest permission read', async () => {
expect(await service.mayWrite(null, notes[8])).toBeFalsy();
});
it('guest permission write', async () => {
expect(await service.mayWrite(null, notes[9])).toBeFalsy();
});
});
describe('with guest access write', () => {
beforeEach(() => {
noteMockConfig.guestAccess = GuestAccess.WRITE;
});
it('guest permission none', async () => {
expect(await service.mayWrite(null, notes[10])).toBeFalsy();
});
it('guest permission read', async () => {
expect(await service.mayWrite(null, notes[8])).toBeFalsy();
});
it('guest permission write', async () => {
expect(await service.mayWrite(null, notes[9])).toBeTruthy();
});
});
describe('with guest access create', () => {
beforeEach(() => {
noteMockConfig.guestAccess = GuestAccess.CREATE;
});
it('guest permission none', async () => {
expect(await service.mayWrite(null, notes[10])).toBeFalsy();
});
it('guest permission read', async () => {
expect(await service.mayWrite(null, notes[8])).toBeFalsy();
});
it('guest permission write', async () => {
expect(await service.mayWrite(null, notes[9])).toBeTruthy();
});
});
});
});
/*
@ -326,19 +427,17 @@ describe('PermissionsService', () => {
function createGroups(): { [id: string]: Group } {
const result: { [id: string]: Group } = {};
const everybody: Group = Group.create(
result[SpecialGroup.EVERYONE] = Group.create(
SpecialGroup.EVERYONE,
SpecialGroup.EVERYONE,
true,
) as Group;
result[SpecialGroup.EVERYONE] = everybody;
const loggedIn = Group.create(
result[SpecialGroup.LOGGED_IN] = 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]);
@ -370,7 +469,7 @@ describe('PermissionsService', () => {
/*
* Create all GroupPermissions: For each group two GroupPermissions are created one with read permission and one with write permission.
*/
function createAllNoteGroupPermissions(): NoteGroupPermission[][] {
function createAllNoteGroupPermissions(): (NoteGroupPermission | null)[][] {
const groups = createGroups();
/*
@ -450,7 +549,7 @@ describe('PermissionsService', () => {
* creates the matrix multiplication of group0 to group4 of createAllNoteGroupPermissions
*/
function createNoteGroupPermissionsCombinations(
guestPermission: GuestPermission,
everyoneDefaultPermission: DefaultAccessPermission,
): NoteGroupPermissionWithResultForUser[] {
// for logged in users
const noteGroupPermissions = createAllNoteGroupPermissions();
@ -476,15 +575,14 @@ describe('PermissionsService', () => {
}
if (group2 !== null) {
// everybody group TODO config options
switch (guestPermission) {
case GuestPermission.CREATE_ALIAS:
case GuestPermission.CREATE:
case GuestPermission.WRITE:
if (
everyoneDefaultPermission === DefaultAccessPermission.WRITE
) {
writePermission = writePermission || group2.canEdit;
readPermission = true;
break;
case GuestPermission.READ:
} else if (
everyoneDefaultPermission === DefaultAccessPermission.READ
) {
readPermission = true;
}
insert.push(group2);
@ -558,9 +656,9 @@ describe('PermissionsService', () => {
}
describe('check if groups work with', () => {
const guestPermission = GuestPermission.WRITE;
const rawPermissions =
createNoteGroupPermissionsCombinations(guestPermission);
const rawPermissions = createNoteGroupPermissionsCombinations(
DefaultAccessPermission.WRITE,
);
const permissions = permuteNoteGroupPermissions(rawPermissions);
let i = 0;
for (const permission of permissions) {
@ -571,13 +669,11 @@ describe('PermissionsService', () => {
permissionString += ` ${perm.id}:${String(perm.canEdit)}`;
}
it(`mayWrite - test #${i}:${permissionString}`, async () => {
service.guestPermission = guestPermission;
expect(await service.mayWrite(user1, note)).toEqual(
permission.allowsWrite,
);
});
it(`mayRead - test #${i}:${permissionString}`, async () => {
service.guestPermission = guestPermission;
expect(await service.mayRead(user1, note)).toEqual(
permission.allowsRead,
);
@ -586,40 +682,33 @@ describe('PermissionsService', () => {
}
});
describe('mayCreate works for', () => {
it('logged in', () => {
service.guestPermission = GuestPermission.DENY;
describe('mayCreate', () => {
it('allows creation for logged in', () => {
expect(service.mayCreate(user1)).toBeTruthy();
});
it('guest denied', () => {
service.guestPermission = GuestPermission.DENY;
expect(service.mayCreate(null)).toBeFalsy();
});
it('guest read', () => {
service.guestPermission = GuestPermission.READ;
expect(service.mayCreate(null)).toBeFalsy();
});
it('guest write', () => {
service.guestPermission = GuestPermission.WRITE;
expect(service.mayCreate(null)).toBeFalsy();
});
it('guest create', () => {
service.guestPermission = GuestPermission.CREATE;
it('allows creation of notes for guests with permission', () => {
noteMockConfig.guestAccess = GuestAccess.CREATE;
noteMockConfig.permissions.default.loggedIn =
DefaultAccessPermission.WRITE;
noteMockConfig.permissions.default.everyone =
DefaultAccessPermission.WRITE;
expect(service.mayCreate(null)).toBeTruthy();
});
it('guest create alias', () => {
service.guestPermission = GuestPermission.CREATE_ALIAS;
expect(service.mayCreate(null)).toBeTruthy();
it('denies creation of notes for guests without permission', () => {
noteMockConfig.guestAccess = GuestAccess.WRITE;
noteMockConfig.permissions.default.loggedIn =
DefaultAccessPermission.WRITE;
noteMockConfig.permissions.default.everyone =
DefaultAccessPermission.WRITE;
expect(service.mayCreate(null)).toBeFalsy();
});
});
describe('isOwner works', () => {
it('for positive case', async () => {
service.guestPermission = GuestPermission.DENY;
expect(await service.isOwner(user1, notes[0])).toBeTruthy();
});
it('for negative case', async () => {
service.guestPermission = GuestPermission.DENY;
expect(await service.isOwner(user1, notes[1])).toBeFalsy();
});
});

View file

@ -3,10 +3,15 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import {
getGuestAccessOrdinal,
GuestAccess,
} from '../config/guest_access.enum';
import noteConfiguration, { NoteConfig } from '../config/note.config';
import { PermissionsUpdateInconsistentError } from '../errors/errors';
import { Group } from '../groups/group.entity';
import { GroupsService } from '../groups/groups.service';
@ -20,26 +25,17 @@ import { checkArrayForDuplicates } from '../utils/arrayDuplicatCheck';
import { NoteGroupPermission } from './note-group-permission.entity';
import { NoteUserPermission } from './note-user-permission.entity';
// TODO move to config or remove
export enum GuestPermission {
DENY = 'deny',
READ = 'read',
WRITE = 'write',
CREATE = 'create',
CREATE_ALIAS = 'createAlias',
}
@Injectable()
export class PermissionsService {
constructor(
public usersService: UsersService,
public groupsService: GroupsService,
private usersService: UsersService,
private groupsService: GroupsService,
@InjectRepository(Note) private noteRepository: Repository<Note>,
private readonly logger: ConsoleLoggerService,
@Inject(noteConfiguration.KEY)
private noteConfig: NoteConfig,
) {}
public guestPermission: GuestPermission; // TODO change to configOption
/**
* Checks if the given {@link User} is allowed to read the given {@link Note}.
*
@ -80,18 +76,7 @@ export class PermissionsService {
* @return if the user is allowed to create notes
*/
public mayCreate(user: User | null): boolean {
if (user) {
return true;
} else {
if (
this.guestPermission == GuestPermission.CREATE ||
this.guestPermission == GuestPermission.CREATE_ALIAS
) {
// TODO change to guestPermission to config option
return true;
}
}
return false;
return user !== null || this.noteConfig.guestAccess === GuestAccess.CREATE;
}
async isOwner(user: User | null, note: Note): Promise<boolean> {
@ -101,6 +86,7 @@ export class PermissionsService {
return owner.id === user.id;
}
// noinspection JSMethodCanBeStatic
private async hasPermissionUser(
user: User | null,
note: Note,
@ -125,28 +111,27 @@ export class PermissionsService {
note: Note,
wantEdit: boolean,
): Promise<boolean> {
// TODO: Get real config value
let guestsAllowed = false;
switch (this.guestPermission) {
case GuestPermission.CREATE_ALIAS:
case GuestPermission.CREATE:
case GuestPermission.WRITE:
guestsAllowed = true;
break;
case GuestPermission.READ:
guestsAllowed = !wantEdit;
if (user === null) {
if (
(!wantEdit &&
getGuestAccessOrdinal(this.noteConfig.guestAccess) <
getGuestAccessOrdinal(GuestAccess.READ)) ||
(wantEdit &&
getGuestAccessOrdinal(this.noteConfig.guestAccess) <
getGuestAccessOrdinal(GuestAccess.WRITE))
) {
return false;
}
}
for (const groupPermission of await note.groupPermissions) {
if (groupPermission.canEdit || !wantEdit) {
// Handle special groups
if ((await groupPermission.group).special) {
if ((await groupPermission.group).name == SpecialGroup.LOGGED_IN) {
return true;
}
if (
(await groupPermission.group).name == SpecialGroup.EVERYONE &&
(groupPermission.canEdit || !wantEdit) &&
guestsAllowed
(user &&
(await groupPermission.group).name == SpecialGroup.LOGGED_IN) ||
(await groupPermission.group).name == SpecialGroup.EVERYONE
) {
return true;
}