Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SPSH-1583: Refaktorisierung OX-User-Blacklist #829

Merged
merged 8 commits into from
Dec 13, 2024
Merged
5 changes: 4 additions & 1 deletion src/console/console.module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import { Test, TestingModule } from '@nestjs/testing';
import { ConsoleModule } from './console.module.js';
import { DbConsole } from './db.console.js';
import { DbInitConsole } from './db-init.console.js';
import { LoggingTestModule } from '../../test/utils/index.js';
import { PersonModule } from '../modules/person/person.module.js';
import { KeycloakAdministrationModule } from '../modules/keycloak-administration/keycloak-administration.module.js';

describe('ConsoleModule', () => {
let module: TestingModule;

beforeAll(async () => {
module = await Test.createTestingModule({
imports: [ConsoleModule],
imports: [PersonModule, KeycloakAdministrationModule, ConsoleModule, LoggingTestModule],
}).compile();
});

Expand Down
2 changes: 0 additions & 2 deletions src/console/console.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { DbConsole } from './db.console.js';
import { DbInitConsole } from './db-init.console.js';
import { LoggerModule } from '../core/logging/logger.module.js';
import { KeycloakAdministrationModule } from '../modules/keycloak-administration/keycloak-administration.module.js';
import { UsernameGeneratorService } from '../modules/person/domain/username-generator.service.js';
import { KeycloakConfigModule } from '../modules/keycloak-administration/keycloak-config.module.js';
import { OrganisationModule } from '../modules/organisation/organisation.module.js';
import { RolleModule } from '../modules/rolle/rolle.module.js';
Expand Down Expand Up @@ -93,7 +92,6 @@ import { KeycloakConsoleModule } from './keycloak/keycloak-console.module.js';
DbInitMigrationConsole,
DbCreateMigrationConsole,
DbApplyMigrationConsole,
UsernameGeneratorService,
DbSeedDataGeneratorConsole,
],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { DBiamPersonenkontextService } from '../../modules/personenkontext/domai
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';
import { OxUserBlacklistRepo } from '../../modules/person/persistence/ox-user-blacklist.repo.js';

describe('DbSeedConsoleMockedDbSeedRepo', () => {
let module: TestingModule;
Expand All @@ -50,6 +51,7 @@ describe('DbSeedConsoleMockedDbSeedRepo', () => {
providers: [
UsernameGeneratorService,
DBiamPersonenkontextRepo,
OxUserBlacklistRepo,
DbSeedConsole,
DbSeedService,
DBiamPersonenkontextService,
Expand Down
3 changes: 2 additions & 1 deletion src/console/dbseed/db-seed.console.integration-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { RolleModule } from '../../modules/rolle/rolle.module.js';
import { ServiceProviderModule } from '../../modules/service-provider/service-provider.module.js';
import { DbSeedModule } from './db-seed.module.js';
import { PersonenKontextModule } from '../../modules/personenkontext/personenkontext.module.js';
import { OxUserBlacklistRepo } from '../../modules/person/persistence/ox-user-blacklist.repo.js';

describe('DbSeedConsoleIntegration', () => {
let module: TestingModule;
Expand All @@ -41,7 +42,7 @@ describe('DbSeedConsoleIntegration', () => {
ServiceProviderModule,
PersonenKontextModule,
],
providers: [UsernameGeneratorService, DBiamPersonenkontextRepo],
providers: [UsernameGeneratorService, DBiamPersonenkontextRepo, OxUserBlacklistRepo],
})
.overrideModule(KeycloakConfigModule)
.useModule(KeycloakConfigTestModule.forRoot({ isKeycloakRequired: true }))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { PersonModule } from '../../../modules/person/person.module.js';
import { DbSeedModule } from '../db-seed.module.js';
import { PersonenKontextModule } from '../../../modules/personenkontext/personenkontext.module.js';
import { VornameForPersonWithTrailingSpaceError } from '../../../modules/person/domain/vorname-with-trailing-space.error.js';
import { OxUserBlacklistRepo } from '../../../modules/person/persistence/ox-user-blacklist.repo.js';

describe('DbSeedServiceIntegration', () => {
let module: TestingModule;
Expand All @@ -42,7 +43,7 @@ describe('DbSeedServiceIntegration', () => {
LoggingTestModule,
PersonenKontextModule,
],
providers: [UsernameGeneratorService, DBiamPersonenkontextRepo],
providers: [UsernameGeneratorService, DBiamPersonenkontextRepo, OxUserBlacklistRepo],
})
.overrideModule(KeycloakConfigModule)
.useModule(KeycloakConfigTestModule.forRoot({ isKeycloakRequired: true }))
Expand Down
2 changes: 2 additions & 0 deletions src/modules/email/persistence/email.repo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { PersonAlreadyHasEnabledEmailAddressError } from '../error/person-alread
import { UserLockRepository } from '../../keycloak-administration/repository/user-lock.repository.js';
import { PersonEmailResponse } from '../../person/api/person-email-response.js';
import { generatePassword } from '../../../shared/util/password-generator.js';
import { OxUserBlacklistRepo } from '../../person/persistence/ox-user-blacklist.repo.js';
import { OrganisationID, PersonID } from '../../../shared/types/aggregate-ids.types.js';

describe('EmailRepo', () => {
Expand All @@ -45,6 +46,7 @@ describe('EmailRepo', () => {
imports: [ConfigTestModule, DatabaseTestModule.forRoot({ isDatabaseRequired: true })],
providers: [
UsernameGeneratorService,
OxUserBlacklistRepo,
EmailRepo,
EmailFactory,
PersonFactory,
Expand Down
27 changes: 27 additions & 0 deletions src/modules/person/domain/ox-user-blacklist-entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { OXEmail, OXUserName } from '../../../shared/types/ox-ids.types.js';

export class OxUserBlacklistEntry<WasPersisted extends boolean> {
public constructor(
public id: Persisted<string, WasPersisted>,
public readonly createdAt: Persisted<Date, WasPersisted>,
public readonly updatedAt: Persisted<Date, WasPersisted>,
public email: OXEmail,
public name: string,
public username: OXUserName,
) {}

public static construct(
id: string,
createdAt: Date,
updatedAt: Date,
email: OXEmail,
name: string,
username: OXUserName,
): OxUserBlacklistEntry<true> {
return new OxUserBlacklistEntry(id, createdAt, updatedAt, email, name, username);
}

public static createNew(email: OXEmail, name: string, username: OXUserName): OxUserBlacklistEntry<false> {
return new OxUserBlacklistEntry(undefined, undefined, undefined, email, name, username);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ import {
ConfigTestModule,
} from '../../../../test/utils/index.js';
import { MikroORM } from '@mikro-orm/core';
import { OxUserBlacklistRepo } from '../persistence/ox-user-blacklist.repo.js';
import { ClassLogger } from '../../../core/logging/class-logger.js';

describe('The UsernameGenerator Service', () => {
describe('UsernameGeneratorService', () => {
let module: TestingModule;
let service: UsernameGeneratorService;
let kcUserService: DeepMocked<KeycloakUserService>;
let loggerMock: DeepMocked<ClassLogger>;
let em: EntityManager;
let orm: MikroORM;

Expand All @@ -31,12 +34,21 @@ describe('The UsernameGenerator Service', () => {
imports: [ConfigTestModule, DatabaseTestModule.forRoot({ isDatabaseRequired: true })],
providers: [
UsernameGeneratorService,
{ provide: KeycloakUserService, useValue: createMock<KeycloakUserService>() },
OxUserBlacklistRepo,
{
provide: KeycloakUserService,
useValue: createMock<KeycloakUserService>(),
},
{
provide: ClassLogger,
useValue: createMock<ClassLogger>(),
},
],
}).compile();
orm = module.get(MikroORM);
service = module.get(UsernameGeneratorService);
kcUserService = module.get(KeycloakUserService);
loggerMock = module.get(ClassLogger);
em = module.get(EntityManager);

await DatabaseTestModule.setupDatabase(orm);
Expand Down Expand Up @@ -127,6 +139,7 @@ describe('The UsernameGenerator Service', () => {
.mockResolvedValueOnce({ ok: true, value: createMock<User<true>>() })
.mockResolvedValueOnce({ ok: false, error: new EntityNotFoundError('Not found') });
const generatedUsername: Result<string, DomainError> = await service.generateUsername('Max', 'Meyer');
expect(loggerMock.info).toHaveBeenLastCalledWith(`Next Available Username Is:mmeyer1`);
expect(generatedUsername).toEqual({ ok: true, value: 'mmeyer1' });
});

Expand All @@ -137,6 +150,7 @@ describe('The UsernameGenerator Service', () => {
} else return Promise.resolve({ ok: false, error: new EntityNotFoundError('Not found') });
});
const generatedUsername: Result<string, DomainError> = await service.generateUsername('Max', 'Meyer');
expect(loggerMock.info).toHaveBeenLastCalledWith(`Next Available Username Is:mmeyer2`);
expect(generatedUsername).toEqual({ ok: true, value: 'mmeyer2' });
});

Expand All @@ -148,6 +162,7 @@ describe('The UsernameGenerator Service', () => {
.mockResolvedValueOnce({ ok: false, error: new EntityNotFoundError('Not found') })
.mockResolvedValueOnce({ ok: true, value: createMock<User<true>>() });
const generatedUsername: Result<string, DomainError> = await service.generateUsername('Renate', 'Bergmann');
expect(loggerMock.info).toHaveBeenLastCalledWith(`Next Available Username Is:rbergmann3`);
expect(generatedUsername).toEqual({ ok: true, value: 'rbergmann3' });
});

Expand All @@ -156,6 +171,7 @@ describe('The UsernameGenerator Service', () => {
await expect(service.generateUsername('Maximilian', 'Mustermann')).rejects.toStrictEqual(
new KeycloakClientError('Could not reach'),
);
expect(loggerMock.info).toHaveBeenCalledTimes(0);
});

it('should return error if username can not be generated (cleaned names are of length 0)', async () => {
Expand Down Expand Up @@ -199,6 +215,8 @@ describe('The UsernameGenerator Service', () => {
const generatedUsername: Result<string, DomainError> = await service.generateUsername('Max', 'Meyer');

// Assert: The generated username should have the counter appended
expect(loggerMock.info).toHaveBeenLastCalledWith(`Next Available Username Is:mmeyer1`);
phaelcg marked this conversation as resolved.
Show resolved Hide resolved

expect(generatedUsername).toEqual({ ok: true, value: 'mmeyer1' });
});
});
37 changes: 15 additions & 22 deletions src/modules/person/domain/username-generator.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import {
InvalidAttributeLengthError,
} from '../../../shared/error/index.js';
import { isDIN91379A, toDIN91379SearchForm } from '../../../shared/util/din-91379-validation.js';
import { OxUserBlacklistEntity } from '../persistence/ox-user-blacklist.entity.js';
import { EntityManager } from '@mikro-orm/postgresql';
import { OxUserBlacklistRepo } from '../persistence/ox-user-blacklist.repo.js';
import { OxUserBlacklistEntry } from './ox-user-blacklist-entry.js';
import { ClassLogger } from '../../../core/logging/class-logger.js';

@Injectable()
export class UsernameGeneratorService {
public constructor(
private readonly em: EntityManager,
private readonly logger: ClassLogger,
private kcUserService: KeycloakUserService,
private oxUserBlacklistRepo: OxUserBlacklistRepo,
) {}

public async generateUsername(firstname: string, lastname: string): Promise<Result<string, DomainError>> {
Expand Down Expand Up @@ -80,22 +82,17 @@ export class UsernameGeneratorService {
while (await this.usernameExists(calculatedUsername + counter)) {
counter = counter + 1;
}
/* eslint-disable no-await-in-loop */
return calculatedUsername + counter;
}

public async findByOxUsername(username: string): Promise<OxUserBlacklistEntity | null> {
const person: Option<OxUserBlacklistEntity> = await this.em.findOne(OxUserBlacklistEntity, {
username: username,
});
if (person) {
return person;
}
this.logger.info(`Next Available Username Is:${calculatedUsername + counter}`);

return null;
return calculatedUsername + counter;
}

public async usernameExists(username: string): Promise<boolean> {
/**
* This method can throw errors e.g. if Keycloak search fails.
* @param username
* @private
*/
private async usernameExists(username: string): Promise<boolean> {
// Check Keycloak
const searchResult: Result<User<true>, DomainError> = await this.kcUserService.findOne({ username });
if (searchResult.ok) {
Expand All @@ -105,12 +102,8 @@ export class UsernameGeneratorService {
}

// Check OX Blacklist for the username. If it exists then return true.
const oxUser: OxUserBlacklistEntity | null = await this.findByOxUsername(username);

if (oxUser) {
return true; // Username exists in the blacklist
}
const oxUser: Option<OxUserBlacklistEntry<true>> = await this.oxUserBlacklistRepo.findByOxUsername(username);
phaelcg marked this conversation as resolved.
Show resolved Hide resolved

return false; // Username is available
return !!oxUser;
}
}
Loading
Loading