diff --git a/charts/dbildungs-iam-server/seeding/dev/01_organisation.json b/charts/dbildungs-iam-server/seeding/dev/01_organisation.json index c6bcac4b1..d594e850c 100644 --- a/charts/dbildungs-iam-server/seeding/dev/01_organisation.json +++ b/charts/dbildungs-iam-server/seeding/dev/01_organisation.json @@ -3,7 +3,7 @@ "entities": [ { "id": 0, - "name": "Wurzel Land Schleswig Holstein", + "name": "Land Schleswig-Holstein", "kuerzel": "Root", "typ": "ROOT", "administriertVon": null, @@ -11,7 +11,7 @@ }, { "id": 1, - "name": "Öffentliche Schulen Land Schleswig Holstein", + "name": "Öffentliche Schulen Land Schleswig-Holstein", "kuerzel": "Öffentl. Schulen", "typ": "LAND", "administriertVon": 0, @@ -19,7 +19,7 @@ }, { "id": 2, - "name": "Ersatzschulen Land Schleswig Holstein", + "name": "Ersatzschulen Land Schleswig-Holstein", "kuerzel": "Ersatzschulen", "typ": "LAND", "administriertVon": 0, diff --git a/seeding/dev/01/01_organisation.json b/seeding/dev/01/01_organisation.json index c6bcac4b1..d594e850c 100644 --- a/seeding/dev/01/01_organisation.json +++ b/seeding/dev/01/01_organisation.json @@ -3,7 +3,7 @@ "entities": [ { "id": 0, - "name": "Wurzel Land Schleswig Holstein", + "name": "Land Schleswig-Holstein", "kuerzel": "Root", "typ": "ROOT", "administriertVon": null, @@ -11,7 +11,7 @@ }, { "id": 1, - "name": "Öffentliche Schulen Land Schleswig Holstein", + "name": "Öffentliche Schulen Land Schleswig-Holstein", "kuerzel": "Öffentl. Schulen", "typ": "LAND", "administriertVon": 0, @@ -19,7 +19,7 @@ }, { "id": 2, - "name": "Ersatzschulen Land Schleswig Holstein", + "name": "Ersatzschulen Land Schleswig-Holstein", "kuerzel": "Ersatzschulen", "typ": "LAND", "administriertVon": 0, diff --git a/seeding/dev/01/04_rolle.json b/seeding/dev/01/04_rolle.json index eb1d89673..b7f45cc04 100644 --- a/seeding/dev/01/04_rolle.json +++ b/seeding/dev/01/04_rolle.json @@ -116,6 +116,15 @@ "MIGRATION_DURCHFUEHREN" ], "serviceProviderIds": [] + }, + { + "id": 6, + "administeredBySchulstrukturknoten": 0, + "name": "Schulbegleitung", + "rollenart": "LERN", + "merkmale": [], + "systemrechte": [], + "serviceProviderIds": [] } ] } diff --git a/seeding/seeding-integration-test/organisation/06_kuerzel-is-root.json b/seeding/seeding-integration-test/organisation/06_kuerzel-is-root.json index 6b4e62919..90f6ebc14 100644 --- a/seeding/seeding-integration-test/organisation/06_kuerzel-is-root.json +++ b/seeding/seeding-integration-test/organisation/06_kuerzel-is-root.json @@ -3,7 +3,7 @@ "entities": [ { "id": 0, - "name": "Wurzel Land Schleswig Holstein", + "name": "Land Schleswig-Holstein", "kuerzel": "Root", "typ": "ROOT", "administriertVon": null, @@ -11,5 +11,3 @@ } ] } - - diff --git a/src/modules/keycloak-administration/domain/keycloak-user.service.ts b/src/modules/keycloak-administration/domain/keycloak-user.service.ts index 3440666fe..16d58b50e 100644 --- a/src/modules/keycloak-administration/domain/keycloak-user.service.ts +++ b/src/modules/keycloak-administration/domain/keycloak-user.service.ts @@ -87,9 +87,10 @@ export class KeycloakUserService { } let algorithm: string; let hashIterations: number | undefined; + let passwordValue: string; if (hashedPassword.startsWith('{BCRYPT}')) { algorithm = 'bcrypt'; - const parts: string[] = hashedPassword.split('$'); + const parts: string[] = hashedPassword.split('$'); //Only Everything After and including the First $ if (parts.length < 4 || !parts[2]) { return { ok: false, @@ -97,6 +98,7 @@ export class KeycloakUserService { }; } hashIterations = parseInt(parts[2]); + passwordValue = hashedPassword.substring(hashedPassword.indexOf('$')); } else if (hashedPassword.startsWith('{crypt}')) { algorithm = 'crypt'; const parts: string[] = hashedPassword.split('$'); @@ -107,6 +109,7 @@ export class KeycloakUserService { }; } hashIterations = undefined; + passwordValue = hashedPassword.substring(hashedPassword.indexOf('$')); } else { return { ok: false, @@ -144,7 +147,7 @@ export class KeycloakUserService { algorithm: algorithm, }), secretData: JSON.stringify({ - value: hashedPassword, + value: passwordValue, }), type: 'password', }, diff --git a/src/modules/organisation/api/organisation.controller.spec.ts b/src/modules/organisation/api/organisation.controller.spec.ts index 9c5c88e9a..b6f50ad76 100644 --- a/src/modules/organisation/api/organisation.controller.spec.ts +++ b/src/modules/organisation/api/organisation.controller.spec.ts @@ -336,7 +336,7 @@ describe('OrganisationController', () => { faker.string.uuid(), faker.string.uuid(), faker.string.numeric(), - 'Öffentliche Schulen Land Schleswig Holstein', + 'Öffentliche Schulen Land Schleswig-Holstein', faker.lorem.word(), faker.string.uuid(), OrganisationsTyp.ROOT, @@ -349,7 +349,7 @@ describe('OrganisationController', () => { faker.string.uuid(), faker.string.uuid(), faker.string.numeric(), - 'Ersatzschulen Land Schleswig Holstein', + 'Ersatzschulen Land Schleswig-Holstein', faker.lorem.word(), faker.string.uuid(), OrganisationsTyp.SCHULE, diff --git a/src/modules/organisation/persistence/organisation.repository.integration-spec.ts b/src/modules/organisation/persistence/organisation.repository.integration-spec.ts index ab82a0708..fa0593d4f 100644 --- a/src/modules/organisation/persistence/organisation.repository.integration-spec.ts +++ b/src/modules/organisation/persistence/organisation.repository.integration-spec.ts @@ -430,7 +430,7 @@ describe('OrganisationRepository', () => { ROOT_ORGANISATION_ID, faker.string.uuid(), faker.string.numeric(), - 'Öffentliche Schulen Land Schleswig Holstein', + 'Öffentliche Schulen Land Schleswig-Holstein', faker.lorem.word(), faker.string.uuid(), OrganisationsTyp.ROOT, @@ -443,7 +443,7 @@ describe('OrganisationRepository', () => { ROOT_ORGANISATION_ID, faker.string.uuid(), faker.string.numeric(), - 'Ersatzschulen Land Schleswig Holstein', + 'Ersatzschulen Land Schleswig-Holstein', faker.lorem.word(), faker.string.uuid(), OrganisationsTyp.SCHULE, diff --git a/src/modules/person/person-api.module.ts b/src/modules/person/person-api.module.ts index 6efab4d8b..41d53efd9 100644 --- a/src/modules/person/person-api.module.ts +++ b/src/modules/person/person-api.module.ts @@ -6,13 +6,10 @@ import { PersonModule } from './person.module.js'; import { PersonFrontendController } from './api/person.frontend.controller.js'; import { PersonenkontextUc } from '../personenkontext/api/personenkontext.uc.js'; import { PersonenKontextModule } from '../personenkontext/personenkontext.module.js'; -import { UsernameGeneratorService } from './domain/username-generator.service.js'; -import { PersonRepository } from './persistence/person.repository.js'; import { RolleModule } from '../rolle/rolle.module.js'; import { OrganisationModule } from '../organisation/organisation.module.js'; import { KeycloakAdministrationModule } from '../keycloak-administration/keycloak-administration.module.js'; import { DBiamPersonenuebersichtController } from './api/personenuebersicht/dbiam-personenuebersicht.controller.js'; -import { DBiamPersonenkontextRepo } from '../personenkontext/persistence/dbiam-personenkontext.repo.js'; import { PersonInfoController } from './api/person-info.controller.js'; import { PersonApiMapper } from './mapper/person-api.mapper.js'; import { DBiamPersonController } from './api/dbiam-person.controller.js'; @@ -29,14 +26,7 @@ import { EventModule } from '../../core/eventbus/event.module.js'; LoggerModule.register(PersonApiModule.name), EventModule, ], - providers: [ - PersonApiMapperProfile, - PersonenkontextUc, - UsernameGeneratorService, - PersonRepository, - DBiamPersonenkontextRepo, - PersonApiMapper, - ], + providers: [PersonApiMapperProfile, PersonenkontextUc, PersonApiMapper], controllers: [ PersonController, PersonFrontendController, diff --git a/src/modules/rolle/api/rolle.controller.integration-spec.ts b/src/modules/rolle/api/rolle.controller.integration-spec.ts index 798d91288..6a641d91b 100644 --- a/src/modules/rolle/api/rolle.controller.integration-spec.ts +++ b/src/modules/rolle/api/rolle.controller.integration-spec.ts @@ -31,11 +31,10 @@ import { OrganisationRepository } from '../../organisation/persistence/organisat import { PagedResponse } from '../../../shared/paging/index.js'; import { ServiceProviderIdNameResponse } from './serviceprovider-id-name.response.js'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { PersonPermissionsRepo } from '../../authentication/domain/person-permission.repo.js'; -//import {PersonPermissions} from "../../authentication/domain/person-permissions.js"; import { Observable } from 'rxjs'; -import { Request } from 'express'; +import { PersonPermissionsRepo } from '../../authentication/domain/person-permission.repo.js'; import { PassportUser } from '../../authentication/types/user.js'; +import { Request } from 'express'; describe('Rolle API', () => { let app: INestApplication; @@ -87,7 +86,6 @@ describe('Rolle API', () => { rolleRepo = module.get(RolleRepo); serviceProviderRepo = module.get(ServiceProviderRepo); personpermissionsRepoMock = module.get(PersonPermissionsRepo); - await DatabaseTestModule.setupDatabase(module.get(MikroORM)); app = module.createNestApplication(); await app.init(); @@ -238,6 +236,18 @@ describe('Rolle API', () => { expect(pagedResponse.items).toHaveLength(3); }); + it('should return no rollen', async () => { + const response: Response = await request(app.getHttpServer() as App) + .get('/rolle') + .send(); + + expect(response.status).toBe(200); + expect(response.body).toBeInstanceOf(Object); + const pagedResponse: PagedResponse = + response.body as PagedResponse; + expect(pagedResponse.items).toHaveLength(0); + }); + it('should return rollen with the given queried name', async () => { const testRolle: { name: string } = await rolleRepo.save(DoFactory.createRolle(false)); diff --git a/src/modules/rolle/api/rolle.controller.spec.ts b/src/modules/rolle/api/rolle.controller.spec.ts index 851871b11..1a55a8441 100644 --- a/src/modules/rolle/api/rolle.controller.spec.ts +++ b/src/modules/rolle/api/rolle.controller.spec.ts @@ -14,6 +14,7 @@ import { FindRolleByIdParams } from './find-rolle-by-id.params.js'; import { OrganisationService } from '../../organisation/domain/organisation.service.js'; import { OrganisationRepository } from '../../organisation/persistence/organisation.repository.js'; import { RolleNameQueryParams } from './rolle-name-query.param.js'; +import { PersonPermissions } from '../../authentication/domain/person-permissions.js'; describe('Rolle API with mocked ServiceProviderRepo', () => { let rolleRepoMock: DeepMocked; @@ -84,11 +85,14 @@ describe('Rolle API with mocked ServiceProviderRepo', () => { const params: RolleNameQueryParams = { searchStr: faker.string.alpha(), }; + const permissions: DeepMocked = createMock(); + permissions.getOrgIdsWithSystemrecht.mockResolvedValueOnce([]); + //mock getRollenByName rolleRepoMock.findByName.mockResolvedValueOnce(undefined); //mock call to get sp (direct in controller-method) serviceProviderRepoMock.findById.mockResolvedValueOnce(undefined); - await expect(rolleController.findRollen(params)).resolves.not.toThrow(Error); + await expect(rolleController.findRollen(params, permissions)).resolves.not.toThrow(Error); }); }); }); diff --git a/src/modules/rolle/api/rolle.controller.ts b/src/modules/rolle/api/rolle.controller.ts index 68f191f31..2dc37aed6 100644 --- a/src/modules/rolle/api/rolle.controller.ts +++ b/src/modules/rolle/api/rolle.controller.ts @@ -77,18 +77,16 @@ export class RolleController { @ApiInternalServerErrorResponse({ description: 'Internal server error while getting all rollen.' }) public async findRollen( @Query() queryParams: RolleNameQueryParams, + @Permissions() permissions: PersonPermissions, ): Promise> { - let rollen: Option[]>; - - if (queryParams.searchStr) { - rollen = await this.rolleRepo.findByName(queryParams.searchStr, queryParams.limit, queryParams.offset); - } else { - rollen = await this.rolleRepo.find(queryParams.limit, queryParams.offset); - } - - const serviceProviders: ServiceProvider[] = await this.serviceProviderRepo.find(); + const rollen: Option[]> = await this.rolleRepo.findRollenAuthorized( + permissions, + queryParams.searchStr, + queryParams.limit, + queryParams.offset, + ); - if (!rollen) { + if (!rollen || rollen.length === 0) { const pagedRolleWithServiceProvidersResponse: Paged = { total: 0, offset: 0, @@ -97,6 +95,7 @@ export class RolleController { }; return new PagedResponse(pagedRolleWithServiceProvidersResponse); } + const serviceProviders: ServiceProvider[] = await this.serviceProviderRepo.find(); const rollenWithServiceProvidersResponses: RolleWithServiceProvidersResponse[] = rollen.map( (r: Rolle) => { diff --git a/src/modules/rolle/repo/rolle.repo.spec.ts b/src/modules/rolle/repo/rolle.repo.spec.ts index ec369d27c..1f6d442f7 100644 --- a/src/modules/rolle/repo/rolle.repo.spec.ts +++ b/src/modules/rolle/repo/rolle.repo.spec.ts @@ -121,7 +121,74 @@ describe('RolleRepo', () => { expect(rolle).toBeNull(); }); }); + describe('findRollenAuthorized', () => { + it('should return no rollen because there are none', async () => { + const organisationId: OrganisationID = faker.string.uuid(); + + const permissions: DeepMocked = createMock(); + permissions.getOrgIdsWithSystemrecht.mockResolvedValueOnce([organisationId]); + + const rolleResult: Option[]> = await sut.findRollenAuthorized(permissions, undefined, 10, 0); + + expect(rolleResult?.length).toBe(0); + }); + + it('should return the rollen when permissions are sufficient', async () => { + const organisationId: OrganisationID = faker.string.uuid(); + await sut.save(DoFactory.createRolle(false, { administeredBySchulstrukturknoten: organisationId })); + + const permissions: DeepMocked = createMock(); + permissions.getOrgIdsWithSystemrecht.mockResolvedValueOnce([organisationId]); + + const rolleResult: Option[]> = await sut.findRollenAuthorized(permissions, undefined, 10, 0); + + expect(rolleResult?.length).toBe(1); + }); + + it('should return empty array when permissions are insufficient', async () => { + const organisationId: OrganisationID = faker.string.uuid(); + await sut.save(DoFactory.createRolle(false, { administeredBySchulstrukturknoten: organisationId })); + + const permissions: DeepMocked = createMock(); + permissions.getOrgIdsWithSystemrecht.mockResolvedValueOnce([]); + + const rolleResult: Option[]> = await sut.findRollenAuthorized(permissions, undefined, 10, 0); + expect(rolleResult?.length).toBe(0); + }); + + it('should filter rollen based on search string and permissions', async () => { + const organisationId: OrganisationID = faker.string.uuid(); + await sut.save( + DoFactory.createRolle(false, { administeredBySchulstrukturknoten: organisationId, name: 'Test' }), + ); + await sut.save( + DoFactory.createRolle(false, { + administeredBySchulstrukturknoten: organisationId, + name: 'AnotherName', + }), + ); + + const permissions: DeepMocked = createMock(); + permissions.getOrgIdsWithSystemrecht.mockResolvedValueOnce([organisationId]); + + const rolleResult: Option[]> = await sut.findRollenAuthorized(permissions, 'Test', 10, 0); + + expect(rolleResult?.length).toBe(1); + }); + + it('should return all rollen when no search string is provided and permissions are sufficient', async () => { + const organisationId: OrganisationID = faker.string.uuid(); + await sut.save(DoFactory.createRolle(false, { administeredBySchulstrukturknoten: organisationId })); + + const permissions: DeepMocked = createMock(); + permissions.getOrgIdsWithSystemrecht.mockResolvedValueOnce([organisationId]); + + const rolleResult: Option[]> = await sut.findRollenAuthorized(permissions, undefined, 10, 0); + + expect(rolleResult?.length).toBe(1); + }); + }); describe('findByIdAuthorized', () => { it('should return the rolle', async () => { const organisationId: OrganisationID = faker.string.uuid(); diff --git a/src/modules/rolle/repo/rolle.repo.ts b/src/modules/rolle/repo/rolle.repo.ts index 78b00546f..6f20e1476 100644 --- a/src/modules/rolle/repo/rolle.repo.ts +++ b/src/modules/rolle/repo/rolle.repo.ts @@ -162,6 +162,42 @@ export class RolleRepo { return rollen.map((rolle: RolleEntity) => mapEntityToAggregate(rolle, this.rolleFactory)); } + public async findRollenAuthorized( + permissions: PersonPermissions, + searchStr?: string, + limit?: number, + offset?: number, + ): Promise[]>> { + let rollen: Option; + if (searchStr) { + rollen = await this.em.find( + this.entityName, + { name: { $ilike: '%' + searchStr + '%' } }, + { populate: ['merkmale', 'systemrechte', 'serviceProvider'] as const, limit: limit, offset: offset }, + ); + } else { + rollen = await this.em.findAll(this.entityName, { + populate: ['merkmale', 'systemrechte', 'serviceProvider'] as const, + limit: limit, + offset: offset, + }); + } + if (rollen.length === 0) { + return []; + } + + const orgIdsWithRecht: OrganisationID[] = await permissions.getOrgIdsWithSystemrecht( + [RollenSystemRecht.ROLLEN_VERWALTEN], + true, + ); + + const filteredRollen: RolleEntity[] = rollen.filter((rolle: RolleEntity) => + orgIdsWithRecht.includes(rolle.administeredBySchulstrukturknoten), + ); + + return filteredRollen.map((rolle: RolleEntity) => mapEntityToAggregate(rolle, this.rolleFactory)); + } + public async exists(id: RolleID): Promise { const rolle: Option> = await this.em.findOne( RolleEntity, diff --git a/src/modules/rolle/rolle.module.ts b/src/modules/rolle/rolle.module.ts index 0f466d70d..d5813194f 100644 --- a/src/modules/rolle/rolle.module.ts +++ b/src/modules/rolle/rolle.module.ts @@ -2,13 +2,12 @@ import { Module } from '@nestjs/common'; import { LoggerModule } from '../../core/logging/logger.module.js'; import { RolleRepo } from './repo/rolle.repo.js'; import { ServiceProviderModule } from '../service-provider/service-provider.module.js'; -import { ServiceProviderRepo } from '../service-provider/repo/service-provider.repo.js'; import { RolleFactory } from './domain/rolle.factory.js'; import { OrganisationModule } from '../organisation/organisation.module.js'; @Module({ imports: [ServiceProviderModule, LoggerModule.register(RolleModule.name), OrganisationModule], - providers: [RolleRepo, RolleFactory, ServiceProviderRepo], + providers: [RolleRepo, RolleFactory], exports: [RolleRepo, RolleFactory], }) export class RolleModule {}