Skip to content

Commit

Permalink
Merge branch 'main' into N21-1293-remove-groups-from-course
Browse files Browse the repository at this point in the history
  • Loading branch information
mrikallab committed Oct 20, 2023
2 parents 9d8f302 + d570dd6 commit 630cb37
Show file tree
Hide file tree
Showing 28 changed files with 853 additions and 22 deletions.
2 changes: 2 additions & 0 deletions apps/server/src/modules/authorization/authorization.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
UserRule,
UserLoginMigrationRule,
LegacySchoolRule,
GroupRule,
} from './domain/rules';
import { AuthorizationHelper, AuthorizationService, RuleManager } from './domain';
import { FeathersAuthorizationService, FeathersAuthProvider } from './feathers';
Expand All @@ -33,6 +34,7 @@ import { FeathersAuthorizationService, FeathersAuthProvider } from './feathers';
ContextExternalToolRule,
CourseGroupRule,
CourseRule,
GroupRule,
LessonRule,
SchoolExternalToolRule,
SubmissionRule,
Expand Down
210 changes: 210 additions & 0 deletions apps/server/src/modules/authorization/domain/rules/group.rule.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { Test, TestingModule } from '@nestjs/testing';
import { Permission, Role, SchoolEntity, User } from '@shared/domain';
import { groupFactory, roleFactory, schoolFactory, setupEntities, userFactory } from '@shared/testing';
import { Action, AuthorizationContext, AuthorizationHelper } from '@src/modules/authorization';
import { Group } from '@src/modules/group';
import { ObjectId } from 'bson';
import { GroupRule } from './group.rule';

