mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-19 01:35:18 -04:00
fix(repository): Move backend code into subdirectory
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
86584e705f
commit
bf30cbcf48
272 changed files with 87 additions and 67 deletions
52
backend/src/permissions/note-group-permission.entity.ts
Normal file
52
backend/src/permissions/note-group-permission.entity.ts
Normal 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;
|
||||
}
|
||||
}
|
52
backend/src/permissions/note-user-permission.entity.ts
Normal file
52
backend/src/permissions/note-user-permission.entity.ts
Normal 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;
|
||||
}
|
||||
}
|
15
backend/src/permissions/permissions.enum.ts
Normal file
15
backend/src/permissions/permissions.enum.ts
Normal 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',
|
||||
}
|
25
backend/src/permissions/permissions.module.ts
Normal file
25
backend/src/permissions/permissions.module.ts
Normal 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 {}
|
1411
backend/src/permissions/permissions.service.spec.ts
Normal file
1411
backend/src/permissions/permissions.service.spec.ts
Normal file
File diff suppressed because it is too large
Load diff
356
backend/src/permissions/permissions.service.ts
Normal file
356
backend/src/permissions/permissions.service.ts
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue