refactor: replace permission check methods with ordered permission enum

This commit replaces the "mayWrite", "mayRead" and "checkPermissionOnNote"
functions with one that returns a sortable permission value.
This is done because many places in the code need to do actions based on the fact if
the user has no, read or write access. If done with the may-functions then the permission
data need to be looked through multiple times.

Also, the whole check code is split into more functions that are tested separately and make it easier
to understand the process.

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-05-10 22:50:03 +02:00
parent 4e298cccfb
commit a852c79947
15 changed files with 925 additions and 787 deletions

View file

@ -9,6 +9,7 @@ import { Mock } from 'ts-mockery';
import { AppConfig } from '../../config/app.config';
import { ConsoleLoggerService } from '../../logger/console-logger.service';
import { Note } from '../../notes/note.entity';
import { NotePermission } from '../../permissions/note-permission.enum';
import { PermissionsService } from '../../permissions/permissions.service';
import { Revision } from '../../revisions/revision.entity';
import { RevisionsService } from '../../revisions/revisions.service';
@ -91,9 +92,15 @@ describe('RealtimeNoteService', () => {
mockedAppConfig = Mock.of<AppConfig>({ persistInterval: 0 });
mockedPermissionService = Mock.of<PermissionsService>({
mayRead: async (user: User) =>
[readWriteUsername, onlyReadUsername].includes(user?.username),
mayWrite: async (user: User) => user?.username === readWriteUsername,
determinePermission: async (user: User | null) => {
if (user?.username === readWriteUsername) {
return NotePermission.WRITE;
} else if (user?.username === onlyReadUsername) {
return NotePermission.READ;
} else {
return NotePermission.DENY;
}
},
});
const schedulerRegistry = Mock.of<SchedulerRegistry>({

View file

@ -12,6 +12,7 @@ import appConfiguration, { AppConfig } from '../../config/app.config';
import { NoteEvent } from '../../events';
import { ConsoleLoggerService } from '../../logger/console-logger.service';
import { Note } from '../../notes/note.entity';
import { NotePermission } from '../../permissions/note-permission.enum';
import { PermissionsService } from '../../permissions/permissions.service';
import { RevisionsService } from '../../revisions/revisions.service';
import { RealtimeConnection } from './realtime-connection';
@ -126,24 +127,18 @@ export class RealtimeNoteService implements BeforeApplicationShutdown {
note: Note,
): Promise<void> {
for (const connection of connections) {
if (await this.connectionCanRead(connection, note)) {
connection.acceptEdits = await this.permissionService.mayWrite(
connection.getUser(),
note,
);
} else {
const permission = await this.permissionService.determinePermission(
connection.getUser(),
note,
);
if (permission === NotePermission.DENY) {
connection.getTransporter().disconnect();
} else {
connection.acceptEdits = permission > NotePermission.READ;
}
}
}
private async connectionCanRead(
connection: RealtimeConnection,
note: Note,
): Promise<boolean> {
return await this.permissionService.mayRead(connection.getUser(), note);
}
@OnEvent(NoteEvent.DELETION)
public handleNoteDeleted(noteId: Note['id']): void {
const realtimeNote = this.realtimeNoteStore.find(noteId);