feat(quick-260909-ipc): ldap-config.service.ts an forTenant() binden, Loesch-Fremdzugriff schliessen
Aufgabe 2 der Etappe 2: getConfig/createConfig/updateConfig sowie addFieldMapping/removeFieldMapping laufen jetzt ueber forTenant(), gebunden an den aus der Anfrage bekannten Mandanten. removeFieldMapping nimmt den Mandanten neu als Pflichtparameter entgegen und der Controller holt ihn aus dem Sitzungsnachweis statt nur die URL-Kennung weiterzureichen (T-IPC-01) -- ein Administrator konnte bisher die Feldzuordnung eines fremden Mandanten loeschen, wenn er ihre Kennung kannte. getAllActiveConfigs() und die Start-Nachverschluesselung bleiben bewusst uebergreifend, mit ausgeschriebener Begruendung im Code (Befund B). rls-access-inventory.spec.ts erkennt jetzt neben `this.prisma.<Modell>` auch gebundene `<Name>.<Modell>`-Zugriffe (Befund F/G) und prueft eine neue Stand-Spalte (gebunden/ungebunden/gemischt) im Klassifikationsdokument gegen den Quelltext. Das macht zwei bisher unsichtbare, weil schon laenger gebundene Fundstellen sichtbar (auth.service.ts/passwordResetToken, ldap.service.ts/groupMembership) und deckt auf, dass (ldap-config.service.ts, ldapConfig) tatsaechlich "beides" ist, nicht "muss-mandantengebunden" (Befund B). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
This commit is contained in:
@@ -1,6 +1,17 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { LdapConfigService } from './ldap-config.service';
|
||||
|
||||
// forTenant gibt in den Bestandstests denselben Client zurueck (tenant
|
||||
// scoping ist dort nicht unter Test) — ohne diesen Mock bricht die Datei am
|
||||
// blanken Prisma-Ersatz beim ersten `$extends`-Aufruf (Befund F,
|
||||
// 260909-ipc-PLAN.md). Der eigene Bindungs-Testblock unten biegt die
|
||||
// Implementierung auf ein zweites, unterscheidbares Client-Objekt um.
|
||||
vi.mock('../prisma/prisma-tenant.extension', () => ({
|
||||
forTenant: vi.fn((p: unknown) => p),
|
||||
}));
|
||||
|
||||
import { forTenant } from '../prisma/prisma-tenant.extension';
|
||||
|
||||
/**
|
||||
* Das Bind-Passwort ist das einzige Zugangsdatum, das nicht gehasht werden
|
||||
* kann — Tessera muss sich damit am Domain Controller anmelden, braucht es
|
||||
@@ -176,3 +187,125 @@ describe('LdapConfigService — Bind-Passwort verschluesselt at rest', () => {
|
||||
await expect(service.onApplicationBootstrap()).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Aufgabe 2 (260909-ipc, WINDOWS #20 Etappe 2, T-IPC-01/T-IPC-02): belegt,
|
||||
* dass die drei pro-Mandant-Methoden gebunden laufen, die beiden
|
||||
* uebergreifenden Methoden es NICHT tun, und dass das Loeschen einer
|
||||
* Feldzuordnung ohne Mandant nicht mehr moeglich ist.
|
||||
*/
|
||||
describe('LdapConfigService — Bindung an forTenant() (260909-ipc)', () => {
|
||||
let prisma: any;
|
||||
let service: LdapConfigService;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
prisma = {
|
||||
ldapConfig: {
|
||||
findUnique: vi.fn().mockResolvedValue(CONFIG_ROW),
|
||||
findMany: vi.fn().mockResolvedValue([]),
|
||||
create: vi.fn((args: any) => Promise.resolve({ ...CONFIG_ROW, ...args.data })),
|
||||
update: vi.fn((args: any) => Promise.resolve({ ...CONFIG_ROW, ...args.data })),
|
||||
},
|
||||
ldapFieldMapping: {
|
||||
create: vi.fn((args: any) => Promise.resolve({ id: 'map1', isDefault: false, ...args.data })),
|
||||
findUnique: vi.fn(),
|
||||
delete: vi.fn((args: any) => Promise.resolve({ id: args.where.id })),
|
||||
},
|
||||
};
|
||||
service = new LdapConfigService(prisma, crypto as any);
|
||||
});
|
||||
|
||||
it('getConfig() bindet ueber forTenant() an den uebergebenen Mandanten', async () => {
|
||||
await service.getConfig('t1');
|
||||
expect(forTenant).toHaveBeenCalledWith(prisma, 't1');
|
||||
expect(prisma.ldapConfig.findUnique).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ where: { tenantId: 't1' } }),
|
||||
);
|
||||
});
|
||||
|
||||
it('createConfig() bindet ueber forTenant() an den uebergebenen Mandanten', async () => {
|
||||
await service.createConfig('t1', {
|
||||
serverUrl: 'ldap://example',
|
||||
baseDn: 'dc=example,dc=com',
|
||||
} as any);
|
||||
expect(forTenant).toHaveBeenCalledWith(prisma, 't1');
|
||||
expect(prisma.ldapConfig.create).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('updateConfig() bindet ueber forTenant() an den uebergebenen Mandanten', async () => {
|
||||
await service.updateConfig('t1', { serverUrl: 'ldap://anders' } as any);
|
||||
expect(forTenant).toHaveBeenCalledWith(prisma, 't1');
|
||||
expect(prisma.ldapConfig.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ where: { tenantId: 't1' } }),
|
||||
);
|
||||
});
|
||||
|
||||
it('addFieldMapping() nimmt den Mandanten entgegen und schreibt gebunden', async () => {
|
||||
await service.addFieldMapping('t1', 'cfg1', {
|
||||
ldapField: 'department',
|
||||
tesseraField: 'department',
|
||||
} as any);
|
||||
expect(forTenant).toHaveBeenCalledWith(prisma, 't1');
|
||||
expect(prisma.ldapFieldMapping.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({ ldapConfigId: 'cfg1' }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('removeFieldMapping() nimmt den Mandanten entgegen, liest gebunden und loescht gebunden', async () => {
|
||||
prisma.ldapFieldMapping.findUnique.mockResolvedValue({
|
||||
id: 'map1',
|
||||
ldapConfigId: 'cfg1',
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
const result = await service.removeFieldMapping('t1', 'map1');
|
||||
|
||||
expect(forTenant).toHaveBeenCalledWith(prisma, 't1');
|
||||
expect(prisma.ldapFieldMapping.findUnique).toHaveBeenCalledWith({
|
||||
where: { id: 'map1' },
|
||||
});
|
||||
expect(prisma.ldapFieldMapping.delete).toHaveBeenCalledWith({
|
||||
where: { id: 'map1' },
|
||||
});
|
||||
expect(result).toEqual({ id: 'map1' });
|
||||
});
|
||||
|
||||
it('removeFieldMapping() liefert null, wenn die Zuordnung unter diesem Mandanten nicht sichtbar ist (T-IPC-01)', async () => {
|
||||
// Simuliert die RLS-Wirkung: unter dem Mandantenkontext von t1 ist eine
|
||||
// fremde Feldzuordnung (Mandant t2) unsichtbar — findUnique liefert null,
|
||||
// genau wie es die echte Policy nach dem Scharfschalten taete.
|
||||
prisma.ldapFieldMapping.findUnique.mockResolvedValue(null);
|
||||
|
||||
const result = await service.removeFieldMapping('t1', 'map-fremd');
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(prisma.ldapFieldMapping.delete).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('removeFieldMapping() schuetzt Vorgabe-Zuordnungen weiterhin, auch gebunden', async () => {
|
||||
prisma.ldapFieldMapping.findUnique.mockResolvedValue({
|
||||
id: 'map1',
|
||||
ldapConfigId: 'cfg1',
|
||||
isDefault: true,
|
||||
});
|
||||
|
||||
await expect(service.removeFieldMapping('t1', 'map1')).rejects.toThrow(
|
||||
'Cannot delete default field mappings',
|
||||
);
|
||||
expect(prisma.ldapFieldMapping.delete).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('getAllActiveConfigs() bleibt bewusst uebergreifend — kein Mandantenkontext', async () => {
|
||||
await service.getAllActiveConfigs();
|
||||
expect(forTenant).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onApplicationBootstrap() bleibt bewusst uebergreifend — kein Mandantenkontext', async () => {
|
||||
prisma.ldapConfig.findMany.mockResolvedValue([]);
|
||||
await service.onApplicationBootstrap();
|
||||
expect(forTenant).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user