Skip to content

Commit

Permalink
N21-1298 tool context restrictions (#4579)
Browse files Browse the repository at this point in the history
* get tools/context-types
* extend external tool data model by restrictToContexts
* available tools filter for contexts
* create context external tool validation for restricted contexts
* add seed data

---------

Co-authored-by: Malte Berg <[email protected]>
  • Loading branch information
2 people authored and bergatco committed Dec 6, 2023
1 parent 415bab4 commit ca9b575
Show file tree
Hide file tree
Showing 41 changed files with 994 additions and 163 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,6 @@
import { IdTokenCreationLoggableException } from './id-token-creation-exception.loggable';

describe('IdTokenCreationExceptionLoggable', () => {
describe('constructor', () => {
const setup = () => {
const clientId = 'clientId';
const userId = 'userId';

return { clientId, userId };
};

it('should create an instance of IdTokenCreationExceptionLoggable', () => {
const { clientId, userId } = setup();

const loggable = new IdTokenCreationLoggableException(clientId, userId);

expect(loggable).toBeInstanceOf(IdTokenCreationLoggableException);
});
});

describe('getLogMessage', () => {
const setup = () => {
const clientId = 'clientId';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,6 @@ import { ExternalGroupUserDto } from '../dto';
import { UserForGroupNotFoundLoggable } from './user-for-group-not-found.loggable';

describe('UserForGroupNotFoundLoggable', () => {
describe('constructor', () => {
const setup = () => {
const externalGroupUserDto: ExternalGroupUserDto = new ExternalGroupUserDto({
externalUserId: 'externalUserId',
roleName: RoleName.TEACHER,
});

return { externalGroupUserDto };
};

it('should create an instance of UserForGroupNotFoundLoggable', () => {
const { externalGroupUserDto } = setup();

const loggable = new UserForGroupNotFoundLoggable(externalGroupUserDto);

expect(loggable).toBeInstanceOf(UserForGroupNotFoundLoggable);
});
});

describe('getLogMessage', () => {
const setup = () => {
const externalGroupUserDto: ExternalGroupUserDto = new ExternalGroupUserDto({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,6 @@
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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { contextExternalToolFactory, externalToolFactory, schoolExternalToolFact
import { CommonToolService } from './common-tool.service';
import { ExternalTool } from '../../external-tool/domain';
import { SchoolExternalTool } from '../../school-external-tool/domain';
import { ToolConfigurationStatus } from '../enum';
import { ToolConfigurationStatus, ToolContextType } from '../enum';
import { ContextExternalTool } from '../../context-external-tool/domain';

describe('CommonToolService', () => {
Expand Down Expand Up @@ -205,4 +205,78 @@ describe('CommonToolService', () => {
});
});
});

describe('isContextRestricted', () => {
describe('when tool is not restricted to context', () => {
const setup = () => {
const externalTool: ExternalTool = externalToolFactory.build({ restrictToContexts: [] });
const context: ToolContextType = ToolContextType.COURSE;

return { externalTool, context };
};

it('should return false', () => {
const { externalTool, context } = setup();

const result = service.isContextRestricted(externalTool, context);

expect(result).toBe(false);
});
});

describe('when tool is restricted to all contexts', () => {
const setup = () => {
const externalTool: ExternalTool = externalToolFactory.build({
restrictToContexts: [ToolContextType.COURSE, ToolContextType.BOARD_ELEMENT],
});
const context: ToolContextType = ToolContextType.COURSE;

return { externalTool, context };
};

it('should return false', () => {
const { externalTool, context } = setup();

const result = service.isContextRestricted(externalTool, context);

expect(result).toBe(false);
});
});

describe('when tool is restricted to correct context', () => {
const setup = () => {
const externalTool: ExternalTool = externalToolFactory.build({ restrictToContexts: [ToolContextType.COURSE] });
const context: ToolContextType = ToolContextType.COURSE;

return { externalTool, context };
};

it('should return false', () => {
const { externalTool, context } = setup();

const result = service.isContextRestricted(externalTool, context);

expect(result).toBe(false);
});
});

describe('when tool is restricted to wrong context', () => {
const setup = () => {
const externalTool: ExternalTool = externalToolFactory.build({
restrictToContexts: [ToolContextType.BOARD_ELEMENT],
});
const context: ToolContextType = ToolContextType.COURSE;

return { externalTool, context };
};

it('should return true', () => {
const { externalTool, context } = setup();

const result = service.isContextRestricted(externalTool, context);

expect(result).toBe(true);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { ExternalTool } from '../../external-tool/domain';
import { SchoolExternalTool } from '../../school-external-tool/domain';
import { ContextExternalTool } from '../../context-external-tool/domain';
import { ToolConfigurationStatus } from '../enum';
import { ToolConfigurationStatus, ToolContextType } from '../enum';
import { ToolVersion } from '../interface';

// TODO N21-1337 remove class when tool versioning is removed
Expand All @@ -11,7 +11,7 @@ export class CommonToolService {
/**
* @deprecated use ToolVersionService
*/
determineToolConfigurationStatus(
public determineToolConfigurationStatus(
externalTool: ExternalTool,
schoolExternalTool: SchoolExternalTool,
contextExternalTool: ContextExternalTool
Expand All @@ -30,4 +30,11 @@ export class CommonToolService {
private isLatest(tool1: ToolVersion, tool2: ToolVersion): boolean {
return tool1.getVersion() >= tool2.getVersion();
}

public isContextRestricted(externalTool: ExternalTool, context: ToolContextType): boolean {
if (externalTool.restrictToContexts?.length && !externalTool.restrictToContexts.includes(context)) {
return true;
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ describe('ToolContextController (API)', () => {
isOptional: true,
}),
],
restrictToContexts: [ToolContextType.COURSE],
version: 1,
});
const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({
Expand Down Expand Up @@ -174,6 +175,115 @@ describe('ToolContextController (API)', () => {
// expected body is missed
});
});

describe('when external tool has no restrictions ', () => {
const setup = async () => {
const school: SchoolEntity = schoolFactory.buildWithId();
const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({ school }, [
Permission.CONTEXT_TOOL_ADMIN,
]);

const course: Course = courseFactory.buildWithId({ teachers: [teacherUser], school });

const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId({
parameters: [],
restrictToContexts: [],
version: 1,
});
const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({
tool: externalToolEntity,
school,
schoolParameters: [],
toolVersion: 1,
});

const postParams: ContextExternalToolPostParams = {
schoolToolId: schoolExternalToolEntity.id,
contextId: course.id,
displayName: course.name,
contextType: ToolContextType.COURSE,
parameters: [],
toolVersion: 1,
};

await em.persistAndFlush([teacherUser, teacherAccount, course, school, schoolExternalToolEntity]);
em.clear();

const loggedInClient: TestApiClient = await testApiClient.login(teacherAccount);

return {
loggedInClient,
postParams,
};
};

it('should create tool', async () => {
const { postParams, loggedInClient } = await setup();

const response = await loggedInClient.post().send(postParams);

expect(response.statusCode).toEqual(HttpStatus.CREATED);
expect(response.body).toEqual<ContextExternalToolResponse>({
id: expect.any(String),
schoolToolId: postParams.schoolToolId,
contextId: postParams.contextId,
displayName: postParams.displayName,
contextType: postParams.contextType,
parameters: [],
toolVersion: postParams.toolVersion,
});
});
});

describe('when external tool restricts to wrong context ', () => {
const setup = async () => {
const school: SchoolEntity = schoolFactory.buildWithId();
const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({ school }, [
Permission.CONTEXT_TOOL_ADMIN,
]);

const course: Course = courseFactory.buildWithId({ teachers: [teacherUser], school });

const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId({
parameters: [],
restrictToContexts: [ToolContextType.BOARD_ELEMENT],
version: 1,
});
const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({
tool: externalToolEntity,
school,
schoolParameters: [],
toolVersion: 1,
});

const postParams: ContextExternalToolPostParams = {
schoolToolId: schoolExternalToolEntity.id,
contextId: course.id,
displayName: course.name,
contextType: ToolContextType.COURSE,
parameters: [],
toolVersion: 1,
};

await em.persistAndFlush([teacherUser, teacherAccount, course, school, schoolExternalToolEntity]);
em.clear();

const loggedInClient: TestApiClient = await testApiClient.login(teacherAccount);

return {
loggedInClient,
postParams,
};
};

it('should return unprocessable entity', async () => {
const { postParams, loggedInClient } = await setup();

const response = await loggedInClient.post().send(postParams);

expect(response.statusCode).toEqual(HttpStatus.UNPROCESSABLE_ENTITY);
});
});
});

describe('[DELETE] tools/context-external-tools/:contextExternalToolId', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import {
cleanupCollections,
contextExternalToolEntityFactory,
courseFactory,
customParameterFactory,
externalToolEntityFactory,
schoolExternalToolEntityFactory,
schoolFactory,
} from '@shared/testing';
import { Response } from 'supertest';
import { ToolContextType } from '../../../common/enum';
import { CustomParameterLocation, CustomParameterScope, ToolContextType } from '../../../common/enum';
import { ExternalToolEntity } from '../../../external-tool/entity';
import { SchoolExternalToolEntity } from '../../../school-external-tool/entity';
import { ContextExternalToolEntity, ContextExternalToolType } from '../../entity';
Expand Down Expand Up @@ -115,6 +116,18 @@ describe('ToolReferenceController (API)', () => {
const course: Course = courseFactory.buildWithId({ school, teachers: [adminUser] });
const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId({
logoBase64: 'logoBase64',
parameters: [
customParameterFactory.build({
name: 'schoolMockParameter',
scope: CustomParameterScope.SCHOOL,
location: CustomParameterLocation.PATH,
}),
customParameterFactory.build({
name: 'contextMockParameter',
scope: CustomParameterScope.CONTEXT,
location: CustomParameterLocation.PATH,
}),
],
});
const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({
school,
Expand Down Expand Up @@ -234,6 +247,18 @@ describe('ToolReferenceController (API)', () => {
const course: Course = courseFactory.buildWithId({ school, teachers: [adminUser] });
const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId({
logoBase64: 'logoBase64',
parameters: [
customParameterFactory.build({
name: 'schoolMockParameter',
scope: CustomParameterScope.SCHOOL,
location: CustomParameterLocation.PATH,
}),
customParameterFactory.build({
name: 'contextMockParameter',
scope: CustomParameterScope.CONTEXT,
location: CustomParameterLocation.PATH,
}),
],
});
const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({
school,
Expand Down
Loading

0 comments on commit ca9b575

Please sign in to comment.