fix(security): Posteingangs-Maske nur für Dokumente im eigenen Arbeitsbereich

PUT paperless/inbox/:id prüft jetzt pro Dokument: Posteingang-Rolle nur
für Dokumente mit Tag 1 (Posteingang), Buchhaltung nur für Dokumente mit
MANUELL_BEARBEITEN_TAG, Admins immer. Bisher ließen sich Mandant (Owner),
Felder und Titel beliebiger Paperless-Dokumente ändern, weil das Backend
mit dem Service-Token arbeitet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bjoernpoettkerandClaude Opus 5.5 committed 2026-11-04 10:46:43 +01:00
1 parent 3f98dfd98f
commit cedd7c78f4
4 files changed
+97 -4

No files matched your search

@@ -0,0 +1,45 @@
import { Permission } from '../auth/permissions.enum';
import {
POSTEINGANG_TAG_ID,
darfDokumentBearbeiten,
} from './dokument-zugriff.util';
const MANUELL = 6;
describe('darfDokumentBearbeiten', () => {
it('Posteingang-Rolle: nur Dokumente mit Posteingang-Tag', () => {
const rechte = [Permission.VIEW_INBOX];
expect(
darfDokumentBearbeiten([POSTEINGANG_TAG_ID, 9], rechte, MANUELL),
).toBe(true);
expect(darfDokumentBearbeiten([9], rechte, MANUELL)).toBe(false);
expect(darfDokumentBearbeiten([MANUELL], rechte, MANUELL)).toBe(false);
});
it('Buchhaltung: Dokumente mit Manuell-Tag', () => {
const rechte = [Permission.PROCESS_MANUALLY];
expect(darfDokumentBearbeiten([MANUELL], rechte, MANUELL)).toBe(true);
expect(darfDokumentBearbeiten([POSTEINGANG_TAG_ID], rechte, MANUELL)).toBe(
false,
);
});
it('Admins dürfen alles', () => {
expect(darfDokumentBearbeiten([], [Permission.MANAGE_ALL], MANUELL)).toBe(
true,
);
});
it('andere Rollen und fehlende Tags: nein', () => {
expect(
darfDokumentBearbeiten(
[POSTEINGANG_TAG_ID],
[Permission.VIEW_FREIGABE],
MANUELL,
),
).toBe(false);
expect(
darfDokumentBearbeiten(undefined, [Permission.VIEW_INBOX], MANUELL),
).toBe(false);
});
});
@@ -0,0 +1,25 @@
import { Permission } from '../auth/permissions.enum';
/** Paperless-Tag, der ein Dokument als „im Posteingang“ kennzeichnet. */
export const POSTEINGANG_TAG_ID = 1;
/**
* Darf der Benutzer dieses Paperless-Dokument über die Posteingangs-Maske
* bearbeiten? Der Backend-Service-Token umgeht die Paperless-Rechte, deshalb
* wird hier pro Dokument geprüft: Posteingang-Rolle nur für Dokumente mit
* Posteingang-Tag, Buchhaltung nur für Dokumente mit Manuell-Tag.
*/
export function darfDokumentBearbeiten(
tags: number[] | undefined,
permissions: Permission[],
manuellTagId: number,
): boolean {
if (permissions.includes(Permission.MANAGE_ALL)) return true;
const vorhanden = new Set((tags ?? []).map(Number));
return (
(vorhanden.has(POSTEINGANG_TAG_ID) &&
permissions.includes(Permission.VIEW_INBOX)) ||
(vorhanden.has(manuellTagId) &&
permissions.includes(Permission.PROCESS_MANUALLY))
);
}
@@ -17,11 +17,13 @@ import {
Res, Res,
Query, Query,
Request, Request,
ForbiddenException,
} from '@nestjs/common'; } from '@nestjs/common';
import type { Response } from 'express'; import type { Response } from 'express';
import { AllowApiKey, RequirePermissions } from '../auth/permissions.decorator'; import { AllowApiKey, RequirePermissions } from '../auth/permissions.decorator';
import { Permission } from '../auth/permissions.enum'; import { Permission } from '../auth/permissions.enum';
import { entferneGeschuetzteStatusfelder } from './status-felder.util'; import { entferneGeschuetzteStatusfelder } from './status-felder.util';
import { darfDokumentBearbeiten } from './dokument-zugriff.util';
import { FileInterceptor } from '@nestjs/platform-express'; import { FileInterceptor } from '@nestjs/platform-express';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
@@ -392,6 +394,23 @@ export class PaperlessController {
// Fetch from paperless // Fetch from paperless
const oldDocument = await this.paperlessService.getDocument(documentId); const oldDocument = await this.paperlessService.getDocument(documentId);
// Nur Dokumente aus dem eigenen Arbeitsbereich (Posteingang bzw. Manuell)
// – sonst ließen sich Mandant/Felder beliebiger Dokumente ändern.
if (
!darfDokumentBearbeiten(
oldDocument.tags as number[] | undefined,
req.user?.permissions ?? [],
this.paperlessService.getManuellTagId(),
)
) {
this.logger.warn(
`Dokument ${documentId}: Bearbeitung abgelehnt – nicht im Posteingang/Manuell-Bereich des Benutzers (${req.user?.preferredUsername ?? 'unbekannt'})`,
);
throw new ForbiddenException(
'Dieses Dokument liegt nicht in Ihrem Posteingang bzw. Ihrer Bearbeitungsliste',
);
}
oldDocument.document_type = body.documentType ?? oldDocument.document_type; oldDocument.document_type = body.documentType ?? oldDocument.document_type;
oldDocument.owner = body.mandant ?? oldDocument.owner; oldDocument.owner = body.mandant ?? oldDocument.owner;
oldDocument.correspondent = body.correspondent ?? oldDocument.correspondent; oldDocument.correspondent = body.correspondent ?? oldDocument.correspondent;
@@ -215,11 +215,15 @@ export class PaperlessService {
return response.data.results; return response.data.results;
} }
async getManuellDocuments(): Promise<any[]> { /** Tag der Liste „Manuell bearbeiten“ (Env-Werte kommen als String). */
const errorTag = this.configService.get<number>( getManuellTagId(): number {
'MANUELL_BEARBEITEN_TAG', return Number(
6, this.configService.get<number | string>('MANUELL_BEARBEITEN_TAG', 6),
); );
}
async getManuellDocuments(): Promise<any[]> {
const errorTag = this.getManuellTagId();
const response = await this.client.get('/documents/', { const response = await this.client.get('/documents/', {
params: { params: {
page: 1, page: 1,