fix(repository): Move backend code into subdirectory

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-10-02 20:10:32 +02:00 committed by David Mehren
parent 86584e705f
commit bf30cbcf48
272 changed files with 87 additions and 67 deletions

View file

@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
Column,
Entity,
Index,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Group } from '../groups/group.entity';
import { Note } from '../notes/note.entity';
@Entity()
@Index(['group', 'note'], { unique: true })
export class NoteGroupPermission {
@PrimaryGeneratedColumn()
id: number;
@ManyToOne((_) => Group, {
onDelete: 'CASCADE', // This deletes the NoteGroupPermission, when the associated Group is deleted
orphanedRowAction: 'delete', // This ensures the row of the NoteGroupPermission is deleted when no group references it anymore
})
group: Promise<Group>;
@ManyToOne((_) => Note, (note) => note.groupPermissions, {
onDelete: 'CASCADE', // This deletes the NoteGroupPermission, when the associated Note is deleted
orphanedRowAction: 'delete', // This ensures the row of the NoteGroupPermission is deleted when no note references it anymore
})
note: Promise<Note>;
@Column()
canEdit: boolean;
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
public static create(
group: Group,
note: Note,
canEdit: boolean,
): NoteGroupPermission {
const groupPermission = new NoteGroupPermission();
groupPermission.group = Promise.resolve(group);
groupPermission.note = Promise.resolve(note);
groupPermission.canEdit = canEdit;
return groupPermission;
}
}

View file

@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
Column,
Entity,
Index,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Note } from '../notes/note.entity';
import { User } from '../users/user.entity';
@Entity()
@Index(['user', 'note'], { unique: true })
export class NoteUserPermission {
@PrimaryGeneratedColumn()
id: number;
@ManyToOne((_) => User, {
onDelete: 'CASCADE', // This deletes the NoteUserPermission, when the associated Note is deleted
orphanedRowAction: 'delete', // This ensures the row of the NoteUserPermission is deleted when no user references it anymore
})
user: Promise<User>;
@ManyToOne((_) => Note, (note) => note.userPermissions, {
onDelete: 'CASCADE', // This deletes the NoteUserPermission, when the associated Note is deleted
orphanedRowAction: 'delete', // This ensures the row of the NoteUserPermission is deleted when no note references it anymore
})
note: Promise<Note>;
@Column()
canEdit: boolean;
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
public static create(
user: User,
note: Note,
canEdit: boolean,
): NoteUserPermission {
const userPermission = new NoteUserPermission();
userPermission.user = Promise.resolve(user);
userPermission.note = Promise.resolve(note);
userPermission.canEdit = canEdit;
return userPermission;
}
}

View file

@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* Represents the Permissions a user may hold in a request
*/
export enum Permission {
READ = 'read',
WRITE = 'write',
CREATE = 'create',
OWNER = 'owner',
}

View file

