Skip to content

Commit

Permalink
N21-1968 schulconnex config fix (#5002)
Browse files Browse the repository at this point in the history
* changes schulconnex configs from config service and log message if their are missing
  • Loading branch information
arnegns authored and virgilchiriac committed May 14, 2024
1 parent 8ae9d24 commit 582ebed
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 64 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
import { OauthAdapterService } from '@modules/oauth/service';
import { HttpModule, HttpService } from '@nestjs/axios';
import { DynamicModule, Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Logger, LoggerModule } from '@src/core/logger';
import { SchulconnexRestClient } from './schulconnex-rest-client';
import { SchulconnexRestClientOptions } from './schulconnex-rest-client-options';

@Module({})
export class SchulconnexClientModule {
static register(options: SchulconnexRestClientOptions): DynamicModule {
static registerAsync(): DynamicModule {
return {
imports: [HttpModule, LoggerModule],
module: SchulconnexClientModule,
providers: [
OauthAdapterService,
{
provide: SchulconnexRestClient,
useFactory: (httpService: HttpService, oauthAdapterService: OauthAdapterService, logger: Logger) =>
new SchulconnexRestClient(options, httpService, oauthAdapterService, logger),
inject: [HttpService, OauthAdapterService, Logger],
useFactory: (
httpService: HttpService,
oauthAdapterService: OauthAdapterService,
logger: Logger,
configService: ConfigService
) => {
const options: SchulconnexRestClientOptions = {
apiUrl: configService.get<string>('SCHULCONNEX_CLIENT__API_URL'),
tokenEndpoint: configService.get<string>('SCHULCONNEX_CLIENT__TOKEN_ENDPOINT'),
clientId: configService.get<string>('SCHULCONNEX_CLIENT__CLIENT_ID'),
clientSecret: configService.get<string>('SCHULCONNEX_CLIENT__CLIENT_SECRET'),
personenInfoTimeoutInMs: configService.get<number>('SCHULCONNEX_CLIENT__PERSONEN_INFO_TIMEOUT_IN_MS'),
};
return new SchulconnexRestClient(options, httpService, oauthAdapterService, logger);
},
inject: [HttpService, OauthAdapterService, Logger, ConfigService],
},
],
exports: [SchulconnexRestClient],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
export interface SchulconnexRestClientOptions {
apiUrl: string;
apiUrl?: string;

tokenEndpoint: string;
tokenEndpoint?: string;

clientId: string;
clientId?: string;

clientSecret: string;
clientSecret?: string;

personenInfoTimeoutInMs?: number;
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ describe(SchulconnexRestClient.name, () => {
const setup = () => {
const badOptions: SchulconnexRestClientOptions = {
apiUrl: '',
clientId: '',
clientSecret: '',
clientId: undefined,
clientSecret: undefined,
tokenEndpoint: '',
};
return {
Expand All @@ -58,6 +58,16 @@ describe(SchulconnexRestClient.name, () => {

expect(logger.debug).toHaveBeenCalledWith(new SchulconnexConfigurationMissingLoggable());
});

it('should reject promise if configuration is missing', async () => {
const { badOptions } = setup();

const badOptionsClient = new SchulconnexRestClient(badOptions, httpService, oauthAdapterService, logger);

await expect(badOptionsClient.getPersonenInfo({})).rejects.toThrow(
'Missing configuration for SchulconnexRestClient'
);
});
});
});

Expand All @@ -80,7 +90,7 @@ describe(SchulconnexRestClient.name, () => {

await client.getPersonInfo(accessToken);

expect(httpService.get).toHaveBeenCalledWith(`${options.apiUrl}/person-info`, {
expect(httpService.get).toHaveBeenCalledWith(`${options.apiUrl ?? ''}/person-info`, {
headers: {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'gzip',
Expand Down Expand Up @@ -163,7 +173,9 @@ describe(SchulconnexRestClient.name, () => {
});

expect(httpService.get).toHaveBeenCalledWith(
`${optionsWithTimeout.apiUrl}/personen-info?organisation.id=1234&vollstaendig=personen%2Corganisationen`,
`${
optionsWithTimeout.apiUrl ?? ''
}/personen-info?organisation.id=1234&vollstaendig=personen%2Corganisationen`,
{
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
Expand Down Expand Up @@ -202,7 +214,7 @@ describe(SchulconnexRestClient.name, () => {

await client.getLizenzInfo(accessToken);

expect(httpService.get).toHaveBeenCalledWith(`${options.apiUrl}/lizenz-info`, {
expect(httpService.get).toHaveBeenCalledWith(`${options.apiUrl ?? ''}/lizenz-info`, {
headers: {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'gzip',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class SchulconnexRestClient implements SchulconnexApiInterface {
private readonly logger: Logger
) {
this.checkOptions();
this.SCHULCONNEX_API_BASE_URL = options.apiUrl;
this.SCHULCONNEX_API_BASE_URL = options.apiUrl || '';
}

// TODO: N21-1678 use this in provisioning module
Expand Down Expand Up @@ -63,10 +63,12 @@ export class SchulconnexRestClient implements SchulconnexApiInterface {
return response;
}

private checkOptions(): void {
private checkOptions(): boolean {
if (!this.options.apiUrl || !this.options.clientId || !this.options.clientSecret || !this.options.tokenEndpoint) {
this.logger.debug(new SchulconnexConfigurationMissingLoggable());
return false;
}
return true;
}

private async getRequest<T>(url: URL, accessToken: string, timeout?: number): Promise<T> {
Expand All @@ -86,13 +88,17 @@ export class SchulconnexRestClient implements SchulconnexApiInterface {
private async requestClientCredentialToken(): Promise<OAuthTokenDto> {
const { tokenEndpoint, clientId, clientSecret } = this.options;

if (!this.checkOptions()) {
return Promise.reject(new Error('Missing configuration for SchulconnexRestClient'));
}

const payload: ClientCredentialsGrantTokenRequest = new ClientCredentialsGrantTokenRequest({
client_id: clientId,
client_secret: clientSecret,
client_id: clientId ?? '',
client_secret: clientSecret ?? '',
grant_type: OAuthGrantType.CLIENT_CREDENTIALS_GRANT,
});

const tokenDto: OAuthTokenDto = await this.oauthAdapterService.sendTokenRequest(tokenEndpoint, payload);
const tokenDto: OAuthTokenDto = await this.oauthAdapterService.sendTokenRequest(tokenEndpoint ?? '', payload);

return tokenDto;
}
Expand Down
27 changes: 10 additions & 17 deletions apps/server/src/modules/idp-console/idp-console.module.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,23 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { Configuration } from '@hpi-schul-cloud/commons/lib';
import { ConsoleWriterModule } from '@infra/console';
import { RabbitMQWrapperModule } from '@infra/rabbitmq';
import { SchulconnexClientModule } from '@infra/schulconnex-client';
import { SynchronizationEntity, SynchronizationModule } from '@modules/synchronization';
import { defaultMikroOrmOptions } from '@modules/server';
import { DB_PASSWORD, DB_URL, DB_USERNAME } from '@src/config';
import { ALL_ENTITIES } from '@shared/domain/entity';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { defaultMikroOrmOptions } from '@modules/server';
import { SynchronizationEntity, SynchronizationModule } from '@modules/synchronization';
import { UserModule } from '@modules/user';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ALL_ENTITIES } from '@shared/domain/entity';
import { DB_PASSWORD, DB_URL, DB_USERNAME } from '@src/config';
import { LoggerModule } from '@src/core/logger';
import { RabbitMQWrapperModule } from '@infra/rabbitmq';
import { ConsoleWriterModule } from '@infra/console';
import { ConsoleModule } from 'nestjs-console';
import { SynchronizationUc } from './uc';
import { IdpSyncConsole } from './idp-sync-console';
import { SynchronizationUc } from './uc';

@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
SchulconnexClientModule.register({
apiUrl: Configuration.get('SCHULCONNEX_CLIENT__API_URL') as string,
tokenEndpoint: Configuration.get('SCHULCONNEX_CLIENT__TOKEN_ENDPOINT') as string,
clientId: Configuration.get('SCHULCONNEX_CLIENT__CLIENT_ID') as string,
clientSecret: Configuration.get('SCHULCONNEX_CLIENT__CLIENT_SECRET') as string,
personenInfoTimeoutInMs: Configuration.get('SCHULCONNEX_CLIENT__PERSONEN_INFO_TIMEOUT_IN_MS') as number,
}),
SchulconnexClientModule.registerAsync(),
SynchronizationModule,
MikroOrmModule.forRoot({
...defaultMikroOrmOptions,
Expand Down
9 changes: 1 addition & 8 deletions apps/server/src/modules/provisioning/provisioning.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Configuration } from '@hpi-schul-cloud/commons/lib';
import { AccountModule } from '@modules/account';
import { GroupModule } from '@modules/group';
import { LearnroomModule } from '@modules/learnroom';
Expand Down Expand Up @@ -39,13 +38,7 @@ import {
LoggerModule,
GroupModule,
LearnroomModule,
SchulconnexClientModule.register({
apiUrl: Configuration.get('SCHULCONNEX_CLIENT__API_URL') as string,
tokenEndpoint: Configuration.get('SCHULCONNEX_CLIENT__TOKEN_ENDPOINT') as string,
clientId: Configuration.get('SCHULCONNEX_CLIENT__CLIENT_ID') as string,
clientSecret: Configuration.get('SCHULCONNEX_CLIENT__CLIENT_SECRET') as string,
personenInfoTimeoutInMs: Configuration.get('SCHULCONNEX_CLIENT__PERSONEN_INFO_TIMEOUT_IN_MS') as number,
}),
SchulconnexClientModule.registerAsync(),
UserLicenseModule,
],
providers: [
Expand Down
20 changes: 18 additions & 2 deletions apps/server/src/modules/server/server.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Configuration } from '@hpi-schul-cloud/commons';
import type { IdentityManagementConfig } from '@infra/identity-management';
import type { SchulconnexClientConfig } from '@infra/schulconnex-client';
import type { AccountConfig } from '@modules/account';
import { AlertConfig } from '@modules/alert';
import type { AuthenticationConfig, XApiKeyConfig } from '@modules/authentication';
import type { BoardConfig } from '@modules/board';
import type { MediaBoardConfig } from '@modules/board/media-board.config';
Expand All @@ -21,10 +22,9 @@ import { type IUserImportFeatures, UserImportConfiguration } from '@modules/user
import type { UserLoginMigrationConfig } from '@modules/user-login-migration';
import { type IVideoConferenceSettings, VideoConferenceConfiguration } from '@modules/video-conference';
import { LanguageType } from '@shared/domain/interface';
import { SchulcloudTheme } from '@shared/domain/types';
import type { CoreModuleConfig } from '@src/core';
import type { MailConfig } from '@src/infra/mail/interfaces/mail-config';
import { AlertConfig } from '@modules/alert';
import { SchulcloudTheme } from '@shared/domain/types';
import { Timezone } from './types/timezone.enum';

export enum NodeEnvType {
Expand Down Expand Up @@ -112,6 +112,10 @@ export interface ServerConfig
I18N__DEFAULT_TIMEZONE: Timezone;
BOARD_COLLABORATION_URI: string;
FEATURE_NEW_LAYOUT_ENABLED: boolean;
SCHULCONNEX_CLIENT__API_URL: string | undefined;
SCHULCONNEX_CLIENT__TOKEN_ENDPOINT: string | undefined;
SCHULCONNEX_CLIENT__CLIENT_ID: string | undefined;
SCHULCONNEX_CLIENT__CLIENT_SECRET: string | undefined;
}

const config: ServerConfig = {
Expand Down Expand Up @@ -223,6 +227,18 @@ const config: ServerConfig = {
I18N__DEFAULT_LANGUAGE: Configuration.get('I18N__DEFAULT_LANGUAGE') as unknown as LanguageType,
I18N__FALLBACK_LANGUAGE: Configuration.get('I18N__FALLBACK_LANGUAGE') as unknown as LanguageType,
I18N__DEFAULT_TIMEZONE: Configuration.get('I18N__DEFAULT_TIMEZONE') as Timezone,
SCHULCONNEX_CLIENT__API_URL: Configuration.has('SCHULCONNEX_CLIENT__API_URL')
? (Configuration.get('SCHULCONNEX_CLIENT__API_URL') as string)
: undefined,
SCHULCONNEX_CLIENT__TOKEN_ENDPOINT: Configuration.has('SCHULCONNEX_CLIENT__TOKEN_ENDPOINT')
? (Configuration.get('SCHULCONNEX_CLIENT__TOKEN_ENDPOINT') as string)
: undefined,
SCHULCONNEX_CLIENT__CLIENT_ID: Configuration.has('SCHULCONNEX_CLIENT__CLIENT_ID')
? (Configuration.get('SCHULCONNEX_CLIENT__CLIENT_ID') as string)
: undefined,
SCHULCONNEX_CLIENT__CLIENT_SECRET: Configuration.has('SCHULCONNEX_CLIENT__CLIENT_SECRET')
? (Configuration.get('SCHULCONNEX_CLIENT__CLIENT_SECRET') as string)
: undefined,
SCHULCONNEX_CLIENT__PERSONEN_INFO_TIMEOUT_IN_MS: Configuration.get(
'SCHULCONNEX_CLIENT__PERSONEN_INFO_TIMEOUT_IN_MS'
) as number,
Expand Down
10 changes: 2 additions & 8 deletions apps/server/src/modules/server/server.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { SchulconnexClientModule } from '@infra/schulconnex-client';
import { Dictionary, IPrimaryKey } from '@mikro-orm/core';
import { MikroOrmModule, MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs';
import { AccountApiModule } from '@modules/account/account-api.module';
import { AlertModule } from '@modules/alert/alert.module';
import { AuthenticationApiModule } from '@modules/authentication/authentication-api.module';
import { BoardApiModule } from '@modules/board/board-api.module';
import { MediaBoardApiModule } from '@modules/board/media-board-api.module';
Expand Down Expand Up @@ -40,7 +41,6 @@ import { ALL_ENTITIES } from '@shared/domain/entity';
import { createConfigModuleOptions, DB_PASSWORD, DB_URL, DB_USERNAME } from '@src/config';
import { CoreModule } from '@src/core';
import { LoggerModule } from '@src/core/logger';
import { AlertModule } from '@modules/alert/alert.module';
import { UserLicenseModule } from '../user-license';
import { ServerConfigController, ServerController, ServerUc } from './api';
import { SERVER_CONFIG_TOKEN, serverConfig } from './server.config';
Expand All @@ -58,13 +58,7 @@ const serverModules = [
NewsModule,
UserApiModule,
UsersAdminApiModule,
SchulconnexClientModule.register({
apiUrl: Configuration.get('SCHULCONNEX_CLIENT__API_URL') as string,
tokenEndpoint: Configuration.get('SCHULCONNEX_CLIENT__TOKEN_ENDPOINT') as string,
clientId: Configuration.get('SCHULCONNEX_CLIENT__CLIENT_ID') as string,
clientSecret: Configuration.get('SCHULCONNEX_CLIENT__CLIENT_SECRET') as string,
personenInfoTimeoutInMs: Configuration.get('SCHULCONNEX_CLIENT__PERSONEN_INFO_TIMEOUT_IN_MS') as number,
}),
SchulconnexClientModule.registerAsync(),
ImportUserModule,
UserImportConfigModule,
LearnroomApiModule,
Expand Down
9 changes: 1 addition & 8 deletions apps/server/src/modules/user-import/user-import.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Configuration } from '@hpi-schul-cloud/commons/lib';
import { SchulconnexClientModule } from '@infra/schulconnex-client';
import { AccountModule } from '@modules/account';
import { AuthorizationModule } from '@modules/authorization';
Expand All @@ -25,13 +24,7 @@ import { UserImportConfigModule } from './user-import-config.module';
HttpModule,
UserModule,
OauthModule,
SchulconnexClientModule.register({
apiUrl: Configuration.get('SCHULCONNEX_CLIENT__API_URL') as string,
tokenEndpoint: Configuration.get('SCHULCONNEX_CLIENT__TOKEN_ENDPOINT') as string,
clientId: Configuration.get('SCHULCONNEX_CLIENT__CLIENT_ID') as string,
clientSecret: Configuration.get('SCHULCONNEX_CLIENT__CLIENT_SECRET') as string,
personenInfoTimeoutInMs: Configuration.get('SCHULCONNEX_CLIENT__PERSONEN_INFO_TIMEOUT_IN_MS') as number,
}),
SchulconnexClientModule.registerAsync(),
UserLoginMigrationModule,
],
controllers: [ImportUserController],
Expand Down
4 changes: 1 addition & 3 deletions config/development.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,7 @@
"FEATURE_BOARD_LAYOUT_ENABLED": true,
"SCHULCONNEX_CLIENT": {
"API_URL": "http://localhost:8888/v1/",
"TOKEN_ENDPOINT": "http://localhost:8888/realms/SANIS/protocol/openid-connect/token",
"CLIENT_ID": "schulcloud",
"CLIENT_SECRET": "secret"
"TOKEN_ENDPOINT": "http://localhost:8888/realms/SANIS/protocol/openid-connect/token"
},
"ETHERPAD_URI": "http://localhost:9001/api/1/",
"ETHERPAD": {
Expand Down

0 comments on commit 582ebed

Please sign in to comment.