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

N21-1298 tool context restrictions #4579

Merged
merged 38 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
bc0c5cd
get tools/context-types
IgorCapCoder Nov 22, 2023
2eabe71
get tools/context-types unit tests
IgorCapCoder Nov 22, 2023
8e52e0c
extend external tool data model by restrictToContexts
IgorCapCoder Nov 22, 2023
9b1b4e9
fix dependency cycle WIP
IgorCapCoder Nov 22, 2023
59c9e22
fix dependency cycle
IgorCapCoder Nov 22, 2023
481ebbb
fix swagger and nuxt types
IgorCapCoder Nov 23, 2023
69ed45e
add attribute to repo mapper
MBergCap Nov 23, 2023
435f9b2
available tools filter for contexts
IgorCapCoder Nov 24, 2023
54d703c
Merge remote-tracking branch 'origin/N21-1298-tool-context-restrictio…
IgorCapCoder Nov 24, 2023
134390f
create context external tool validation for restricted contexts
IgorCapCoder Nov 24, 2023
3087a57
Merge branch 'main' into N21-1298-tool-context-restrictions
IgorCapCoder Nov 24, 2023
6db51dc
fix imports
IgorCapCoder Nov 24, 2023
0f87ba5
Merge branch 'main' into N21-1298-tool-context-restrictions
IgorCapCoder Nov 27, 2023
12d782d
Merge branch 'main' into N21-1298-tool-context-restrictions
IgorCapCoder Nov 27, 2023
ac819af
Merge remote-tracking branch 'origin/N21-1298-tool-context-restrictio…
IgorCapCoder Nov 27, 2023
25745b1
fix dependency cycle
IgorCapCoder Nov 27, 2023
7df6b85
fix dependency unit tests
IgorCapCoder Nov 27, 2023
fbcd329
coverage up
IgorCapCoder Nov 27, 2023
f746e86
requested changes WIP
IgorCapCoder Nov 28, 2023
9b3a441
Merge branch 'main' into N21-1298-tool-context-restrictions
IgorCapCoder Nov 29, 2023
3db5438
requested changes WIP 2
IgorCapCoder Nov 29, 2023
7c2e9cc
Merge branch 'main' into N21-1298-tool-context-restrictions
IgorCapCoder Nov 29, 2023
9550665
requested changes
IgorCapCoder Nov 29, 2023
04fa2c7
fix tests and imports
IgorCapCoder Nov 30, 2023
af6dfc2
Merge branch 'main' into N21-1298-tool-context-restrictions
IgorCapCoder Nov 30, 2023
cbec08c
Merge branch 'main' into N21-1298-tool-context-restrictions
IgorCapCoder Nov 30, 2023
8282351
add seed data
MBergCap Nov 30, 2023
4b3b9e1
Merge remote-tracking branch 'origin/N21-1298-tool-context-restrictio…
MBergCap Nov 30, 2023
8a9aa35
remove logo
MBergCap Nov 30, 2023
2f2601b
change seed data
MBergCap Dec 1, 2023
3847fbb
Merge branch 'main' into N21-1298-tool-context-restrictions
IgorCapCoder Dec 1, 2023
3ad8e7e
requested changes
IgorCapCoder Dec 1, 2023
18d3f8a
delete loggable constructor tests
IgorCapCoder Dec 1, 2023
c7885a6
change seed data
MBergCap Dec 1, 2023
c30906e
Merge remote-tracking branch 'origin/N21-1298-tool-context-restrictio…
MBergCap Dec 1, 2023
78c0af5
add unauthorized test
IgorCapCoder Dec 1, 2023
9b1af1d
Merge branch 'main' into N21-1298-tool-context-restrictions
IgorCapCoder Dec 1, 2023
b2cff53
Merge branch 'main' into N21-1298-tool-context-restrictions
IgorCapCoder Dec 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: [
IgorCapCoder marked this conversation as resolved.
Show resolved Hide resolved
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
Loading