Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { CustomFieldTypes, CaseStatuses } from '../../../common/types/domain';
import { CustomFieldTypes, CaseStatuses, CaseSeverity } from '../../../common/types/domain';
import {
MAX_CATEGORY_LENGTH,
MAX_DESCRIPTION_LENGTH,
Expand Down Expand Up @@ -379,24 +379,64 @@ describe('update', () => {
expect(operations).toEqual([Operations.updateCase]);
});

it('returns only assignCase operation when all cases are assignee changes', () => {
it('returns only assignCase operation when all cases are assignee-only changes', () => {
const assignOnlyCases = [
{ id: mockCases[0].id, version: mockCases[0].version ?? '', assignees: [{ uid: '1' }] },
];
const operations = getOperationsToAuthorize({
reopenedCases: [],
changedAssignees: cases.cases,
allCases: cases.cases,
changedAssignees: assignOnlyCases,
allCases: assignOnlyCases,
});
expect(operations).toEqual([Operations.assignCase]);
});

it('returns only reopenCase operation when all cases are being reopened', () => {
it('returns assignCase and updateCase when an assignee-change request includes an injected title field', () => {
const assignWithTitle = [
{
id: mockCases[0].id,
version: mockCases[0].version ?? '',
assignees: [{ uid: '1' }],
title: 'injected',
},
];
const operations = getOperationsToAuthorize({
reopenedCases: cases.cases,
reopenedCases: [],
changedAssignees: assignWithTitle,
allCases: assignWithTitle,
});
expect(operations).toEqual([Operations.assignCase, Operations.updateCase]);
});

it('returns only reopenCase operation when all cases are being reopened with only status', () => {
const statusOnlyCases = [
{ id: mockCases[0].id, version: mockCases[0].version ?? '', status: CaseStatuses.open },
];
const operations = getOperationsToAuthorize({
reopenedCases: statusOnlyCases,
changedAssignees: [],
allCases: cases.cases,
allCases: statusOnlyCases,
});
expect(operations).toEqual([Operations.reopenCase]);
});

it('returns reopenCase and updateCase when a reopened case includes assignees', () => {
const reopenWithAssignees = [
{
id: mockCases[0].id,
version: mockCases[0].version ?? '',
status: CaseStatuses.open,
assignees: [{ uid: '1' }],
},
];
const operations = getOperationsToAuthorize({
reopenedCases: reopenWithAssignees,
changedAssignees: [],
allCases: reopenWithAssignees,
});
expect(operations).toEqual([Operations.reopenCase, Operations.updateCase]);
});

it('returns assignCase and updateCase when some cases have non-assignee changes', () => {
const case2 = { id: 'case-2', version: '1' };
const operations = getOperationsToAuthorize({
Expand Down Expand Up @@ -454,6 +494,57 @@ describe('update', () => {
]);
});

it('returns reopenCase and updateCase when a reopened case has an injected title field', () => {
const reopenWithTitle = [
{
id: mockCases[0].id,
version: mockCases[0].version ?? '',
status: CaseStatuses.open,
title: 'injected',
},
];
const operations = getOperationsToAuthorize({
reopenedCases: reopenWithTitle,
changedAssignees: [],
allCases: reopenWithTitle,
});
expect(operations).toEqual([Operations.reopenCase, Operations.updateCase]);
});

it('returns reopenCase and updateCase when a reopened case has an injected description field', () => {
const reopenWithDescription = [
{
id: mockCases[0].id,
version: mockCases[0].version ?? '',
status: CaseStatuses.open,
description: 'injected',
},
];
const operations = getOperationsToAuthorize({
reopenedCases: reopenWithDescription,
changedAssignees: [],
allCases: reopenWithDescription,
});
expect(operations).toEqual([Operations.reopenCase, Operations.updateCase]);
});

it('returns reopenCase and updateCase when a reopened case has an injected severity field', () => {
const reopenWithSeverity = [
{
id: mockCases[0].id,
version: mockCases[0].version ?? '',
status: CaseStatuses.open,
severity: CaseSeverity.CRITICAL,
},
];
const operations = getOperationsToAuthorize({
reopenedCases: reopenWithSeverity,
changedAssignees: [],
allCases: reopenWithSeverity,
});
expect(operations).toEqual([Operations.reopenCase, Operations.updateCase]);
});

it('should filter out empty user profiles', async () => {
const casesWithEmptyAssignee = {
cases: [
Expand Down Expand Up @@ -1944,6 +2035,89 @@ describe('update', () => {
});
});

it('checks authorization for reopenCase and updateCase when reopening with extra fields', async () => {
const closedCase = {
...mockCases[0],
attributes: {
...mockCases[0].attributes,
status: CaseStatuses.closed,
},
};

clientArgs.services.caseService.getCases.mockResolvedValue({ saved_objects: [closedCase] });

clientArgs.services.caseService.patchCases.mockResolvedValue({
saved_objects: [{ ...closedCase }],
});

await bulkUpdate(
{
cases: [
{
id: closedCase.id,
version: closedCase.version ?? '',
status: CaseStatuses.open,
title: 'injected title',
},
],
},
clientArgs,
casesClientMock
);

expect(clientArgs.authorization.ensureAuthorized).toHaveBeenCalledWith({
entities: [{ id: closedCase.id, owner: closedCase.attributes.owner }],
operation: [Operations.reopenCase, Operations.updateCase],
});
});

it('throws when a reopen request contains an injected title and the user lacks updateCase permission', async () => {
const closedCase = {
...mockCases[0],
attributes: { ...mockCases[0].attributes, status: CaseStatuses.closed },
};
clientArgs.services.caseService.getCases.mockResolvedValue({ saved_objects: [closedCase] });
clientArgs.authorization.ensureAuthorized.mockRejectedValue(new Error('Unauthorized'));

await expect(
bulkUpdate(
{
cases: [
{
id: closedCase.id,
version: closedCase.version ?? '',
status: CaseStatuses.open,
title: 'injected title',
},
],
},
clientArgs,
casesClientMock
)
).rejects.toThrow('Unauthorized');
});

it('throws when an assignee-change request contains an injected title and the user lacks updateCase permission', async () => {
clientArgs.authorization.ensureAuthorized.mockRejectedValue(new Error('Unauthorized'));

await expect(
bulkUpdate(
{
cases: [
{
id: mockCases[0].id,
version: mockCases[0].version ?? '',
assignees: [{ uid: '1' }],
title: 'injected title',
},
],
},
clientArgs,
casesClientMock
)
).rejects.toThrow('Unauthorized');
});

it('throws when user is not authorized to update case', async () => {
const error = new Error('Unauthorized');
clientArgs.authorization.ensureAuthorized.mockRejectedValue(error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,16 @@ function partitionPatchRequest(
};
}

/**
* Fields that are allowed to be present when users reopen cases
*/
const REOPEN_ONLY_CASE_FIELDS = new Set(['id', 'version', 'status']);

/**
* Fields that are allowed to be present when case is reassigned
*/
const ASSIGN_ONLY_CASE_FIELDS = new Set(['id', 'version', 'assignees']);

export function getOperationsToAuthorize({
reopenedCases,
changedAssignees,
Expand All @@ -407,9 +417,17 @@ export function getOperationsToAuthorize({
}): OperationDetails[] {
const operations: OperationDetails[] = [];
const onlyAssigneeOperations =
reopenedCases.length === 0 && changedAssignees.length === allCases.length;
reopenedCases.length === 0 &&
changedAssignees.length === allCases.length &&
changedAssignees.every((caseReq) =>
Object.keys(caseReq).every((key) => ASSIGN_ONLY_CASE_FIELDS.has(key))
);
const onlyReopenOperations =
changedAssignees.length === 0 && reopenedCases.length === allCases.length;
changedAssignees.length === 0 &&
reopenedCases.length === allCases.length &&
reopenedCases.every((caseReq) =>
Object.keys(caseReq).every((key) => REOPEN_ONLY_CASE_FIELDS.has(key))
);

if (reopenedCases.length > 0) {
operations.push(Operations.reopenCase);
Expand Down
Loading