describe('GroupRule', () => {
let module: TestingModule;
let rule: GroupRule;

let authorizationHelper: DeepMocked<AuthorizationHelper>;

beforeAll(async () => {
await setupEntities();

module = await Test.createTestingModule({
providers: [
GroupRule,
{
provide: AuthorizationHelper,
useValue: createMock<AuthorizationHelper>(),
},
],
}).compile();

rule = module.get(GroupRule);
authorizationHelper = module.get(AuthorizationHelper);
});

afterAll(async () => {
await module.close();
});

beforeEach(() => {
jest.clearAllMocks();
});

describe('isApplicable', () => {
describe('when the entity is applicable', () => {
const setup = () => {
const role: Role = roleFactory.buildWithId();
const user: User = userFactory.buildWithId({ roles: [role] });
const group: Group = groupFactory.build({
users: [
{
userId: user.id,
roleId: user.roles[0].id,
},
],
});

return {
user,
group,
};
};

it('should return true', () => {
const { user, group } = setup();

const result = rule.isApplicable(user, group);

expect(result).toEqual(true);
});
});

describe('when the entity is not applicable', () => {
const setup = () => {
const role: Role = roleFactory.buildWithId();
const userNotInGroup: User = userFactory.buildWithId({ roles: [role] });

return {
userNotInGroup,
};
};

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

const result = rule.isApplicable(userNotInGroup, {} as unknown as Group);

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

describe('hasPermission', () => {
describe('when the user has all required permissions and is at the same school then the group', () => {
const setup = () => {
const role: Role = roleFactory.buildWithId();
const school: SchoolEntity = schoolFactory.buildWithId();
const user: User = userFactory.buildWithId({ school, roles: [role] });
const group: Group = groupFactory.build({
users: [
{
userId: user.id,
roleId: user.roles[0].id,
},
],
organizationId: user.school.id,
});
const context: AuthorizationContext = {
action: Action.write,
requiredPermissions: [Permission.GROUP_VIEW],
};

authorizationHelper.hasAllPermissions.mockReturnValue(true);

return {
user,
group,
context,
};
};

it('should check all permissions', () => {
const { user, group, context } = setup();

rule.hasPermission(user, group, context);

expect(authorizationHelper.hasAllPermissions).toHaveBeenCalledWith(user, context.requiredPermissions);
});

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

const result = rule.hasPermission(user, group, context);

expect(result).toEqual(true);
});
});

describe('when the user has not the required permission', () => {
const setup = () => {
const role: Role = roleFactory.buildWithId({ permissions: [] });
const school: SchoolEntity = schoolFactory.buildWithId();
const user: User = userFactory.buildWithId({ school, roles: [role] });
const group: Group = groupFactory.build({
users: [
{
userId: user.id,
roleId: user.roles[0].id,
},
],
organizationId: user.school.id,
});
const context: AuthorizationContext = {
action: Action.write,
requiredPermissions: [Permission.GROUP_VIEW],
};

authorizationHelper.hasAllPermissions.mockReturnValue(false);

return {
user,
group,
context,
};
};

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

const result = rule.hasPermission(user, group, context);

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

describe('when the user is at another school then the group', () => {
const setup = () => {
const role: Role = roleFactory.buildWithId({ permissions: [] });
const school: SchoolEntity = schoolFactory.buildWithId();
const user: User = userFactory.buildWithId({ school, roles: [role] });
const group: Group = groupFactory.build({
users: [
{
userId: user.id,
roleId: user.roles[0].id,
},
],
organizationId: new ObjectId().toHexString(),
});
const context: AuthorizationContext = {
action: Action.write,
requiredPermissions: [Permission.GROUP_VIEW],
};

authorizationHelper.hasAllPermissions.mockReturnValue(true);

return {
user,
group,
context,
};
};

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

const result = rule.hasPermission(user, group, context);

expect(result).toEqual(false);
});
});
});
});
24 changes: 24 additions & 0 deletions apps/server/src/modules/authorization/domain/rules/group.rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Injectable } from '@nestjs/common';
import { User } from '@shared/domain';
import { Group } from '@src/modules/group';
import { AuthorizationContext, Rule } from '../type';
import { AuthorizationHelper } from '../service/authorization.helper';

@Injectable()
export class GroupRule implements Rule<Group> {
constructor(private readonly authorizationHelper: AuthorizationHelper) {}

public isApplicable(user: User, domainObject: Group): boolean {
const isMatched: boolean = domainObject instanceof Group;

return isMatched;
}

public hasPermission(user: User, domainObject: Group, context: AuthorizationContext): boolean {
const hasPermission: boolean =
this.authorizationHelper.hasAllPermissions(user, context.requiredPermissions) &&
(domainObject.organizationId ? user.school.id === domainObject.organizationId : true);

return hasPermission;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export * from './task.rule';
export * from './team.rule';
export * from './user-login-migration.rule';
export * from './user.rule';
export * from './group.rule';
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
TeamRule,
UserRule,
UserLoginMigrationRule,
GroupRule,
} from '../rules';
import { RuleManager } from './rule-manager';

Expand All @@ -33,6 +34,7 @@ describe('RuleManager', () => {
let boardDoRule: DeepMocked<BoardDoRule>;
let contextExternalToolRule: DeepMocked<ContextExternalToolRule>;
let userLoginMigrationRule: DeepMocked<UserLoginMigrationRule>;
let groupRule: DeepMocked<GroupRule>;

beforeAll(async () => {
await setupEntities();
Expand All @@ -42,6 +44,7 @@ describe('RuleManager', () => {
RuleManager,
{ provide: CourseRule, useValue: createMock<CourseRule>() },
{ provide: CourseGroupRule, useValue: createMock<CourseGroupRule>() },
{ provide: GroupRule, useValue: createMock<GroupRule>() },
{ provide: LessonRule, useValue: createMock<LessonRule>() },
{ provide: LegacySchoolRule, useValue: createMock<LegacySchoolRule>() },
{ provide: UserRule, useValue: createMock<UserRule>() },
Expand All @@ -68,6 +71,7 @@ describe('RuleManager', () => {
boardDoRule = await module.get(BoardDoRule);
contextExternalToolRule = await module.get(ContextExternalToolRule);
userLoginMigrationRule = await module.get(UserLoginMigrationRule);
groupRule = await module.get(GroupRule);
});

afterEach(() => {
Expand Down Expand Up @@ -98,6 +102,7 @@ describe('RuleManager', () => {
boardDoRule.isApplicable.mockReturnValueOnce(false);
contextExternalToolRule.isApplicable.mockReturnValueOnce(false);
userLoginMigrationRule.isApplicable.mockReturnValueOnce(false);
groupRule.isApplicable.mockReturnValueOnce(false);

return { user, object, context };
};
Expand All @@ -119,6 +124,7 @@ describe('RuleManager', () => {
expect(boardDoRule.isApplicable).toBeCalled();
expect(contextExternalToolRule.isApplicable).toBeCalled();
expect(userLoginMigrationRule.isApplicable).toBeCalled();
expect(groupRule.isApplicable).toBeCalled();
});

it('should return CourseRule', () => {
Expand Down Expand Up @@ -148,6 +154,7 @@ describe('RuleManager', () => {
boardDoRule.isApplicable.mockReturnValueOnce(false);
contextExternalToolRule.isApplicable.mockReturnValueOnce(false);
userLoginMigrationRule.isApplicable.mockReturnValueOnce(false);
groupRule.isApplicable.mockReturnValueOnce(false);

return { user, object, context };
};
Expand Down Expand Up @@ -177,6 +184,7 @@ describe('RuleManager', () => {
boardDoRule.isApplicable.mockReturnValueOnce(false);
contextExternalToolRule.isApplicable.mockReturnValueOnce(false);
userLoginMigrationRule.isApplicable.mockReturnValueOnce(false);
groupRule.isApplicable.mockReturnValueOnce(false);

return { user, object, context };
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
TeamRule,
UserLoginMigrationRule,
UserRule,
GroupRule,
} from '../rules';

@Injectable()
Expand All @@ -33,7 +34,8 @@ export class RuleManager {
private readonly schoolExternalToolRule: SchoolExternalToolRule,
private readonly boardDoRule: BoardDoRule,
private readonly contextExternalToolRule: ContextExternalToolRule,
private readonly userLoginMigrationRule: UserLoginMigrationRule
private readonly userLoginMigrationRule: UserLoginMigrationRule,
private readonly groupRule: GroupRule
) {
this.rules = [
this.courseRule,
Expand All @@ -48,6 +50,7 @@ export class RuleManager {
this.boardDoRule,
this.contextExternalToolRule,
this.userLoginMigrationRule,
this.groupRule,
];
}

Expand Down
Loading

0 comments on commit 630cb37

Please sign in to comment.