Skip to content

Commit

Permalink
Merge branch 'main' into N21-2103-update-media-activations
Browse files Browse the repository at this point in the history
  • Loading branch information
GordonNicholasCap authored Dec 13, 2024
2 parents a564ea1 + 0df7b67 commit 81d7e80
Show file tree
Hide file tree
Showing 43 changed files with 793 additions and 200 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface SchulconnexClientConfig {
SCHULCONNEX_CLIENT__PERSON_INFO_TIMEOUT_IN_MS: number;
SCHULCONNEX_CLIENT__PERSONEN_INFO_TIMEOUT_IN_MS: number;
SCHULCONNEX_CLIENT__POLICIES_INFO_TIMEOUT_IN_MS: number;
SCHULCONNEX_CLIENT__API_URL?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { SchulconnexRestClientOptions } from './schulconnex-rest-client-options'

@Module({})
export class SchulconnexClientModule {
static registerAsync(): DynamicModule {
public static registerAsync(): DynamicModule {
return {
imports: [HttpModule, LoggerModule],
module: SchulconnexClientModule,
Expand All @@ -27,6 +27,7 @@ export class SchulconnexClientModule {
tokenEndpoint: configService.get<string>('SCHULCONNEX_CLIENT__TOKEN_ENDPOINT'),
clientId: configService.get<string>('SCHULCONNEX_CLIENT__CLIENT_ID'),
clientSecret: configService.get<string>('SCHULCONNEX_CLIENT__CLIENT_SECRET'),
personInfoTimeoutInMs: configService.get<number>('SCHULCONNEX_CLIENT__PERSON_INFO_TIMEOUT_IN_MS'),
personenInfoTimeoutInMs: configService.get<number>('SCHULCONNEX_CLIENT__PERSONEN_INFO_TIMEOUT_IN_MS'),
policiesInfoTimeoutInMs: configService.get<number>('SCHULCONNEX_CLIENT__POLICIES_INFO_TIMEOUT_IN_MS'),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export interface SchulconnexRestClientOptions {

clientSecret?: string;

personInfoTimeoutInMs?: number;

personenInfoTimeoutInMs?: number;

policiesInfoTimeoutInMs?: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ describe(SchulconnexRestClient.name, () => {
clientId: 'clientId',
clientSecret: 'clientSecret',
tokenEndpoint: 'https://schulconnex.url/token',
personenInfoTimeoutInMs: 30000,
policiesInfoTimeoutInMs: 30000,
personInfoTimeoutInMs: 30001,
personenInfoTimeoutInMs: 30002,
policiesInfoTimeoutInMs: 30003,
};

beforeAll(() => {
Expand Down Expand Up @@ -100,6 +101,7 @@ describe(SchulconnexRestClient.name, () => {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'gzip',
},
timeout: options.personInfoTimeoutInMs,
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@ export class SchulconnexRestClient implements SchulconnexApiInterface {
this.SCHULCONNEX_API_BASE_URL = options.apiUrl || '';
}

public async getPersonInfo(accessToken: string, options?: { overrideUrl: string }): Promise<SchulconnexResponse> {
public getPersonInfo(accessToken: string, options?: { overrideUrl: string }): Promise<SchulconnexResponse> {
const url: URL = new URL(options?.overrideUrl ?? `${this.SCHULCONNEX_API_BASE_URL}/person-info`);

const response: Promise<SchulconnexResponse> = this.getRequest<SchulconnexResponse>(url, accessToken);
const response: Promise<SchulconnexResponse> = this.getRequest<SchulconnexResponse>(
url,
accessToken,
this.options.personInfoTimeoutInMs
);

return response;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class Migration20241113100535 extends Migration {
);

if (teacherRoleUpdate.modifiedCount > 0) {
console.info('Rollback: Permission ROOM_CREATE added to role teacher.');
console.info('Rollback: Permission ROOM_CREATE removed from role teacher.');
}

const roomEditorRoleUpdate = await this.getCollection('roles').updateOne(
Expand All @@ -61,7 +61,7 @@ export class Migration20241113100535 extends Migration {
);

if (roomEditorRoleUpdate.modifiedCount > 0) {
console.info('Rollback: Permission ROOM_DELETE added to role roomeditor.');
console.info('Rollback: Permission ROOM_DELETE removed from role roomeditor.');
}
}
}
40 changes: 40 additions & 0 deletions apps/server/src/migrations/mikro-orm/Migration20241209165812.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Migration } from '@mikro-orm/migrations-mongodb';

export class Migration20241209165812 extends Migration {
async up(): Promise<void> {
// Add ROOM_OWNER role
await this.getCollection('roles').insertOne({
name: 'roomowner',
permissions: [
'ROOM_VIEW',
'ROOM_EDIT',
'ROOM_DELETE',
'ROOM_MEMBERS_ADD',
'ROOM_MEMBERS_REMOVE',
'ROOM_CHANGE_OWNER',
],
});
console.info(
'Added ROOM_OWNER role with ROOM_VIEW, -_EDIT, _DELETE, -_MEMBERS_ADD, -_MEMBERS_REMOVE AND -_CHANGE_OWNER permission'
);

// Add ROOM_ADMIN role
await this.getCollection('roles').insertOne({
name: 'roomadmin',
permissions: ['ROOM_VIEW', 'ROOM_EDIT', 'ROOM_MEMBERS_ADD', 'ROOM_MEMBERS_REMOVE'],
});
console.info(
'Added ROOM_ADMIN role with ROOM_VIEW, ROOM_EDIT, ROOM_MEMBERS_ADD AND ROOM_MEMBERS_REMOVE permissions'
);
}

async down(): Promise<void> {
// Remove ROOM_OWNER role
await this.getCollection('roles').deleteOne({ name: 'roomowner' });
console.info('Rollback: Removed ROOM_OWNER role');

// Remove ROOM_ADMIN role
await this.getCollection('roles').deleteOne({ name: 'roomadmin' });
console.info('Rollback: Removed ROOM_ADMIN role');
}
}
35 changes: 35 additions & 0 deletions apps/server/src/migrations/mikro-orm/Migration20241210152600.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Migration } from '@mikro-orm/migrations-mongodb';

export class Migration20241210152600 extends Migration {
async up(): Promise<void> {
const roomEditorRoleUpdate = await this.getCollection('roles').updateOne(
{ name: 'roomeditor' },
{
$set: {
permissions: ['ROOM_VIEW', 'ROOM_EDIT'],
},
}
);

if (roomEditorRoleUpdate.modifiedCount > 0) {
console.info('Permission ROOM_DELETE removed from role roomeditor.');
}
}

async down(): Promise<void> {
const roomEditorRoleUpdate = await this.getCollection('roles').updateOne(
{ name: 'roomeditor' },
{
$set: {
permissions: ['ROOM_VIEW', 'ROOM_EDIT', 'ROOM_DELETE'],
},
}
);

if (roomEditorRoleUpdate.modifiedCount > 0) {
console.info(
'Rollback: Permissions ROOM_DELETE added to and ROOM_MEMBERS_ADD and ROOM_MEMBERS_REMOVE removed from role roomeditor.'
);
}
}
}
13 changes: 8 additions & 5 deletions apps/server/src/modules/idp-console/idp-console.config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Configuration } from '@hpi-schul-cloud/commons';
import { ConsoleWriterConfig } from '@infra/console';
import { LoggerConfig } from '@src/core/logger';
import { RabbitMqConfig } from '@infra/rabbitmq';
import { SchulconnexClientConfig } from '@infra/schulconnex-client';
import { AccountConfig } from '@modules/account';
import { UserConfig } from '@modules/user';
import { SynchronizationConfig } from '@modules/synchronization';
import { SchulconnexClientConfig } from '@infra/schulconnex-client';
import { Configuration } from '@hpi-schul-cloud/commons';
import { UserConfig } from '@modules/user';
import { LanguageType } from '@shared/domain/interface';
import { RabbitMqConfig } from '@infra/rabbitmq';
import { LoggerConfig } from '@src/core/logger';

export interface IdpConsoleConfig
extends ConsoleWriterConfig,
Expand All @@ -33,6 +33,9 @@ const config: IdpConsoleConfig = {
TEACHER_VISIBILITY_FOR_EXTERNAL_TEAM_INVITATION: Configuration.get(
'TEACHER_VISIBILITY_FOR_EXTERNAL_TEAM_INVITATION'
) as string,
SCHULCONNEX_CLIENT__PERSON_INFO_TIMEOUT_IN_MS: Configuration.get(
'SCHULCONNEX_CLIENT__PERSON_INFO_TIMEOUT_IN_MS'
) as number,
SCHULCONNEX_CLIENT__PERSONEN_INFO_TIMEOUT_IN_MS: Configuration.get(
'SCHULCONNEX_CLIENT__PERSONEN_INFO_TIMEOUT_IN_MS'
) as number,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { externalGroupDtoFactory, externalGroupUserDtoFactory } from '../testing';
import { GroupProvisioningInfoLoggable } from './group-provisioning-info.loggable';

describe(GroupProvisioningInfoLoggable.name, () => {
describe('getLogMessage', () => {
const setup = () => {
const groupCount = 2;
const otherUserCount = 5;
const totalUserCount = groupCount * otherUserCount + groupCount;
const externalGroups = externalGroupDtoFactory.buildList(groupCount, {
otherUsers: externalGroupUserDtoFactory.buildList(otherUserCount),
});

const loggable = new GroupProvisioningInfoLoggable(externalGroups, 100);

return {
loggable,
totalUserCount,
groupCount,
};
};

it('should return a loggable message', () => {
const { loggable, totalUserCount, groupCount } = setup();

const message = loggable.getLogMessage();

expect(message).toEqual({
message: 'Group provisioning has finished.',
data: {
groupCount,
userCount: totalUserCount,
durationMs: 100,
},
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ErrorLogMessage, Loggable, LogMessage, ValidationErrorLogMessage } from '@src/core/logger';
import { ExternalGroupDto } from '../dto';

export class GroupProvisioningInfoLoggable implements Loggable {
constructor(private readonly groups: ExternalGroupDto[], private readonly durationMs: number) {}

public getLogMessage(): LogMessage | ErrorLogMessage | ValidationErrorLogMessage {
const userCount = this.groups.reduce(
(count: number, group: ExternalGroupDto) => count + (group.otherUsers?.length ?? 0),
this.groups.length
);

return {
message: 'Group provisioning has finished.',
data: {
groupCount: this.groups.length,
userCount,
durationMs: this.durationMs,
},
};
}
}
1 change: 1 addition & 0 deletions apps/server/src/modules/provisioning/loggable/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export { FetchingPoliciesInfoFailedLoggable } from './fetching-policies-info-fai
export { PoliciesInfoErrorResponseLoggable } from './policies-info-error-response-loggable';
export { UserRoleUnknownLoggableException } from './user-role-unknown.loggable-exception';
export { SchoolMissingLoggableException } from './school-missing.loggable-exception';
export { GroupProvisioningInfoLoggable } from './group-provisioning-info.loggable';
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface ProvisioningConfig {
FEATURE_SCHULCONNEX_COURSE_SYNC_ENABLED: boolean;
FEATURE_SCHULCONNEX_MEDIA_LICENSE_ENABLED: boolean;
PROVISIONING_SCHULCONNEX_POLICIES_INFO_URL: string;
PROVISIONING_SCHULCONNEX_GROUP_USERS_LIMIT?: number;
FEATURE_SANIS_GROUP_PROVISIONING_ENABLED: boolean;
FEATURE_OTHER_GROUPUSERS_PROVISIONING_ENABLED: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ export class SanisProvisioningStrategy extends SchulconnexProvisioningStrategy {
protected readonly schulconnexLicenseProvisioningService: SchulconnexLicenseProvisioningService,
protected readonly schulconnexToolProvisioningService: SchulconnexToolProvisioningService,
protected readonly configService: ConfigService<ProvisioningConfig, true>,
protected readonly logger: Logger,
private readonly responseMapper: SchulconnexResponseMapper,
private readonly schulconnexRestClient: SchulconnexRestClient,
private readonly logger: Logger
private readonly schulconnexRestClient: SchulconnexRestClient
) {
super(
schulconnexSchoolProvisioningService,
Expand All @@ -58,7 +58,8 @@ export class SanisProvisioningStrategy extends SchulconnexProvisioningStrategy {
schulconnexLicenseProvisioningService,
schulconnexToolProvisioningService,
groupService,
configService
configService,
logger
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ describe(SchulconnexResponseMapper.name, () => {
mapper = module.get(SchulconnexResponseMapper);
});

beforeEach(() => {
config.FEATURE_OTHER_GROUPUSERS_PROVISIONING_ENABLED = false;
config.PROVISIONING_SCHULCONNEX_GROUP_USERS_LIMIT = undefined;
});

describe('mapToExternalSchoolDto', () => {
describe('when a schulconnex response is provided', () => {
const setup = () => {
Expand Down Expand Up @@ -316,6 +321,8 @@ describe(SchulconnexResponseMapper.name, () => {

describe('when other participants have unknown roles', () => {
const setup = () => {
config.FEATURE_OTHER_GROUPUSERS_PROVISIONING_ENABLED = true;

const schulconnexResponse: SchulconnexResponse = schulconnexResponseFactory.build();
schulconnexResponse.personenkontexte[0].gruppen![0]!.sonstige_gruppenzugehoerige = [
{
Expand Down Expand Up @@ -514,6 +521,56 @@ describe(SchulconnexResponseMapper.name, () => {
);
});
});

describe('when there are too many users in groups', () => {
const setup = () => {
config.FEATURE_OTHER_GROUPUSERS_PROVISIONING_ENABLED = true;
config.PROVISIONING_SCHULCONNEX_GROUP_USERS_LIMIT = 1;

const schulconnexResponse: SchulconnexResponse = schulconnexResponseFactory.build();

return {
schulconnexResponse,
};
};

it('should not map other group users', () => {
const { schulconnexResponse } = setup();

const result: ExternalGroupDto[] | undefined = mapper.mapToExternalGroupDtos(schulconnexResponse);

expect(result).toEqual([
expect.objectContaining<Partial<ExternalGroupDto>>({
otherUsers: undefined,
}),
]);
});
});

describe('when there are not too many users in groups', () => {
const setup = () => {
config.FEATURE_OTHER_GROUPUSERS_PROVISIONING_ENABLED = true;
config.PROVISIONING_SCHULCONNEX_GROUP_USERS_LIMIT = 10;

const schulconnexResponse: SchulconnexResponse = schulconnexResponseFactory.build();

return {
schulconnexResponse,
};
};

it('should not map other group users', () => {
const { schulconnexResponse } = setup();

const result: ExternalGroupDto[] | undefined = mapper.mapToExternalGroupDtos(schulconnexResponse);

expect(result).not.toEqual([
expect.objectContaining({
otherUsers: undefined,
}),
]);
});
});
});

describe('mapLernperiode', () => {
Expand Down
Loading

0 comments on commit 81d7e80

Please sign in to comment.