/* eslint-disable @typescript-eslint/unbound-method -- Handler dienen nur als Metadaten-Schlüssel */ import { ExecutionContext, ForbiddenException } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { PermissionsGuard } from './permissions.guard'; import { AllowAnyAuthenticated, AllowApiKey, RequirePermissions, } from './permissions.decorator'; import { Public } from './public.decorator'; import { Permission } from './permissions.enum'; class Routen { @RequirePermissions(Permission.VIEW_MAIL) mail() {} @AllowAnyAuthenticated() eigeneEinstellungen() {} ohneAngabe() {} @Public() oeffentlich() {} @AllowApiKey() @RequirePermissions(Permission.MANAGE_SETTINGS) agent() {} @AllowApiKey() nurApiKey() {} } @RequirePermissions(Permission.VIEW_INBOX) class KlasseMitRecht { route() {} } function ctx( handler: (...args: unknown[]) => unknown, request: Record, cls: object = Routen, ): ExecutionContext { return { getHandler: () => handler, getClass: () => cls, switchToHttp: () => ({ getRequest: () => request }), } as unknown as ExecutionContext; } const r = Routen.prototype; const nutzer = (...permissions: Permission[]) => ({ method: 'GET', url: '/x', user: { permissions }, }); const apiKey = { method: 'GET', url: '/x', apiKeyMetadata: { id: '1', name: 'Agent' }, }; describe('PermissionsGuard', () => { const guard = new PermissionsGuard(new Reflector()); it('erlaubt Benutzer mit passender Berechtigung', () => { expect(guard.canActivate(ctx(r.mail, nutzer(Permission.VIEW_MAIL)))).toBe( true, ); }); it('verweigert Benutzer ohne passende Berechtigung', () => { expect(guard.canActivate(ctx(r.mail, nutzer(Permission.VIEW_INBOX)))).toBe( false, ); }); it('erlaubt Admins (MANAGE_ALL) überall mit Rechteangabe', () => { expect(guard.canActivate(ctx(r.mail, nutzer(Permission.MANAGE_ALL)))).toBe( true, ); }); it('übernimmt Rechte von der Klasse', () => { const k = KlasseMitRecht.prototype; expect( guard.canActivate( ctx(k.route, nutzer(Permission.VIEW_INBOX), KlasseMitRecht), ), ).toBe(true); expect( guard.canActivate( ctx(k.route, nutzer(Permission.VIEW_MAIL), KlasseMitRecht), ), ).toBe(false); }); it('verweigert Routen ohne Rechteangabe (deny by default) – auch Admins', () => { expect( guard.canActivate(ctx(r.ohneAngabe, nutzer(Permission.MANAGE_ALL))), ).toBe(false); }); it('erlaubt @AllowAnyAuthenticated jedem angemeldeten Benutzer, auch ohne PM-Gruppe', () => { expect(guard.canActivate(ctx(r.eigeneEinstellungen, nutzer()))).toBe(true); expect( guard.canActivate( ctx(r.eigeneEinstellungen, { method: 'GET', url: '/x' }), ), ).toBe(false); }); it('lässt @Public-Routen immer durch', () => { expect( guard.canActivate(ctx(r.oeffentlich, { method: 'GET', url: '/x' })), ).toBe(true); }); describe('API-Keys', () => { it('gelten nur auf Routen mit @AllowApiKey', () => { expect(guard.canActivate(ctx(r.agent, apiKey))).toBe(true); expect(guard.canActivate(ctx(r.nurApiKey, apiKey))).toBe(true); }); it('umgehen keine Rechteprüfung mehr auf anderen Routen', () => { expect(() => guard.canActivate(ctx(r.mail, apiKey))).toThrow( ForbiddenException, ); expect(() => guard.canActivate(ctx(r.eigeneEinstellungen, apiKey)), ).toThrow(ForbiddenException); expect(() => guard.canActivate(ctx(r.ohneAngabe, apiKey))).toThrow( ForbiddenException, ); }); it('Benutzer brauchen auf @AllowApiKey-Routen weiterhin die Berechtigung', () => { expect( guard.canActivate(ctx(r.agent, nutzer(Permission.VIEW_SCANNER))), ).toBe(false); expect( guard.canActivate(ctx(r.agent, nutzer(Permission.MANAGE_SETTINGS))), ).toBe(true); expect( guard.canActivate(ctx(r.nurApiKey, nutzer(Permission.MANAGE_ALL))), ).toBe(false); }); }); });