Skip to content

Commit

Permalink
fix: check permissions of workspace team when sharing actions
Browse files Browse the repository at this point in the history
  • Loading branch information
apsantiso committed Aug 12, 2024
1 parent 87c2ea6 commit 6b7d161
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 58 deletions.
51 changes: 24 additions & 27 deletions src/modules/sharing/guards/sharing-permissions.guard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ describe('SharingPermissionsGuard', () => {
}));

jest
.spyOn(guard, 'isTeamMemberAbleToPerformAction')
.spyOn(guard, 'isWorkspaceMemberAbleToPerfomAction')
.mockResolvedValue(true);
jest.spyOn(userUseCases, 'findByUuid').mockResolvedValue(user);

Expand All @@ -123,7 +123,7 @@ describe('SharingPermissionsGuard', () => {
}));

jest
.spyOn(guard, 'isTeamMemberAbleToPerformAction')
.spyOn(guard, 'isWorkspaceMemberAbleToPerfomAction')
.mockResolvedValue(false);

await expect(guard.canActivate(context)).resolves.toBe(false);
Expand All @@ -148,7 +148,7 @@ describe('SharingPermissionsGuard', () => {
}));

jest
.spyOn(guard, 'isTeamMemberAbleToPerformAction')
.spyOn(guard, 'isWorkspaceMemberAbleToPerfomAction')
.mockResolvedValue(true);
jest.spyOn(userUseCases, 'findByUuid').mockResolvedValue(null);

Expand All @@ -175,66 +175,63 @@ describe('SharingPermissionsGuard', () => {
await expect(guard.canActivate(context)).resolves.toBe(true);
});