@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { GroupsModule } from '../groups/groups.module';
import { LoggerModule } from '../logger/logger.module';
import { Note } from '../notes/note.entity';
import { UsersModule } from '../users/users.module';
import { PermissionsService } from './permissions.service';
@Module({
imports: [
TypeOrmModule.forFeature([Note]),
UsersModule,
GroupsModule,
LoggerModule,
],
exports: [PermissionsService],
providers: [PermissionsService],
})
export class PermissionsModule {}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,356 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
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 { NoteEvent } from '../events';
import { Group } from '../groups/group.entity';
import { GroupsService } from '../groups/groups.service';
import { SpecialGroup } from '../groups/groups.special';
import { ConsoleLoggerService } from '../logger/console-logger.service';
import { NotePermissionsUpdateDto } from '../notes/note-permissions.dto';
import { Note } from '../notes/note.entity';
import { User } from '../users/user.entity';
import { UsersService } from '../users/users.service';
import { checkArrayForDuplicates } from '../utils/arrayDuplicatCheck';
import { NoteGroupPermission } from './note-group-permission.entity';
import { NoteUserPermission } from './note-user-permission.entity';
@Injectable()
export class PermissionsService {
constructor(
private usersService: UsersService,
private groupsService: GroupsService,
@InjectRepository(Note) private noteRepository: Repository<Note>,
private readonly logger: ConsoleLoggerService,
@Inject(noteConfiguration.KEY)
private noteConfig: NoteConfig,
private eventEmitter: EventEmitter2,
) {}
/**
* Checks if the given {@link User} is allowed to read the given {@link Note}.
*
* @async
* @param {User} user - The user whose permission should be checked. Value is null if guest access should be checked
* @param {Note} note - The note for which the permission should be checked
* @return if the user is allowed to read the note
*/
public async mayRead(user: User | null, note: Note): Promise<boolean> {
return (
(await this.isOwner(user, note)) ||
(await this.hasPermissionUser(user, note, false)) ||
(await this.hasPermissionGroup(user, note, false))
);
}
/**
* Checks if the given {@link User} is allowed to edit the given {@link Note}.
*
* @async
* @param {User} user - The user whose permission should be checked
* @param {Note} note - The note for which the permission should be checked. Value is null if guest access should be checked
* @return if the user is allowed to edit the note
*/
public async mayWrite(user: User | null, note: Note): Promise<boolean> {
return (
(await this.isOwner(user, note)) ||
(await this.hasPermissionUser(user, note, true)) ||
(await this.hasPermissionGroup(user, note, true))
);
}
/**
* Checks if the given {@link User} is allowed to create notes.
*
* @async
* @param {User} user - The user whose permission should be checked. Value is null if guest access should be checked
* @return if the user is allowed to create notes
*/
public mayCreate(user: User | null): boolean {
return user !== null || this.noteConfig.guestAccess === GuestAccess.CREATE;
}
async isOwner(user: User | null, note: Note): Promise<boolean> {
if (!user) return false;
const owner = await note.owner;
if (!owner) return false;
return owner.id === user.id;
}
// noinspection JSMethodCanBeStatic
private async hasPermissionUser(
user: User | null,
note: Note,
wantEdit: boolean,
): Promise<boolean> {
if (!user) {
return false;
}
for (const userPermission of await note.userPermissions) {
if (
(await userPermission.user).id === user.id &&
(userPermission.canEdit || !wantEdit)
) {
return true;
}
}
return false;
}
private async hasPermissionGroup(
user: User | null,
note: Note,
wantEdit: boolean,
): Promise<boolean> {
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 (
(user &&
(await groupPermission.group).name == SpecialGroup.LOGGED_IN) ||
(await groupPermission.group).name == SpecialGroup.EVERYONE
) {
return true;
}
} else {
// Handle normal groups
if (user) {
for (const member of await (
await groupPermission.group
).members) {
if (member.id === user.id) return true;
}
}
}
}
}
return false;
}
private notifyOthers(noteId: Note['id']): void {
this.eventEmitter.emit(NoteEvent.PERMISSION_CHANGE, noteId);
}
/**
* @async
* Update a notes permissions.
* @param {Note} note - the note
* @param {NotePermissionsUpdateDto} newPermissions - the permissions that should be applied to the note
* @return {Note} the note with the new permissions
* @throws {NotInDBError} there is no note with this id or alias
* @throws {PermissionsUpdateInconsistentError} the new permissions specify a user or group twice.
*/
async updateNotePermissions(
note: Note,
newPermissions: NotePermissionsUpdateDto,
): Promise<Note> {
const users = newPermissions.sharedToUsers.map(
(userPermission) => userPermission.username,
);
const groups = newPermissions.sharedToGroups.map(
(groupPermission) => groupPermission.groupName,
);
if (checkArrayForDuplicates(users) || checkArrayForDuplicates(groups)) {
this.logger.debug(
`The PermissionUpdate requested specifies the same user or group multiple times.`,
'updateNotePermissions',
);
throw new PermissionsUpdateInconsistentError(
'The PermissionUpdate requested specifies the same user or group multiple times.',
);
}
note.userPermissions = Promise.resolve([]);
note.groupPermissions = Promise.resolve([]);
// Create new userPermissions
for (const newUserPermission of newPermissions.sharedToUsers) {
const user = await this.usersService.getUserByUsername(
newUserPermission.username,
);
const createdPermission = NoteUserPermission.create(
user,
note,
newUserPermission.canEdit,
);
createdPermission.note = Promise.resolve(note);
(await note.userPermissions).push(createdPermission);
}
// Create groupPermissions
for (const newGroupPermission of newPermissions.sharedToGroups) {
const group = await this.groupsService.getGroupByName(
newGroupPermission.groupName,
);
const createdPermission = NoteGroupPermission.create(
group,
note,
newGroupPermission.canEdit,
);
createdPermission.note = Promise.resolve(note);
(await note.groupPermissions).push(createdPermission);
}
this.notifyOthers(note.id);
return await this.noteRepository.save(note);
}
/**
* @async
* Set permission for a specific user on a note.
* @param {Note} note - the note
* @param {User} permissionUser - the user for which the permission should be set
* @param {boolean} canEdit - specifies if the user can edit the note
* @return {Note} the note with the new permission
*/
async setUserPermission(
note: Note,
permissionUser: User,
canEdit: boolean,
): Promise<Note> {
const permissions = await note.userPermissions;
let permissionIndex = 0;
const permission = permissions.find(async (value, index) => {
permissionIndex = index;
return (await value.user).id == permissionUser.id;
});
if (permission != undefined) {
permission.canEdit = canEdit;
permissions[permissionIndex] = permission;
} else {
const noteUserPermission = NoteUserPermission.create(
permissionUser,
note,
canEdit,
);
(await note.userPermissions).push(noteUserPermission);
}
this.notifyOthers(note.id);
return await this.noteRepository.save(note);
}
/**
* @async
* Remove permission for a specific user on a note.
* @param {Note} note - the note
* @param {User} permissionUser - the user for which the permission should be set
* @return {Note} the note with the new permission
*/
async removeUserPermission(note: Note, permissionUser: User): Promise<Note> {
const permissions = await note.userPermissions;
const newPermissions = [];
for (const permission of permissions) {
if ((await permission.user).id != permissionUser.id) {
newPermissions.push(permission);
}
}
note.userPermissions = Promise.resolve(newPermissions);
this.notifyOthers(note.id);
return await this.noteRepository.save(note);
}
/**
* @async
* Set permission for a specific group on a note.
* @param {Note} note - the note
* @param {Group} permissionGroup - the group for which the permission should be set
* @param {boolean} canEdit - specifies if the group can edit the note
* @return {Note} the note with the new permission
*/
async setGroupPermission(
note: Note,
permissionGroup: Group,
canEdit: boolean,
): Promise<Note> {
this.logger.debug(
`Setting group permission for group ${permissionGroup.name} on note ${note.id}`,
'setGroupPermission',
);
const permissions = await note.groupPermissions;
let permissionIndex = 0;
const permission = permissions.find(async (value, index) => {
permissionIndex = index;
return (await value.group).id == permissionGroup.id;
});
if (permission != undefined) {
permission.canEdit = canEdit;
permissions[permissionIndex] = permission;
} else {
this.logger.debug(
`Permission does not exist yet, creating new one.`,
'setGroupPermission',
);
const noteGroupPermission = NoteGroupPermission.create(
permissionGroup,
note,
canEdit,
);
(await note.groupPermissions).push(noteGroupPermission);
}
this.notifyOthers(note.id);
return await this.noteRepository.save(note);
}
/**
* @async
* Remove permission for a specific group on a note.
* @param {Note} note - the note
* @param {Group} permissionGroup - the group for which the permission should be set
* @return {Note} the note with the new permission
*/
async removeGroupPermission(
note: Note,
permissionGroup: Group,
): Promise<Note> {
const permissions = await note.groupPermissions;
const newPermissions = [];
for (const permission of permissions) {
if ((await permission.group).id != permissionGroup.id) {
newPermissions.push(permission);
}
}
note.groupPermissions = Promise.resolve(newPermissions);
this.notifyOthers(note.id);
return await this.noteRepository.save(note);
}
/**
* @async
* Updates the owner of a note.
* @param {Note} note - the note to use
* @param {User} owner - the new owner
* @return {Note} the updated note
*/
async changeOwner(note: Note, owner: User): Promise<Note> {
note.owner = Promise.resolve(owner);
this.notifyOthers(note.id);
return await this.noteRepository.save(note);
}
}