diff --git a/apps/server/src/modules/pseudonym/loggable/index.ts b/apps/server/src/modules/pseudonym/loggable/index.ts new file mode 100644 index 00000000000..134eab4c556 --- /dev/null +++ b/apps/server/src/modules/pseudonym/loggable/index.ts @@ -0,0 +1 @@ +export * from './too-many-pseudonyms.loggable-exception'; diff --git a/apps/server/src/modules/pseudonym/loggable/too-many-pseudonyms.loggable-exception.spec.ts b/apps/server/src/modules/pseudonym/loggable/too-many-pseudonyms.loggable-exception.spec.ts new file mode 100644 index 00000000000..b03350c38ba --- /dev/null +++ b/apps/server/src/modules/pseudonym/loggable/too-many-pseudonyms.loggable-exception.spec.ts @@ -0,0 +1,43 @@ +import { TooManyPseudonymsLoggableException } from './too-many-pseudonyms.loggable-exception'; + +describe('TooManyPseudonymsLoggableException', () => { + describe('constructor', () => { + const setup = () => { + const pseudonym = 'pseudonym'; + + return { pseudonym }; + }; + + it('should create an instance of TooManyPseudonymsLoggableException', () => { + const { pseudonym } = setup(); + + const loggable = new TooManyPseudonymsLoggableException(pseudonym); + + expect(loggable).toBeInstanceOf(TooManyPseudonymsLoggableException); + }); + }); + + describe('getLogMessage', () => { + const setup = () => { + const pseudonym = 'pseudonym'; + const loggable = new TooManyPseudonymsLoggableException(pseudonym); + + return { loggable, pseudonym }; + }; + + it('should return a loggable message', () => { + const { loggable, pseudonym } = setup(); + + const message = loggable.getLogMessage(); + + expect(message).toEqual({ + type: 'PSEUDONYMS_TOO_MANY_PSEUDONYMS_FOUND', + message: 'Too many pseudonyms where found.', + stack: loggable.stack, + data: { + pseudonym, + }, + }); + }); + }); +}); diff --git a/apps/server/src/modules/pseudonym/loggable/too-many-pseudonyms.loggable-exception.ts b/apps/server/src/modules/pseudonym/loggable/too-many-pseudonyms.loggable-exception.ts new file mode 100644 index 00000000000..eb402f93900 --- /dev/null +++ b/apps/server/src/modules/pseudonym/loggable/too-many-pseudonyms.loggable-exception.ts @@ -0,0 +1,28 @@ +import { HttpStatus } from '@nestjs/common'; +import { BusinessError } from '@shared/common'; +import { Loggable } from '@src/core/logger/interfaces'; +import { ErrorLogMessage, LogMessage, ValidationErrorLogMessage } from '@src/core/logger/types'; + +export class TooManyPseudonymsLoggableException extends BusinessError implements Loggable { + constructor(private readonly pseudonym: string) { + super( + { + type: 'PSEUDONYMS_TOO_MANY_PSEUDONYMS_FOUND', + title: 'Too many pseudonyms where found.', + defaultMessage: 'Too many pseudonyms where found.', + }, + HttpStatus.BAD_REQUEST + ); + } + + getLogMessage(): LogMessage | ErrorLogMessage | ValidationErrorLogMessage { + return { + type: 'PSEUDONYMS_TOO_MANY_PSEUDONYMS_FOUND', + message: 'Too many pseudonyms where found.', + stack: this.stack, + data: { + pseudonym: this.pseudonym, + }, + }; + } +} diff --git a/apps/server/src/modules/tool/tool-launch/error/tool-status-outdated.loggable-exception.spec.ts b/apps/server/src/modules/tool/tool-launch/error/tool-status-outdated.loggable-exception.spec.ts new file mode 100644 index 00000000000..c20249fd69f --- /dev/null +++ b/apps/server/src/modules/tool/tool-launch/error/tool-status-outdated.loggable-exception.spec.ts @@ -0,0 +1,32 @@ +import { ToolStatusOutdatedLoggableException } from './tool-status-outdated.loggable-exception'; + +describe('ToolStatusOutdatedLoggableException', () => { + describe('getLogMessage', () => { + const setup = () => { + const toolId = 'toolId'; + const userId = 'userId'; + + const exception = new ToolStatusOutdatedLoggableException(userId, toolId); + + return { + exception, + }; + }; + + it('should log the correct message', () => { + const { exception } = setup(); + + const result = exception.getLogMessage(); + + expect(result).toEqual({ + type: 'TOOL_STATUS_OUTDATED', + message: expect.any(String), + stack: expect.any(String), + data: { + userId: 'userId', + toolId: 'toolId', + }, + }); + }); + }); +}); diff --git a/apps/server/src/modules/tool/tool-launch/mapper/tool-launch.mapper.ts b/apps/server/src/modules/tool/tool-launch/mapper/tool-launch.mapper.ts index 7fd0370d589..d3d8e060127 100644 --- a/apps/server/src/modules/tool/tool-launch/mapper/tool-launch.mapper.ts +++ b/apps/server/src/modules/tool/tool-launch/mapper/tool-launch.mapper.ts @@ -14,11 +14,11 @@ const toolConfigTypeToToolLaunchDataTypeMapping: Record = Object.entries( - toolConfigTypeToToolLaunchDataTypeMapping -).reduce((acc: Record, [key, value]) => { - return { ...acc, [value]: key as ToolConfigType }; -}, {} as Record); +const toolLaunchDataTypeToToolConfigTypeMapping: Record = { + [ToolLaunchDataType.BASIC]: ToolConfigType.BASIC, + [ToolLaunchDataType.LTI11]: ToolConfigType.LTI11, + [ToolLaunchDataType.OAUTH2]: ToolConfigType.OAUTH2, +}; export class ToolLaunchMapper { static mapToParameterLocation(location: CustomParameterLocation): PropertyLocation { diff --git a/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.ts b/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.ts index 826d6d69d91..8c957ca9421 100644 --- a/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.ts +++ b/apps/server/src/modules/tool/tool-launch/service/strategy/lti11-tool-launch.strategy.ts @@ -9,7 +9,7 @@ import { Authorization } from 'oauth-1.0a'; import { LtiRole } from '../../../common/enum'; import { ExternalTool } from '../../../external-tool/domain'; import { LtiRoleMapper } from '../../mapper'; -import { LaunchRequestMethod, PropertyData, PropertyLocation } from '../../types'; +import { LaunchRequestMethod, PropertyData, PropertyLocation, AuthenticationValues } from '../../types'; import { Lti11EncryptionService } from '../lti11-encryption.service'; import { AbstractLaunchStrategy } from './abstract-launch.strategy'; import { IToolLaunchParams } from './tool-launch-params.interface'; @@ -129,6 +129,19 @@ export class Lti11ToolLaunchStrategy extends AbstractLaunchStrategy { payload[property.name] = property.value; } + const authentication: AuthenticationValues = this.getAuthenticationValues(properties); + + const signedPayload: Authorization = this.lti11EncryptionService.sign( + authentication.keyValue, + authentication.secretValue, + url, + payload + ); + + return JSON.stringify(signedPayload); + } + + private getAuthenticationValues(properties: PropertyData[]): AuthenticationValues { const key: PropertyData | undefined = properties.find((property: PropertyData) => property.name === 'key'); const secret: PropertyData | undefined = properties.find((property: PropertyData) => property.name === 'secret'); @@ -138,9 +151,12 @@ export class Lti11ToolLaunchStrategy extends AbstractLaunchStrategy { ); } - const signedPayload: Authorization = this.lti11EncryptionService.sign(key.value, secret.value, url, payload); + const authentication: AuthenticationValues = new AuthenticationValues({ + keyValue: key.value, + secretValue: secret.value, + }); - return JSON.stringify(signedPayload); + return authentication; } // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/apps/server/src/modules/tool/tool-launch/service/strategy/oauth2-tool-launch.strategy.spec.ts b/apps/server/src/modules/tool/tool-launch/service/strategy/oauth2-tool-launch.strategy.spec.ts index b49eb4c85de..f1e3388ed15 100644 --- a/apps/server/src/modules/tool/tool-launch/service/strategy/oauth2-tool-launch.strategy.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/service/strategy/oauth2-tool-launch.strategy.spec.ts @@ -33,7 +33,7 @@ describe('OAuth2ToolLaunchStrategy', () => { }); describe('buildToolLaunchRequestPayload', () => { - describe('when always', () => { + describe('whenever it is called', () => { it('should return undefined', () => { const payload: string | null = strategy.buildToolLaunchRequestPayload('url', []); @@ -43,7 +43,7 @@ describe('OAuth2ToolLaunchStrategy', () => { }); describe('buildToolLaunchDataFromConcreteConfig', () => { - describe('when always', () => { + describe('whenever it is called', () => { const setup = () => { const externalTool: ExternalTool = externalToolFactory.build(); const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build(); @@ -69,7 +69,7 @@ describe('OAuth2ToolLaunchStrategy', () => { }); describe('determineLaunchRequestMethod', () => { - describe('when always', () => { + describe('whenever it is called', () => { it('should return GET', () => { const result: LaunchRequestMethod = strategy.determineLaunchRequestMethod([]); diff --git a/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.spec.ts b/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.spec.ts index 3be1df798fe..02bb484093f 100644 --- a/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.spec.ts @@ -181,32 +181,6 @@ describe('ToolLaunchService', () => { new InternalServerErrorException('Unknown tool config type') ); }); - - it('should call getSchoolExternalToolById', async () => { - const { launchParams } = setup(); - - try { - await service.getLaunchData('userId', launchParams.contextExternalTool); - } catch (exception) { - // Do nothing - } - - expect(schoolExternalToolService.getSchoolExternalToolById).toHaveBeenCalledWith( - launchParams.schoolExternalTool.id - ); - }); - - it('should call findExternalToolById', async () => { - const { launchParams } = setup(); - - try { - await service.getLaunchData('userId', launchParams.contextExternalTool); - } catch (exception) { - // Do nothing - } - - expect(externalToolService.findExternalToolById).toHaveBeenCalledWith(launchParams.schoolExternalTool.toolId); - }); }); describe('when tool configuration status is not LATEST', () => { diff --git a/apps/server/src/modules/tool/tool-launch/types/authentication-values.ts b/apps/server/src/modules/tool/tool-launch/types/authentication-values.ts new file mode 100644 index 00000000000..4d4d568cbd6 --- /dev/null +++ b/apps/server/src/modules/tool/tool-launch/types/authentication-values.ts @@ -0,0 +1,10 @@ +export class AuthenticationValues { + keyValue: string; + + secretValue: string; + + constructor(props: AuthenticationValues) { + this.keyValue = props.keyValue; + this.secretValue = props.secretValue; + } +} diff --git a/apps/server/src/modules/tool/tool-launch/types/index.ts b/apps/server/src/modules/tool/tool-launch/types/index.ts index 13eacedf6dd..47a1fe842cf 100644 --- a/apps/server/src/modules/tool/tool-launch/types/index.ts +++ b/apps/server/src/modules/tool/tool-launch/types/index.ts @@ -4,3 +4,4 @@ export * from './property-location'; export * from './tool-launch-request'; export * from './tool-launch-data-type'; export * from './launch-request-method'; +export * from './authentication-values'; diff --git a/apps/server/src/modules/tool/tool-launch/uc/tool-launch.uc.spec.ts b/apps/server/src/modules/tool/tool-launch/uc/tool-launch.uc.spec.ts index a0b31b9c321..62424d8b8aa 100644 --- a/apps/server/src/modules/tool/tool-launch/uc/tool-launch.uc.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/uc/tool-launch.uc.spec.ts @@ -1,6 +1,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { contextExternalToolFactory } from '@shared/testing'; +import { ObjectId } from 'bson'; import { ToolLaunchService } from '../service'; import { ToolLaunchData, ToolLaunchDataType, ToolLaunchRequest } from '../types'; import { ToolLaunchUc } from './tool-launch.uc'; @@ -62,7 +63,11 @@ describe('ToolLaunchUc', () => { properties: [], }); - const userId = 'userId'; + const userId: string = new ObjectId().toHexString(); + + toolPermissionHelper.ensureContextPermissions.mockResolvedValueOnce(); + contextExternalToolService.getContextExternalToolById.mockResolvedValueOnce(contextExternalTool); + toolLaunchService.getLaunchData.mockResolvedValueOnce(toolLaunchData); return { userId, @@ -82,8 +87,6 @@ describe('ToolLaunchUc', () => { it('should call service to get data', async () => { const { userId, contextExternalToolId, contextExternalTool } = setup(); - toolPermissionHelper.ensureContextPermissions.mockResolvedValue(); - contextExternalToolService.getContextExternalToolById.mockResolvedValue(contextExternalTool); await uc.getToolLaunchRequest(userId, contextExternalToolId); @@ -91,11 +94,9 @@ describe('ToolLaunchUc', () => { }); it('should call service to generate launch request', async () => { - const { userId, contextExternalToolId, contextExternalTool, toolLaunchData } = setup(); - toolPermissionHelper.ensureContextPermissions.mockResolvedValue(); - contextExternalToolService.getContextExternalToolById.mockResolvedValue(contextExternalTool); + const { userId, contextExternalToolId, toolLaunchData } = setup(); - toolLaunchService.getLaunchData.mockResolvedValue(toolLaunchData); + toolLaunchService.getLaunchData.mockResolvedValueOnce(toolLaunchData); await uc.getToolLaunchRequest(userId, contextExternalToolId); @@ -103,10 +104,7 @@ describe('ToolLaunchUc', () => { }); it('should return launch request', async () => { - const { userId, contextExternalToolId, toolLaunchData, contextExternalTool } = setup(); - toolPermissionHelper.ensureContextPermissions.mockResolvedValue(); - contextExternalToolService.getContextExternalToolById.mockResolvedValue(contextExternalTool); - toolLaunchService.getLaunchData.mockResolvedValue(toolLaunchData); + const { userId, contextExternalToolId } = setup(); const toolLaunchRequest: ToolLaunchRequest = await uc.getToolLaunchRequest(userId, contextExternalToolId);