describe('isTeamMemberAbleToPerformAction', () => {
it('When team is able to perfom action and user is part of team, it should return true', async () => {
const teamId = v4();
describe('isWorkspaceMemberAbleToPerfomAction', () => {
it('When workspace member is part of a team that allows the action, it should return true', async () => {
const workspaceId = v4();
const sharedRootFolderId = v4();
const action = SharingActionName.UploadFile;
const team = newWorkspaceTeam();

workspaceUseCases.getTeamsUserBelongsTo.mockResolvedValue([team]);

sharingUseCases.canPerfomAction.mockResolvedValue(true);
workspaceUseCases.findUserInTeam.mockResolvedValue({
teamUser: newWorkspaceTeamUser(),
team: newWorkspaceTeam(),
});

const result = await guard.isTeamMemberAbleToPerformAction(
const result = await guard.isWorkspaceMemberAbleToPerfomAction(
user,
teamId,
workspaceId,
sharedRootFolderId,
action,
);

expect(result).toBe(true);
expect(sharingUseCases.canPerfomAction).toHaveBeenCalledWith(
user.uuid,
[team.id],
sharedRootFolderId,
action,
SharedWithType.WorkspaceTeam,
);
expect(workspaceUseCases.findUserInTeam).toHaveBeenCalledWith(
expect(workspaceUseCases.getTeamsUserBelongsTo).toHaveBeenCalledWith(
user.uuid,
teamId,
workspaceId,
);
});

it('When team is able to perform action but user is not part of team, it should return false', async () => {
const teamId = v4();
it('When workspace member is not part of a team that allows the action, it should return true', async () => {
const workspaceId = v4();
const sharedRootFolderId = v4();
const action = SharingActionName.UploadFile;
const team = newWorkspaceTeam();

sharingUseCases.canPerfomAction.mockResolvedValue(true);
workspaceUseCases.findUserInTeam.mockResolvedValue({
teamUser: null,
team: newWorkspaceTeam(),
});
workspaceUseCases.getTeamsUserBelongsTo.mockResolvedValue([team]);
sharingUseCases.canPerfomAction.mockResolvedValue(false);

const result = await guard.isTeamMemberAbleToPerformAction(
const result = await guard.isWorkspaceMemberAbleToPerfomAction(
user,
teamId,
workspaceId,
sharedRootFolderId,
action,
);

expect(result).toBe(false);
expect(sharingUseCases.canPerfomAction).toHaveBeenCalledWith(
user.uuid,
[team.id],
sharedRootFolderId,
action,
SharedWithType.WorkspaceTeam,
);
expect(workspaceUseCases.findUserInTeam).toHaveBeenCalledWith(
expect(workspaceUseCases.getTeamsUserBelongsTo).toHaveBeenCalledWith(
user.uuid,
teamId,
workspaceId,
);
});
});
Expand Down
51 changes: 23 additions & 28 deletions src/modules/sharing/guards/sharing-permissions.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
import { Reflector } from '@nestjs/core';
import { User } from '../../user/user.domain';
import { UserUseCases } from '../../user/user.usecase';
import { FolderAttributes } from '../../folder/folder.attributes';
import { verifyWithDefaultSecret } from '../../../lib/jwt';
import { SharingService } from '../../sharing/sharing.service';
import {
Expand All @@ -20,10 +19,10 @@ import {
PermissionsOptions,
} from './sharing-permissions.decorator';
import { Workspace } from '../../workspaces/domains/workspaces.domain';
import { WorkspaceTeam } from '../../workspaces/domains/workspace-team.domain';
import { Folder } from '../../folder/folder.domain';
import { WorkspacesUsecases } from '../../workspaces/workspaces.usecase';
import { extractDataFromRequest } from '../../../common/extract-data-from-request';
import { SharingAccessTokenData } from './sharings-token.interface';

@Injectable()
export class SharingPermissionsGuard implements CanActivate {
Expand Down Expand Up @@ -60,18 +59,7 @@ export class SharingPermissionsGuard implements CanActivate {
const { action } = permissionsOptions;

const decoded = verifyWithDefaultSecret(resourcesToken) as
| {
isRootToken?: boolean;
owner?: {
uuid?: User['uuid'];
};
sharedRootFolderId?: FolderAttributes['uuid'];
sharedWithType: SharedWithType;
workspace?: {
workspaceId: Workspace['id'];
teamId: WorkspaceTeam['id'];
};
}
| SharingAccessTokenData
| string;

if (typeof decoded === 'string') {
Expand All @@ -85,12 +73,13 @@ export class SharingPermissionsGuard implements CanActivate {
: decoded.sharedRootFolderId;

if (decoded.workspace) {
userIsAllowedToPerfomAction = await this.isTeamMemberAbleToPerformAction(
requester,
decoded.workspace.teamId,
sharedItemId,
action,
);
userIsAllowedToPerfomAction =
await this.isWorkspaceMemberAbleToPerfomAction(
requester,
decoded.workspace.workspaceId,
sharedItemId,
action,
);
} else {
userIsAllowedToPerfomAction = await this.isUserAbleToPerfomAction(
requester,
Expand All @@ -117,23 +106,29 @@ export class SharingPermissionsGuard implements CanActivate {
return true;
}

async isTeamMemberAbleToPerformAction(
async isWorkspaceMemberAbleToPerfomAction(
requester: User,
teamId: WorkspaceTeam['id'],
workspaceId: Workspace['id'],
sharedRootFolderId: Folder['uuid'],
action: SharingActionName,
) {
const [userIsAllowedToPerfomAction, isUserPartOfTeam] = await Promise.all([
this.sharingUseCases.canPerfomAction(
const teamsUserBelongsTo =
await this.workspaceUseCases.getTeamsUserBelongsTo(
requester.uuid,
workspaceId,
);

const teamsIds = teamsUserBelongsTo.map((team) => team.id);

const userIsAllowedToPerfomAction =
await this.sharingUseCases.canPerfomAction(
teamsIds,
sharedRootFolderId,
action,
SharedWithType.WorkspaceTeam,
),
this.workspaceUseCases.findUserInTeam(requester.uuid, teamId),
]);
);

return userIsAllowedToPerfomAction && !!isUserPartOfTeam?.teamUser;
return userIsAllowedToPerfomAction;
}

async isUserAbleToPerfomAction(
Expand Down
16 changes: 16 additions & 0 deletions src/modules/sharing/guards/sharings-token.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { FolderAttributes } from '../../folder/folder.attributes';
import { User } from '../../user/user.domain';
import { Workspace } from '../../workspaces/domains/workspaces.domain';
import { SharedWithType } from '../sharing.domain';

export interface SharingAccessTokenData {
isRootToken?: boolean;
owner?: {
uuid?: User['uuid'];
};
sharedRootFolderId?: FolderAttributes['uuid'];
sharedWithType: SharedWithType;
workspace?: {
workspaceId: Workspace['id'];
};
}
14 changes: 12 additions & 2 deletions src/modules/sharing/sharing.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,22 +92,32 @@ export class SequelizeSharingRepository implements SharingRepository {
}

async findPermissionsInSharing(
sharedWith: Sharing['sharedWith'],
sharedWith: Sharing['sharedWith'] | Sharing['sharedWith'][],
sharedWithType: Sharing['sharedWithType'],
itemId: Sharing['itemId'],
) {
const sharedWithFilter = Array.isArray(sharedWith)
? { [Op.in]: sharedWith }
: sharedWith;

const permissions = await this.permissions.findAll({
group: 'PermissionModel.id',
include: [
{
model: RoleModel,
required: true,
attributes: [],
include: [
{
model: SharingRolesModel,
required: true,
attributes: [],
include: [
{
model: SharingModel,
attributes: [],
where: {
sharedWith,
sharedWith: sharedWithFilter,
itemId,
sharedWithType,
},
Expand Down
2 changes: 1 addition & 1 deletion src/modules/sharing/sharing.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2106,7 +2106,7 @@ export class SharingService {
}

async canPerfomAction(
sharedWith: Sharing['sharedWith'],
sharedWith: Sharing['sharedWith'] | Sharing['sharedWith'][],
resourceId: Sharing['itemId'],
action: SharingActionName,
sharedWithType = SharedWithType.Individual,
Expand Down
7 changes: 7 additions & 0 deletions src/modules/workspaces/workspaces.usecase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2121,6 +2121,13 @@ export class WorkspacesUsecases {
);
}

getTeamsUserBelongsTo(
userUuid: string,
workspaceId: string,
): Promise<WorkspaceTeam[]> {
return this.teamRepository.getTeamsUserBelongsTo(userUuid, workspaceId);
}

findUserInTeam(
userUuid: string,
teamId: string,
Expand Down

0 comments on commit 6b7d161

Please sign in to comment.