From 79bb37a0c049ab8b4698b349d6838e8030485c23 Mon Sep 17 00:00:00 2001 From: Cornelius <144817755+DPDS93CT@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:08:00 +0200 Subject: [PATCH] SPSH-837: LDAP im AutoDeployment befuellen (#575) * adjust configuration for LDAP to accept environmental variables for cluster use * fix lint * fixing * deleted ldap-deployment * adjust values.yaml * adjust seeding to create LDAP entries * add ldap-admin-password to secret.yaml * ldap-address now depending on Release-namespace * fix lint * fix ldapClient issue when seeding * disconnect from LDAP on moduleDestroy * rm commented code --------- Co-authored-by: aimee-889 <93951322+aimee-889@users.noreply.github.com> --- ...publish-check-deploy-on-push-scheduled.yml | 12 ++-- .../dbildungs-iam-server/config/config.json | 4 +- .../templates/_dbildungs-iam-server-envs.tpl | 7 ++- .../templates/configmap.yaml | 4 +- .../templates/ldap-configmap.yaml | 13 ---- .../templates/ldap-deployment.yaml | 63 ------------------- .../templates/ldap-ldif-configmap.yaml | 11 ---- .../templates/ldap-secret.yaml | 9 --- .../templates/ldap-service.yaml | 21 ------- .../templates/secret.yaml | 1 + charts/dbildungs-iam-server/values.yaml | 27 +++----- config/config.json | 2 +- src/console/console.module.ts | 2 + ...le-mocked-db-seed-repo.integration-spec.ts | 5 ++ src/console/dbseed/db-seed.module.ts | 2 + .../dbseed/domain/db-seed.service.spec.ts | 43 ++++++------- src/console/dbseed/domain/db-seed.service.ts | 40 ++++++------ src/core/ldap/domain/ldap-client.service.ts | 4 +- src/core/ldap/domain/ldap-client.spec.ts | 18 +++++- src/core/ldap/domain/ldap-client.ts | 18 +++++- .../ldap/domain/ldap-event-handler.spec.ts | 32 +++++----- src/core/ldap/domain/ldap-event-handler.ts | 8 +-- src/core/ldap/ldap-instance-config.ts | 4 +- src/core/ldap/ldap.module.ts | 2 +- ...rganisation.repository.integration-spec.ts | 11 +++- .../persistence/organisation.repository.ts | 8 +++ src/shared/config/config.env.ts | 7 +++ src/shared/config/config.loader.spec.ts | 10 +++ src/shared/config/ldap.config.ts | 2 +- test/utils/ldap-test.module.ts | 2 +- 30 files changed, 171 insertions(+), 221 deletions(-) delete mode 100644 charts/dbildungs-iam-server/templates/ldap-configmap.yaml delete mode 100644 charts/dbildungs-iam-server/templates/ldap-deployment.yaml delete mode 100644 charts/dbildungs-iam-server/templates/ldap-ldif-configmap.yaml delete mode 100644 charts/dbildungs-iam-server/templates/ldap-secret.yaml delete mode 100644 charts/dbildungs-iam-server/templates/ldap-service.yaml diff --git a/.github/workflows/image-and-helm-publish-check-deploy-on-push-scheduled.yml b/.github/workflows/image-and-helm-publish-check-deploy-on-push-scheduled.yml index 56e4fe9e1..29b784d93 100644 --- a/.github/workflows/image-and-helm-publish-check-deploy-on-push-scheduled.yml +++ b/.github/workflows/image-and-helm-publish-check-deploy-on-push-scheduled.yml @@ -65,7 +65,7 @@ jobs: container_registry: "ghcr.io" fail_on_vulnerabilites: false report_location: "Dockerfile" - + scheduled_trivy_scan: name: "Scheduled trivy scan of latest image" if: ${{ github.event_name == 'schedule' }} @@ -86,10 +86,10 @@ jobs: select_helm_version_generation_and_image_tag_generation: if: ${{ github.event_name == 'push' && !startsWith(github.ref_name,'dependabot/') }} - needs: + needs: - scan_helm runs-on: ubuntu-latest - outputs: + outputs: SELECT_HELM_VERION_GENERATION: ${{ steps.select_generation.outputs.SELECT_HELM_VERION_GENERATION }} SELECT_IMAGE_TAG_GENERATION: ${{ steps.select_generation.outputs.SELECT_IMAGE_TAG_GENERATION }} steps: @@ -105,7 +105,7 @@ jobs: fi release_helm: if: ${{ github.event_name == 'push' && !startsWith(github.ref_name,'dependabot/') }} - needs: + needs: - select_helm_version_generation_and_image_tag_generation uses: dBildungsplattform/dbp-github-workflows/.github/workflows/chart-release.yaml@5 secrets: inherit @@ -120,7 +120,7 @@ jobs: create_branch_identifier: if: ${{ github.event_name == 'push' && !startsWith(github.ref_name,'dependabot/') }} - needs: + needs: - branch_meta uses: dBildungsplattform/spsh-app-deploy/.github/workflows/deploy-branch-to-namespace.yml@3 with: @@ -165,4 +165,4 @@ jobs: - create_branch_identifier_for_deletion runs-on: ubuntu-latest steps: - - run: echo "Deletion workflow of namespace" ${{ needs.create_branch_identifier_for_deletion.outputs.namespace_from_branch }} "done" \ No newline at end of file + - run: echo "Deletion workflow of namespace" ${{ needs.create_branch_identifier_for_deletion.outputs.namespace_from_branch }} "done" diff --git a/charts/dbildungs-iam-server/config/config.json b/charts/dbildungs-iam-server/config/config.json index cca5de99e..899e19b98 100644 --- a/charts/dbildungs-iam-server/config/config.json +++ b/charts/dbildungs-iam-server/config/config.json @@ -28,9 +28,9 @@ "USE_TLS": false }, "LDAP": { - "URL": "ldap://dbildungs-iam-server-ldap", + "URL": "ldap://spsh-xxx.svc.cluster.local", "BIND_DN": "cn=admin,dc=schule-sh,dc=de", - "PASSWORD": "admin" + "ADMIN_PASSWORD": "password" }, "DATA": { "ROOT_ORGANISATION_ID": "d39cb7cf-2f9b-45f1-849f-973661f2f057" diff --git a/charts/dbildungs-iam-server/templates/_dbildungs-iam-server-envs.tpl b/charts/dbildungs-iam-server/templates/_dbildungs-iam-server-envs.tpl index b1515f78e..d6abead63 100644 --- a/charts/dbildungs-iam-server/templates/_dbildungs-iam-server-envs.tpl +++ b/charts/dbildungs-iam-server/templates/_dbildungs-iam-server-envs.tpl @@ -41,4 +41,9 @@ secretKeyRef: name: {{ default .Values.auth.existingSecret .Values.auth.secretName }} key: itslearning-password -{{- end}} \ No newline at end of file + - name: LDAP_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ default .Values.auth.existingSecret .Values.auth.secretName }} + key: ldap-admin-password +{{- end}} diff --git a/charts/dbildungs-iam-server/templates/configmap.yaml b/charts/dbildungs-iam-server/templates/configmap.yaml index 1957cc962..83b9bc354 100644 --- a/charts/dbildungs-iam-server/templates/configmap.yaml +++ b/charts/dbildungs-iam-server/templates/configmap.yaml @@ -15,4 +15,6 @@ data: FRONTEND_OIDC_CALLBACK_URL: "https://{{ .Values.backendHostname }}/api/auth/login" FRONTEND_DEFAULT_LOGIN_REDIRECT: "https://{{ .Values.backendHostname }}/" FRONTEND_LOGOUT_REDIRECT: "https://{{ .Values.backendHostname }}/" - BACKEND_HOSTNAME: "{{.Values.backendHostname}}" + BACKEND_HOSTNAME: "{{ .Values.backendHostname }}" + LDAP_URL: '{{ .Values.ldap.url | replace "spsh-xxx" .Release.Namespace }}' + LDAP_BIND_DN: "{{ .Values.ldap.bindDN }}" diff --git a/charts/dbildungs-iam-server/templates/ldap-configmap.yaml b/charts/dbildungs-iam-server/templates/ldap-configmap.yaml deleted file mode 100644 index 365a1bad8..000000000 --- a/charts/dbildungs-iam-server/templates/ldap-configmap.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: "{{ template "common.names.name" . }}-ldap" - namespace: {{ template "common.names.namespace" . }} - labels: - {{- include "common.labels" . | nindent 4 }} -data: - LDAP_ORGANISATION: "spsh-de" - LDAP_DOMAIN: "schule-sh.de" - LDAP_RFC2307BIS_SCHEMA: "true" - LDAP_REMOVE_CONFIG_AFTER_SETUP: "true" - LDAP_TLS_VERIFY_CLIENT: "never" diff --git a/charts/dbildungs-iam-server/templates/ldap-deployment.yaml b/charts/dbildungs-iam-server/templates/ldap-deployment.yaml deleted file mode 100644 index b1dbbf855..000000000 --- a/charts/dbildungs-iam-server/templates/ldap-deployment.yaml +++ /dev/null @@ -1,63 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "common.names.name" . }}-ldap - namespace: {{ template "common.names.namespace" . }} - labels: - {{- include "common.labels" . | nindent 4 }} - app.kubernetes.io/component: server-ldap -spec: - selector: - matchLabels: - app.kubernetes.io/name: {{ template "common.names.name" . }} - app.kubernetes.io/component: server-ldap - replicas: {{ .Values.replicaCount }} - template: - metadata: - labels: - {{- include "common.labels" . | nindent 8 }} - app.kubernetes.io/component: server-ldap - spec: - containers: - - name: openldap - image: "{{ .Values.ldap.image.repository }}:{{ .Values.ldap.image.tag }}" - imagePullPolicy: {{ .Values.ldap.image.pullPolicy }} - securityContext: - privileged: false - allowPrivilegeEscalation: false - capabilities: - drop: [ "NET_RAW" ] - args: [ "--copy-service", "--loglevel", "debug" ] - resources: {{- toYaml .Values.ldap.resources | nindent 12 }} - volumeMounts: - - name: ldif-volume - mountPath: /container/service/slapd/assets/config/bootstrap/ldif/custom - - name: ldap-certs - mountPath: /container/service/slapd/assets/certs - ports: - - name: openldap - containerPort: {{ .Values.ldap.service.ports.ldap }} - protocol: TCP - env: - - name: LDAP_ADMIN_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "common.names.name" . }}-ldap - key: admin-password - - name: LDAP_CONFIG_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "common.names.name" . }}-ldap - key: config-password - envFrom: - - configMapRef: - name: {{ template "common.names.name" . }}-ldap - automountServiceAccountToken: false - volumes: - - name: ldif-volume - configMap: - name: {{ template "common.names.name" . }}-ldif - - name: ldap-certs - hostPath: - path: "/data/ldap/certs" - restartPolicy: {{ .Values.restartPolicy }} diff --git a/charts/dbildungs-iam-server/templates/ldap-ldif-configmap.yaml b/charts/dbildungs-iam-server/templates/ldap-ldif-configmap.yaml deleted file mode 100644 index 2c3e47af9..000000000 --- a/charts/dbildungs-iam-server/templates/ldap-ldif-configmap.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "common.names.name" . }}-ldif - namespace: {{ template "common.names.namespace" . }} - labels: - {{- include "common.labels" . | nindent 4 }} -data: - spsh-ldif: |- - {{ .Files.Get "config/ldif/spsh.ldif" | nindent 4 }} - diff --git a/charts/dbildungs-iam-server/templates/ldap-secret.yaml b/charts/dbildungs-iam-server/templates/ldap-secret.yaml deleted file mode 100644 index 8de99b2d8..000000000 --- a/charts/dbildungs-iam-server/templates/ldap-secret.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Values.auth.secretName }}-ldap - namespace: {{ template "common.names.namespace" . }} -type: Opaque -stringData: - admin-password: {{ .Values.ldap.adminPassword }} - config-password: {{ .Values.ldap.configPassword }} diff --git a/charts/dbildungs-iam-server/templates/ldap-service.yaml b/charts/dbildungs-iam-server/templates/ldap-service.yaml deleted file mode 100644 index c4c9fe74c..000000000 --- a/charts/dbildungs-iam-server/templates/ldap-service.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - app: ldap - name: ldap-service -metadata: - name: {{ template "common.names.name" . }}-ldap - namespace: {{ template "common.names.namespace" . }} - labels: - {{- include "common.labels" . | nindent 4 }} - app.kubernetes.io/component: server-ldap -spec: - selector: - {{- include "common.labels" . | nindent 4 }} - app.kubernetes.io/component: server-ldap - type: {{ .Values.ldap.service.type }} - ports: - - name: ldap - port: {{ .Values.ldap.service.ports.ldap }} - protocol: TCP diff --git a/charts/dbildungs-iam-server/templates/secret.yaml b/charts/dbildungs-iam-server/templates/secret.yaml index 680d14478..d6b0a5e4f 100644 --- a/charts/dbildungs-iam-server/templates/secret.yaml +++ b/charts/dbildungs-iam-server/templates/secret.yaml @@ -12,6 +12,7 @@ data: db-username: {{ .Values.database.username }} keycloak-adminSecret: {{ .Values.auth.keycloak_adminSecret }} keycloak-clientSecret: {{ .Values.auth.keycloak_clientSecret }} + ldap-admin-password: {{ .Values.auth.ldap_admin_password }} itslearning-endpoint: {{ .Values.auth.itslearning_endpoint }} itslearning-username: {{ .Values.auth.itslearning_username }} itslearning-password: {{ .Values.auth.itslearning_password }} diff --git a/charts/dbildungs-iam-server/values.yaml b/charts/dbildungs-iam-server/values.yaml index 23c57ebda..9945b0cef 100644 --- a/charts/dbildungs-iam-server/values.yaml +++ b/charts/dbildungs-iam-server/values.yaml @@ -26,12 +26,19 @@ database: password: "" username: "dbildungs_iam_server" + +ldap: + url: ldap://dbildungs-iam-ldap.spsh-xxx.svc.cluster.local + bindDN: cn=admin,dc=schule-sh,dc=de + + auth: # existingSecret: Refers to a secret already present in the cluster, which is required. existingSecret: "" secretName: dbildungs-iam-server keycloak_adminSecret: "" keycloak_clientSecret: "" + ldap_admin_password: "" secrets_json: "" frontend_sessionSecret: "" itslearning_endpoint: "" @@ -155,23 +162,3 @@ redis: extraEnvVars: [] extraVolumes: [] extraVolumeMounts: [] - -ldap: - image: - repository: docker.io/osixia/openldap - tag: "1.5.0" - pullPolicy: IfNotPresent - resources: - limits: - cpu: 2 - memory: 4G - ephemeral-storage: 2Gi - requests: - cpu: 200m - memory: 200Mi - adminPassword: admin - configPassword: config - service: - type: ClusterIP - ports: - ldap: 389 diff --git a/config/config.json b/config/config.json index 9ca68e697..d5a9447cb 100644 --- a/config/config.json +++ b/config/config.json @@ -38,7 +38,7 @@ "LDAP": { "URL": "ldap://localhost", "BIND_DN": "cn=admin,dc=schule-sh,dc=de", - "PASSWORD": "admin" + "ADMIN_PASSWORD": "admin" }, "DATA": { "ROOT_ORGANISATION_ID": "d39cb7cf-2f9b-45f1-849f-973661f2f057" diff --git a/src/console/console.module.ts b/src/console/console.module.ts index 5a796f3a3..85472f7b7 100644 --- a/src/console/console.module.ts +++ b/src/console/console.module.ts @@ -24,6 +24,7 @@ import { Migrator, TSMigrationGenerator } from '@mikro-orm/migrations'; import { DbInitMigrationConsole } from './dbmigrate/db-init-migration.console.js'; import { DbCreateMigrationConsole } from './dbmigrate/db-create-migration.console.js'; import { DbApplyMigrationConsole } from './dbmigrate/db-apply-migration.console.js'; +import { LdapModule } from '../core/ldap/ldap.module.js'; @Module({ imports: [ @@ -34,6 +35,7 @@ import { DbApplyMigrationConsole } from './dbmigrate/db-apply-migration.console. RolleModule, ServiceProviderModule, PersonenKontextModule, + LdapModule, DbSeedModule, LoggerModule.register(ConsoleModule.name), ConfigModule.forRoot({ diff --git a/src/console/dbseed/db-seed.console-mocked-db-seed-repo.integration-spec.ts b/src/console/dbseed/db-seed.console-mocked-db-seed-repo.integration-spec.ts index aa0a48004..1d31758f2 100644 --- a/src/console/dbseed/db-seed.console-mocked-db-seed-repo.integration-spec.ts +++ b/src/console/dbseed/db-seed.console-mocked-db-seed-repo.integration-spec.ts @@ -24,6 +24,7 @@ import { DbSeedStatus } from './repo/db-seed.entity.js'; import { DBiamPersonenkontextService } from '../../modules/personenkontext/domain/dbiam-personenkontext.service.js'; import { DbSeedReferenceRepo } from './repo/db-seed-reference.repo.js'; import { PersonenKontextModule } from '../../modules/personenkontext/personenkontext.module.js'; +import { LdapClient } from '../../core/ldap/domain/ldap-client.js'; describe('DbSeedConsoleMockedDbSeedRepo', () => { let module: TestingModule; @@ -57,6 +58,10 @@ describe('DbSeedConsoleMockedDbSeedRepo', () => { provide: DbSeedRepo, useValue: createMock(), }, + { + provide: LdapClient, + useValue: createMock(), + }, ], }) .overrideModule(KeycloakConfigModule) diff --git a/src/console/dbseed/db-seed.module.ts b/src/console/dbseed/db-seed.module.ts index 850c8f88a..ddb59a29b 100644 --- a/src/console/dbseed/db-seed.module.ts +++ b/src/console/dbseed/db-seed.module.ts @@ -13,9 +13,11 @@ import { KeycloakAdministrationModule } from '../../modules/keycloak-administrat import { DbSeedRepo } from './repo/db-seed.repo.js'; import { DBiamPersonenkontextRepo } from '../../modules/personenkontext/persistence/dbiam-personenkontext.repo.js'; import { DbSeedReferenceRepo } from './repo/db-seed-reference.repo.js'; +import { LdapModule } from '../../core/ldap/ldap.module.js'; @Module({ imports: [ + LdapModule, PersonModule, PersonenKontextModule, OrganisationModule, diff --git a/src/console/dbseed/domain/db-seed.service.spec.ts b/src/console/dbseed/domain/db-seed.service.spec.ts index 5b5c211fe..12a942745 100644 --- a/src/console/dbseed/domain/db-seed.service.spec.ts +++ b/src/console/dbseed/domain/db-seed.service.spec.ts @@ -12,7 +12,6 @@ import { DataProviderFile } from '../file/data-provider-file.js'; import { PersonFactory } from '../../../modules/person/domain/person.factory.js'; import { PersonRepository } from '../../../modules/person/persistence/person.repository.js'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { OrganisationRepo } from '../../../modules/organisation/persistence/organisation.repo.js'; import { OrganisationDo } from '../../../modules/organisation/domain/organisation.do.js'; import { EntityNotFoundError } from '../../../shared/error/index.js'; import { faker } from '@faker-js/faker'; @@ -35,7 +34,7 @@ import { KeycloakGroupRoleService } from '../../../modules/keycloak-administrati describe('DbSeedService', () => { let module: TestingModule; let dbSeedService: DbSeedService; - let organisationRepoMock: DeepMocked; + let organisationRepositoryMock: DeepMocked; let rolleRepoMock: DeepMocked; let personRepoMock: DeepMocked; let serviceProviderRepoMock: DeepMocked; @@ -76,10 +75,6 @@ describe('DbSeedService', () => { provide: DBiamPersonenkontextRepo, useValue: createMock(), }, - { - provide: OrganisationRepo, - useValue: createMock(), - }, { provide: OrganisationRepository, useValue: createMock(), @@ -103,7 +98,7 @@ describe('DbSeedService', () => { ], }).compile(); dbSeedService = module.get(DbSeedService); - organisationRepoMock = module.get(OrganisationRepo); + organisationRepositoryMock = module.get(OrganisationRepository); rolleRepoMock = module.get(RolleRepo); personRepoMock = module.get(PersonRepository); serviceProviderRepoMock = module.get(ServiceProviderRepo); @@ -151,7 +146,7 @@ describe('DbSeedService', () => { ); const persistedOrganisation: OrganisationDo = DoFactory.createOrganisation(true); - organisationRepoMock.save.mockResolvedValueOnce(persistedOrganisation); + organisationRepositoryMock.save.mockResolvedValueOnce(persistedOrganisation); await expect(dbSeedService.seedOrganisation(fileContentAsStr)).resolves.not.toThrow( EntityNotFoundError, ); @@ -166,7 +161,7 @@ describe('DbSeedService', () => { ); const persistedOrganisation: OrganisationDo = DoFactory.createOrganisation(true); - organisationRepoMock.save.mockResolvedValueOnce(persistedOrganisation); + organisationRepositoryMock.save.mockResolvedValueOnce(persistedOrganisation); await expect(dbSeedService.seedOrganisation(fileContentAsStr)).resolves.not.toThrow( EntityNotFoundError, ); @@ -180,10 +175,10 @@ describe('DbSeedService', () => { 'utf-8', ); const parent: OrganisationDo = createMock>(); - organisationRepoMock.save.mockResolvedValueOnce(parent); + organisationRepositoryMock.save.mockResolvedValueOnce(parent); //USE MockResolved instead of MockRecolvedOnce because it's called for administriert and zugehoerigZu dbSeedReferenceRepoMock.findUUID.mockResolvedValue(faker.string.uuid()); //mock UUID of referenced parent - organisationRepoMock.findById.mockResolvedValue(parent); + organisationRepositoryMock.findById.mockResolvedValue(parent); await expect(dbSeedService.seedOrganisation(fileContentAsStr)).resolves.not.toThrow( EntityNotFoundError, @@ -198,10 +193,10 @@ describe('DbSeedService', () => { 'utf-8', ); const parent: OrganisationDo = createMock>(); - organisationRepoMock.save.mockResolvedValueOnce(parent); + organisationRepositoryMock.save.mockResolvedValueOnce(parent); //USE MockResolved instead of MockRecolvedOnce because it's called for administriert and zugehoerigZu dbSeedReferenceRepoMock.findUUID.mockResolvedValue(faker.string.uuid()); //mock UUID of referenced parent - organisationRepoMock.findById.mockResolvedValue(parent); // mock get-SSK + organisationRepositoryMock.findById.mockResolvedValue(parent); // mock get-SSK await expect(dbSeedService.seedOrganisation(fileContentAsStr)).resolves.not.toThrow( EntityNotFoundError, @@ -217,7 +212,7 @@ describe('DbSeedService', () => { ); const persistedOrganisation: OrganisationDo = DoFactory.createOrganisation(true); - organisationRepoMock.save.mockResolvedValueOnce(persistedOrganisation); + organisationRepositoryMock.save.mockResolvedValueOnce(persistedOrganisation); await expect(dbSeedService.seedOrganisation(fileContentAsStr)).rejects.toThrow(EntityNotFoundError); }); }); @@ -230,7 +225,7 @@ describe('DbSeedService', () => { ); const persistedOrganisation: OrganisationDo = DoFactory.createOrganisation(true); - organisationRepoMock.save.mockResolvedValueOnce(persistedOrganisation); + organisationRepositoryMock.save.mockResolvedValueOnce(persistedOrganisation); await expect(dbSeedService.seedOrganisation(fileContentAsStr)).rejects.toThrow(EntityNotFoundError); }); }); @@ -242,7 +237,7 @@ describe('DbSeedService', () => { ); const persistedOrganisation: OrganisationDo = DoFactory.createOrganisation(true); - organisationRepoMock.save.mockResolvedValueOnce(persistedOrganisation); + organisationRepositoryMock.save.mockResolvedValueOnce(persistedOrganisation); await expect(dbSeedService.seedOrganisation(fileContentAsStr)).resolves.not.toThrow( EntityNotFoundError, ); @@ -263,7 +258,7 @@ describe('DbSeedService', () => { dbSeedReferenceRepoMock.findUUID.mockResolvedValueOnce(faker.string.uuid()); //mock UUID of referenced serviceProvider serviceProviderRepoMock.findById.mockResolvedValueOnce(serviceProviderMocked); dbSeedReferenceRepoMock.findUUID.mockResolvedValueOnce(faker.string.uuid()); //mock UUID of referenced parent - organisationRepoMock.findById.mockResolvedValue(createMock>()); // mock get-SSK + organisationRepositoryMock.findById.mockResolvedValue(createMock>()); // mock get-SSK rolleRepoMock.save.mockResolvedValueOnce(persistedRolle); await expect(dbSeedService.seedRolle(fileContentAsStr)).resolves.not.toThrow(EntityNotFoundError); @@ -282,7 +277,7 @@ describe('DbSeedService', () => { dbSeedReferenceRepoMock.findUUID.mockResolvedValueOnce(faker.string.uuid()); //mock UUID of referenced serviceProvider serviceProviderRepoMock.findById.mockResolvedValueOnce(serviceProviderMocked); dbSeedReferenceRepoMock.findUUID.mockResolvedValueOnce(faker.string.uuid()); //mock UUID of referenced parent - organisationRepoMock.findById.mockResolvedValue(createMock>()); // mock get-SSK + organisationRepositoryMock.findById.mockResolvedValue(createMock>()); // mock get-SSK rolleRepoMock.save.mockResolvedValueOnce(persistedRolle); await expect(dbSeedService.seedRolle(fileContentAsStr)).resolves.not.toThrow(EntityNotFoundError); @@ -302,7 +297,7 @@ describe('DbSeedService', () => { serviceProviderRepoMock.findById.mockResolvedValueOnce(serviceProviderMocked); dbSeedReferenceRepoMock.findUUID.mockResolvedValueOnce(faker.string.uuid()); //mock UUID of referenced parent - organisationRepoMock.findById.mockRejectedValueOnce(new EntityNotFoundError()); + organisationRepositoryMock.findById.mockRejectedValueOnce(new EntityNotFoundError()); rolleRepoMock.save.mockResolvedValueOnce(persistedRolle); await expect(dbSeedService.seedRolle(fileContentAsStr)).rejects.toThrow(EntityNotFoundError); @@ -331,7 +326,7 @@ describe('DbSeedService', () => { 'utf-8', ); dbSeedReferenceRepoMock.findUUID.mockResolvedValue(faker.string.uuid()); //mock UUID providedOnSchulstrukturknoten - organisationRepoMock.findById.mockResolvedValue(createMock>()); // mock get-SSK + organisationRepositoryMock.findById.mockResolvedValue(createMock>()); // mock get-SSK await expect(dbSeedService.seedServiceProvider(fileContentAsStr)).resolves.not.toThrow( EntityNotFoundError, @@ -377,7 +372,7 @@ describe('DbSeedService', () => { personRepoMock.findById.mockResolvedValue(createMock>()); // mock getReferencedPerson dbSeedReferenceRepoMock.findUUID.mockResolvedValue(faker.string.uuid()); //mock UUID in seeding-ref-table - organisationRepoMock.findById.mockResolvedValue(createMock>()); // mock getReferencedOrganisation + organisationRepositoryMock.findById.mockResolvedValue(createMock>()); // mock getReferencedOrganisation dbSeedReferenceRepoMock.findUUID.mockResolvedValue(faker.string.uuid()); //mock UUID in seeding-ref-table rolleRepoMock.findById.mockResolvedValue(createMock>()); // mock getReferencedRolle @@ -418,7 +413,7 @@ describe('DbSeedService', () => { dbSeedReferenceRepoMock.findUUID.mockResolvedValue(faker.string.uuid()); //mock UUID for person, found via seeding-ref-table personRepoMock.findById.mockResolvedValue(createMock>()); dbSeedReferenceRepoMock.findUUID.mockResolvedValue(faker.string.uuid()); //mock UUID for orga, found via seeding-ref-table - organisationRepoMock.findById.mockResolvedValue(undefined); + organisationRepositoryMock.findById.mockResolvedValue(undefined); await expect(dbSeedService.seedPersonenkontext(fileContentAsStr)).rejects.toThrow(EntityNotFoundError); }); @@ -436,7 +431,7 @@ describe('DbSeedService', () => { dbSeedReferenceRepoMock.findUUID.mockResolvedValueOnce(faker.string.uuid()); //mock UUID for person, found via seeding-ref-table personRepoMock.findById.mockResolvedValue(createMock>()); dbSeedReferenceRepoMock.findUUID.mockResolvedValueOnce(faker.string.uuid()); //mock UUID for orga, found via seeding-ref-table - organisationRepoMock.findById.mockResolvedValueOnce(createMock>()); + organisationRepositoryMock.findById.mockResolvedValueOnce(createMock>()); dbSeedReferenceRepoMock.findUUID.mockResolvedValueOnce(undefined); //mock UUID for rolle, found via seeding-ref-table await expect(dbSeedService.seedPersonenkontext(fileContentAsStr)).rejects.toThrow(EntityNotFoundError); @@ -453,7 +448,7 @@ describe('DbSeedService', () => { dbSeedReferenceRepoMock.findUUID.mockResolvedValue(faker.string.uuid()); //mock UUID for person, found via seeding-ref-table personRepoMock.findById.mockResolvedValue(createMock>()); dbSeedReferenceRepoMock.findUUID.mockResolvedValue(faker.string.uuid()); //mock UUID for orga, found via seeding-ref-table - organisationRepoMock.findById.mockResolvedValueOnce(createMock>()); + organisationRepositoryMock.findById.mockResolvedValueOnce(createMock>()); dbSeedReferenceRepoMock.findUUID.mockResolvedValue(faker.string.uuid()); //mock UUID for rolle, found via seeding-ref-table rolleRepoMock.findById.mockResolvedValue(undefined); diff --git a/src/console/dbseed/domain/db-seed.service.ts b/src/console/dbseed/domain/db-seed.service.ts index 1933f2717..c11797218 100644 --- a/src/console/dbseed/domain/db-seed.service.ts +++ b/src/console/dbseed/domain/db-seed.service.ts @@ -16,7 +16,6 @@ import { DomainError, EntityNotFoundError } from '../../../shared/error/index.js import { ClassLogger } from '../../../core/logging/class-logger.js'; import { ConfigService } from '@nestjs/config'; import { PersonenkontextFile } from '../file/personenkontext-file.js'; -import { OrganisationRepo } from '../../../modules/organisation/persistence/organisation.repo.js'; import { RolleFile } from '../file/rolle-file.js'; import { RolleRepo } from '../../../modules/rolle/repo/rolle.repo.js'; import { RolleFactory } from '../../../modules/rolle/domain/rolle.factory.js'; @@ -31,6 +30,8 @@ import { DbSeedReferenceRepo } from '../repo/db-seed-reference.repo.js'; import { DbSeedReference } from './db-seed-reference.js'; import { ReferencedEntityType } from '../repo/db-seed-reference.entity.js'; import { PersonenkontextFactory } from '../../../modules/personenkontext/domain/personenkontext.factory.js'; +import { OrganisationRepository } from '../../../modules/organisation/persistence/organisation.repository.js'; +import { Organisation } from '../../../modules/organisation/domain/organisation.js'; @Injectable() export class DbSeedService { @@ -41,7 +42,7 @@ export class DbSeedService { private readonly personFactory: PersonFactory, private readonly personRepository: PersonRepository, private readonly dBiamPersonenkontextRepo: DBiamPersonenkontextRepo, - private readonly organisationRepo: OrganisationRepo, + private readonly organisationRepository: OrganisationRepository, private readonly rolleRepo: RolleRepo, private readonly rolleFactory: RolleFactory, private readonly serviceProviderRepo: ServiceProviderRepo, @@ -68,9 +69,7 @@ export class DbSeedService { return entities; } - //to be refactored when organisation becomes DomainDriven - private async constructAndPersistOrganisation(data: OrganisationFile): Promise> { - const organisationDo: OrganisationDo = new OrganisationDo(); + private async constructAndPersistOrganisation(data: OrganisationFile): Promise> { let administriertVon: string | undefined = undefined; let zugehoerigZu: string | undefined = undefined; @@ -87,27 +86,30 @@ export class DbSeedService { zugehoerigZu = zugehoerigZuOrganisation.id; } + const organisation: Organisation = Organisation.createNew( + administriertVon, + zugehoerigZu, + data.kennung, + data.name, + data.namensergaenzung, + data.kuerzel, + data.typ, + data.traegerschaft, + ); + if (!administriertVon && !zugehoerigZu && data.kuerzel === 'Root') { - organisationDo.id = this.ROOT_ORGANISATION_ID; + organisation.id = this.ROOT_ORGANISATION_ID; } - organisationDo.administriertVon = administriertVon ?? undefined; - organisationDo.zugehoerigZu = zugehoerigZu ?? undefined; - organisationDo.kennung = data.kennung ?? undefined; - organisationDo.name = data.name ?? undefined; - organisationDo.namensergaenzung = data.namensergaenzung ?? undefined; - organisationDo.kuerzel = data.kuerzel ?? undefined; - organisationDo.typ = data.typ ?? undefined; - organisationDo.traegerschaft = data.traegerschaft ?? undefined; - - const persistedOrganisation: OrganisationDo = await this.organisationRepo.save(organisationDo); + const savedOrga: Organisation = await this.organisationRepository.save(organisation); const dbSeedReference: DbSeedReference = DbSeedReference.createNew( ReferencedEntityType.ORGANISATION, data.id, - persistedOrganisation.id, + savedOrga.id, ); await this.dbSeedReferenceRepo.create(dbSeedReference); - return organisationDo; + + return savedOrga; } public async seedOrganisation(fileContentAsStr: string): Promise { @@ -311,7 +313,7 @@ export class DbSeedService { ReferencedEntityType.ORGANISATION, ); if (!organisationUUID) throw new EntityNotFoundError('Organisation', seedingId.toString()); - const organisation: Option> = await this.organisationRepo.findById(organisationUUID); + const organisation: Option> = await this.organisationRepository.findById(organisationUUID); if (!organisation) throw new EntityNotFoundError('Organisation', seedingId.toString()); return organisation; diff --git a/src/core/ldap/domain/ldap-client.service.ts b/src/core/ldap/domain/ldap-client.service.ts index 3aa6caec1..93c278194 100644 --- a/src/core/ldap/domain/ldap-client.service.ts +++ b/src/core/ldap/domain/ldap-client.service.ts @@ -26,7 +26,9 @@ export class LdapClientService { private async bind(): Promise> { this.logger.info('LDAP: bind'); try { - await this.ldapClient.getClient().bind(this.ldapInstanceConfig.BIND_DN, this.ldapInstanceConfig.PASSWORD); + await this.ldapClient + .getClient() + .bind(this.ldapInstanceConfig.BIND_DN, this.ldapInstanceConfig.ADMIN_PASSWORD); this.logger.info('LDAP: Successfully connected'); return { ok: true, diff --git a/src/core/ldap/domain/ldap-client.spec.ts b/src/core/ldap/domain/ldap-client.spec.ts index 2fb66dbf1..eb4b7540f 100644 --- a/src/core/ldap/domain/ldap-client.spec.ts +++ b/src/core/ldap/domain/ldap-client.spec.ts @@ -65,7 +65,7 @@ describe('LDAP Client', () => { expect(ldapClient).toBeDefined(); }); - describe('test client creation', () => { + describe('getClient', () => { describe('when client is not already initialized', () => { it('should create a new one and return it', () => { const client: Client = ldapClient.getClient(); @@ -74,4 +74,20 @@ describe('LDAP Client', () => { }); }); }); + + describe('disconnect', () => { + describe('when client is initialized', () => { + it('should disconnect and return true', async () => { + ldapClient.getClient(); + + expect(await ldapClient.disconnect()).toBeTruthy(); + }); + }); + + describe('when client is NOT initialized', () => { + it('should return false', async () => { + expect(await ldapClient.disconnect()).toBeFalsy(); + }); + }); + }); }); diff --git a/src/core/ldap/domain/ldap-client.ts b/src/core/ldap/domain/ldap-client.ts index 2b65c5725..ee9615b27 100644 --- a/src/core/ldap/domain/ldap-client.ts +++ b/src/core/ldap/domain/ldap-client.ts @@ -1,9 +1,9 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, OnModuleDestroy } from '@nestjs/common'; import { Client } from 'ldapts'; import { LdapInstanceConfig } from '../ldap-instance-config.js'; @Injectable() -export class LdapClient { +export class LdapClient implements OnModuleDestroy { private client: Client | undefined; public constructor(private readonly ldapInstanceConfig: LdapInstanceConfig) {} @@ -19,4 +19,18 @@ export class LdapClient { } return this.client; } + + public async disconnect(): Promise { + if (this.client) { + await this.client.unbind(); + this.client = undefined; + return true; + } else { + return false; + } + } + + public async onModuleDestroy(): Promise { + await this.disconnect(); + } } diff --git a/src/core/ldap/domain/ldap-event-handler.spec.ts b/src/core/ldap/domain/ldap-event-handler.spec.ts index f1583fa8f..98ee42ba9 100644 --- a/src/core/ldap/domain/ldap-event-handler.spec.ts +++ b/src/core/ldap/domain/ldap-event-handler.spec.ts @@ -140,7 +140,7 @@ describe('LDAP Event Handler', () => { organisationRepositoryMock.findById.mockResolvedValueOnce(organisation); ldapClientServiceMock.createOrganisation.mockResolvedValueOnce(result); - await ldapEventHandler.asyncSchuleCreatedEventHandler(event); + await ldapEventHandler.handleSchuleCreatedEvent(event); expect(ldapClientServiceMock.createOrganisation).toHaveBeenCalledTimes(1); }); }); @@ -161,7 +161,7 @@ describe('LDAP Event Handler', () => { organisationRepositoryMock.findById.mockResolvedValueOnce(organisation); ldapClientServiceMock.createOrganisation.mockResolvedValueOnce(result); - await ldapEventHandler.asyncSchuleCreatedEventHandler(event); + await ldapEventHandler.handleSchuleCreatedEvent(event); expect(ldapClientServiceMock.createOrganisation).toHaveBeenCalledTimes(1); }); }); @@ -172,7 +172,7 @@ describe('LDAP Event Handler', () => { organisationRepositoryMock.findById.mockResolvedValueOnce(undefined); - await ldapEventHandler.asyncSchuleCreatedEventHandler(event); + await ldapEventHandler.handleSchuleCreatedEvent(event); expect(ldapClientServiceMock.createOrganisation).toHaveBeenCalledTimes(0); }); }); @@ -196,7 +196,7 @@ describe('LDAP Event Handler', () => { organisationRepositoryMock.findById.mockResolvedValueOnce(organisation); ldapClientServiceMock.deleteOrganisation.mockResolvedValueOnce(result); - await ldapEventHandler.asyncSchuleDeletedEventHandler(event); + await ldapEventHandler.handleSchuleDeletedEvent(event); expect(ldapClientServiceMock.deleteOrganisation).toHaveBeenCalledTimes(1); }); }); @@ -218,7 +218,7 @@ describe('LDAP Event Handler', () => { organisationRepositoryMock.findById.mockResolvedValueOnce(organisation); ldapClientServiceMock.deleteOrganisation.mockResolvedValueOnce(result); - await ldapEventHandler.asyncSchuleDeletedEventHandler(event); + await ldapEventHandler.handleSchuleDeletedEvent(event); expect(ldapClientServiceMock.deleteOrganisation).toHaveBeenCalledTimes(1); }); }); @@ -229,7 +229,7 @@ describe('LDAP Event Handler', () => { organisationRepositoryMock.findById.mockResolvedValueOnce(undefined); - await ldapEventHandler.asyncSchuleDeletedEventHandler(event); + await ldapEventHandler.handleSchuleDeletedEvent(event); expect(ldapClientServiceMock.deleteOrganisation).toHaveBeenCalledTimes(0); }); }); @@ -246,7 +246,7 @@ describe('LDAP Event Handler', () => { dbiamPersonenkontextRepoMock.find.mockResolvedValueOnce(undefined); - await ldapEventHandler.asyncPersonenkontextCreatedEventHandler(event); + await ldapEventHandler.handlePersonenkontextCreatedEvent(event); expect(ldapClientServiceMock.createLehrer).toHaveBeenCalledTimes(0); }); }); @@ -263,7 +263,7 @@ describe('LDAP Event Handler', () => { dbiamPersonenkontextRepoMock.find.mockResolvedValueOnce(personenkontext); rolleRepoMock.findById.mockResolvedValueOnce(undefined); - await ldapEventHandler.asyncPersonenkontextCreatedEventHandler(event); + await ldapEventHandler.handlePersonenkontextCreatedEvent(event); expect(ldapClientServiceMock.createLehrer).toHaveBeenCalledTimes(0); }); }); @@ -282,7 +282,7 @@ describe('LDAP Event Handler', () => { rolleRepoMock.findById.mockResolvedValueOnce(rolle); personRepositoryMock.findById.mockResolvedValueOnce(undefined); - await ldapEventHandler.asyncPersonenkontextCreatedEventHandler(event); + await ldapEventHandler.handlePersonenkontextCreatedEvent(event); expect(ldapClientServiceMock.createLehrer).toHaveBeenCalledTimes(0); }); }); @@ -303,7 +303,7 @@ describe('LDAP Event Handler', () => { personRepositoryMock.findById.mockResolvedValueOnce(person); organisationRepositoryMock.findById.mockResolvedValueOnce(undefined); - await ldapEventHandler.asyncPersonenkontextCreatedEventHandler(event); + await ldapEventHandler.handlePersonenkontextCreatedEvent(event); expect(ldapClientServiceMock.createLehrer).toHaveBeenCalledTimes(0); }); }); @@ -329,7 +329,7 @@ describe('LDAP Event Handler', () => { ok: false, error: new Error(), }); - await ldapEventHandler.asyncPersonenkontextCreatedEventHandler(event); + await ldapEventHandler.handlePersonenkontextCreatedEvent(event); expect(ldapClientServiceMock.createLehrer).toHaveBeenCalledTimes(1); }); }); @@ -346,7 +346,7 @@ describe('LDAP Event Handler', () => { dbiamPersonenkontextRepoMock.find.mockResolvedValueOnce(undefined); - await ldapEventHandler.asyncPersonenkontextDeletedEventHandler(event); + await ldapEventHandler.handlePersonenkontextDeletedEvent(event); expect(ldapClientServiceMock.deleteLehrer).toHaveBeenCalledTimes(0); }); }); @@ -363,7 +363,7 @@ describe('LDAP Event Handler', () => { dbiamPersonenkontextRepoMock.find.mockResolvedValueOnce(personenkontext); rolleRepoMock.findById.mockResolvedValueOnce(undefined); - await ldapEventHandler.asyncPersonenkontextDeletedEventHandler(event); + await ldapEventHandler.handlePersonenkontextDeletedEvent(event); expect(ldapClientServiceMock.deleteLehrer).toHaveBeenCalledTimes(0); }); }); @@ -382,7 +382,7 @@ describe('LDAP Event Handler', () => { rolleRepoMock.findById.mockResolvedValueOnce(rolle); personRepositoryMock.findById.mockResolvedValueOnce(undefined); - await ldapEventHandler.asyncPersonenkontextDeletedEventHandler(event); + await ldapEventHandler.handlePersonenkontextDeletedEvent(event); expect(ldapClientServiceMock.deleteLehrer).toHaveBeenCalledTimes(0); }); }); @@ -403,7 +403,7 @@ describe('LDAP Event Handler', () => { personRepositoryMock.findById.mockResolvedValueOnce(person); organisationRepositoryMock.findById.mockResolvedValueOnce(undefined); - await ldapEventHandler.asyncPersonenkontextDeletedEventHandler(event); + await ldapEventHandler.handlePersonenkontextDeletedEvent(event); expect(ldapClientServiceMock.deleteLehrer).toHaveBeenCalledTimes(0); }); }); @@ -429,7 +429,7 @@ describe('LDAP Event Handler', () => { ok: false, error: new Error(), }); - await ldapEventHandler.asyncPersonenkontextDeletedEventHandler(event); + await ldapEventHandler.handlePersonenkontextDeletedEvent(event); expect(ldapClientServiceMock.deleteLehrer).toHaveBeenCalledTimes(1); }); }); diff --git a/src/core/ldap/domain/ldap-event-handler.ts b/src/core/ldap/domain/ldap-event-handler.ts index 5eae86f01..16be410e8 100644 --- a/src/core/ldap/domain/ldap-event-handler.ts +++ b/src/core/ldap/domain/ldap-event-handler.ts @@ -29,7 +29,7 @@ export class LdapEventHandler { ) {} @EventHandler(SchuleCreatedEvent) - public async asyncSchuleCreatedEventHandler(event: SchuleCreatedEvent): Promise { + public async handleSchuleCreatedEvent(event: SchuleCreatedEvent): Promise { this.logger.info(`Received SchuleCreatedEvent, organisationId:${event.organisationId}`); const organisation: Option> = await this.organisationRepository.findById( @@ -52,7 +52,7 @@ export class LdapEventHandler { } @EventHandler(SchuleDeletedEvent) - public async asyncSchuleDeletedEventHandler(event: SchuleDeletedEvent): Promise { + public async handleSchuleDeletedEvent(event: SchuleDeletedEvent): Promise { this.logger.info(`Received SchuleDeletedEvent, organisationId:${event.organisationId}`); const organisation: Option> = await this.organisationRepository.findById( event.organisationId, @@ -74,7 +74,7 @@ export class LdapEventHandler { } @EventHandler(PersonenkontextCreatedEvent) - public async asyncPersonenkontextCreatedEventHandler(event: PersonenkontextCreatedEvent): Promise { + public async handlePersonenkontextCreatedEvent(event: PersonenkontextCreatedEvent): Promise { this.logger.info( `Received PersonenkontextCreatedEvent, personId:${event.personId}, orgaId:${event.organisationId}, rolleId:${event.rolleId}`, ); @@ -121,7 +121,7 @@ export class LdapEventHandler { } @EventHandler(PersonenkontextDeletedEvent) - public async asyncPersonenkontextDeletedEventHandler(event: PersonenkontextDeletedEvent): Promise { + public async handlePersonenkontextDeletedEvent(event: PersonenkontextDeletedEvent): Promise { this.logger.info( `Received PersonenkontextDeletedEvent, personId:${event.personId}, orgaId:${event.organisationId}, rolleId:${event.rolleId}`, ); diff --git a/src/core/ldap/ldap-instance-config.ts b/src/core/ldap/ldap-instance-config.ts index 10657970f..3647bb942 100644 --- a/src/core/ldap/ldap-instance-config.ts +++ b/src/core/ldap/ldap-instance-config.ts @@ -8,7 +8,7 @@ export class LdapInstanceConfig implements LdapConfig { public constructor( public URL: string, public BIND_DN: string, - public PASSWORD: string, + public ADMIN_PASSWORD: string, ) {} public static fromConfigService(): Provider { @@ -17,7 +17,7 @@ export class LdapInstanceConfig implements LdapConfig { useFactory: (configService: ConfigService): LdapInstanceConfig => { const ldapConfig: LdapConfig = configService.getOrThrow('LDAP'); - return new LdapInstanceConfig(ldapConfig.URL, ldapConfig.BIND_DN, ldapConfig.PASSWORD); + return new LdapInstanceConfig(ldapConfig.URL, ldapConfig.BIND_DN, ldapConfig.ADMIN_PASSWORD); }, inject: [ConfigService], }; diff --git a/src/core/ldap/ldap.module.ts b/src/core/ldap/ldap.module.ts index deba59cf2..bf917bdf7 100644 --- a/src/core/ldap/ldap.module.ts +++ b/src/core/ldap/ldap.module.ts @@ -19,6 +19,6 @@ import { LdapClient } from './domain/ldap-client.js'; PersonenKontextModule, ], providers: [LdapEventHandler, LdapClientService, LdapClient], - exports: [LdapEventHandler, LdapClientService], + exports: [LdapEventHandler, LdapClientService, LdapClient], }) export class LdapModule {} diff --git a/src/modules/organisation/persistence/organisation.repository.integration-spec.ts b/src/modules/organisation/persistence/organisation.repository.integration-spec.ts index fa0593d4f..05b319afa 100644 --- a/src/modules/organisation/persistence/organisation.repository.integration-spec.ts +++ b/src/modules/organisation/persistence/organisation.repository.integration-spec.ts @@ -21,6 +21,8 @@ import { ScopeOperator } from '../../../shared/persistence/index.js'; import { ConfigService } from '@nestjs/config'; import { ServerConfig } from '../../../shared/config/server.config.js'; import { DataConfig } from '../../../shared/config/index.js'; +import { EventService } from '../../../core/eventbus/services/event.service.js'; +import { createMock } from '@golevelup/ts-jest'; describe('OrganisationRepository', () => { let module: TestingModule; @@ -35,7 +37,14 @@ describe('OrganisationRepository', () => { beforeAll(async () => { module = await Test.createTestingModule({ imports: [ConfigTestModule, DatabaseTestModule.forRoot({ isDatabaseRequired: true }), MapperTestModule], - providers: [OrganisationPersistenceMapperProfile, OrganisationRepository], + providers: [ + OrganisationPersistenceMapperProfile, + OrganisationRepository, + { + provide: EventService, + useValue: createMock(), + }, + ], }).compile(); sut = module.get(OrganisationRepository); orm = module.get(MikroORM); diff --git a/src/modules/organisation/persistence/organisation.repository.ts b/src/modules/organisation/persistence/organisation.repository.ts index 9a0a168a1..1868687c9 100644 --- a/src/modules/organisation/persistence/organisation.repository.ts +++ b/src/modules/organisation/persistence/organisation.repository.ts @@ -6,6 +6,9 @@ import { OrganisationID } from '../../../shared/types/aggregate-ids.types.js'; import { Organisation } from '../domain/organisation.js'; import { OrganisationEntity } from './organisation.entity.js'; import { OrganisationScope } from './organisation.scope.js'; +import { OrganisationsTyp } from '../domain/organisation.enums.js'; +import { SchuleCreatedEvent } from '../../../shared/events/schule-created.event.js'; +import { EventService } from '../../../core/eventbus/services/event.service.js'; export function mapAggregateToData(organisation: Organisation): RequiredEntityData { return { @@ -42,6 +45,7 @@ export class OrganisationRepository { public readonly ROOT_ORGANISATION_ID: string; public constructor( + private readonly eventService: EventService, private readonly em: EntityManager, config: ConfigService, ) { @@ -65,6 +69,10 @@ export class OrganisationRepository { await this.em.persistAndFlush(organisationEntity); + if (organisationEntity.typ === OrganisationsTyp.SCHULE) { + this.eventService.publish(new SchuleCreatedEvent(organisationEntity.id)); + } + return mapEntityToAggregate(organisationEntity); } diff --git a/src/shared/config/config.env.ts b/src/shared/config/config.env.ts index 3bd52d226..4abb4f12e 100644 --- a/src/shared/config/config.env.ts +++ b/src/shared/config/config.env.ts @@ -3,10 +3,12 @@ import { KeycloakConfig } from './keycloak.config.js'; import { FrontendConfig } from './frontend.config.js'; import { HostConfig } from './host.config.js'; import { ItsLearningConfig } from './itslearning.config.js'; +import { LdapConfig } from './ldap.config.js'; export default (): { DB: Partial; KEYCLOAK: Partial; + LDAP: Partial; FRONTEND: Partial; HOST: Partial; ITSLEARNING: Partial; @@ -21,6 +23,11 @@ export default (): { CLIENT_SECRET: process.env['KC_CLIENT_SECRET'], BASE_URL: process.env['KC_BASE_URL'], }, + LDAP: { + URL: process.env['LDAP_URL'], + BIND_DN: process.env['LDAP_BIND_DN'], + ADMIN_PASSWORD: process.env['LDAP_ADMIN_PASSWORD'], + }, FRONTEND: { SESSION_SECRET: process.env['FRONTEND_SESSION_SECRET'], OIDC_CALLBACK_URL: process.env['FRONTEND_OIDC_CALLBACK_URL'], diff --git a/src/shared/config/config.loader.spec.ts b/src/shared/config/config.loader.spec.ts index e890f8969..d4f548350 100644 --- a/src/shared/config/config.loader.spec.ts +++ b/src/shared/config/config.loader.spec.ts @@ -46,6 +46,10 @@ describe('configloader', () => { LOGGING: { DEFAULT_LOG_LEVEL: 'debug', }, + LDAP: { + URL: 'ldap://localhost', + BIND_DN: 'cn=admin,dc=schule-sh,dc=de', + }, ITSLEARNING: { ENABLED: 'true', ENDPOINT: 'http://itslearning', @@ -58,6 +62,7 @@ describe('configloader', () => { const secrets: DeepPartial = { DB: { SECRET: 'SuperSecretSecret' }, KEYCLOAK: { ADMIN_SECRET: 'AdminClientSecret', CLIENT_SECRET: 'ClientSecret' }, + LDAP: { ADMIN_PASSWORD: 'password' }, FRONTEND: { SESSION_SECRET: 'SessionSecret' }, REDIS: { PASSWORD: 'password' }, ITSLEARNING: { @@ -123,6 +128,11 @@ describe('configloader', () => { LOGGING: { DEFAULT_LOG_LEVEL: 'debug', }, + LDAP: { + URL: 'ldap://localhost', + BIND_DN: 'cn=admin,dc=schule-sh,dc=de', + ADMIN_PASSWORD: 'password', + }, ITSLEARNING: { ENABLED: 'true', ENDPOINT: 'http://itslearning', diff --git a/src/shared/config/ldap.config.ts b/src/shared/config/ldap.config.ts index ad9a2f6df..b88d2c2e5 100644 --- a/src/shared/config/ldap.config.ts +++ b/src/shared/config/ldap.config.ts @@ -11,5 +11,5 @@ export class LdapConfig { @IsString() @IsNotEmpty() - public readonly PASSWORD!: string; + public readonly ADMIN_PASSWORD!: string; } diff --git a/test/utils/ldap-test.module.ts b/test/utils/ldap-test.module.ts index c92b86793..ba15958ca 100644 --- a/test/utils/ldap-test.module.ts +++ b/test/utils/ldap-test.module.ts @@ -46,7 +46,7 @@ export class LdapTestModule implements OnModuleDestroy { ? `ldap://${this.ldap.getHost()}:${this.ldap.getFirstMappedPort()}` : ldapConfig.URL; - return new LdapInstanceConfig(baseUrl, ldapConfig.BIND_DN, ldapConfig.PASSWORD); + return new LdapInstanceConfig(baseUrl, ldapConfig.BIND_DN, ldapConfig.ADMIN_PASSWORD); }, inject: [ConfigService], },