-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR adds a new API for deleting a file within a case given the file id. This API will retrieve the file saved object provided in the query and perform an authorization check using each file's file kind. It will also retrieve all the attachments associated with the files and perform an authorization check for each attachment. This api supports calling it with ids that only have the file saved objects and not the corresponding attachments. For the deletion sub privilege to work correctly, it must have access to updating the file saved objects. Therefore we also had to give the delete sub privilege all access to the file saved objects types. This PR does not contain the logic for deleting all files when a case is deleted. That'll be completed in a separate PR. Example request ``` POST /internal/cases/a58847c0-cccc-11ed-b071-4f11aa24310c/attachments/files/_bulk_delete { "ids": ["clfr5sdky0001n811gjot7tv5", "clfr5sgru0002n8112t54bave"] } ``` Example response ``` 204 ``` Notable changes - Refactored the delete all comments to leverage the bulk delete API from the saved object client - Updated the names of the `api_integration` users and roles to avoid clashing with the ones in `cases_api_integration` --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
- Loading branch information
1 parent
9c67d83
commit 1e63515
Showing
54 changed files
with
2,228 additions
and
201 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { | ||
constructFileKindIdByOwner, | ||
constructFilesHttpOperationTag, | ||
constructOwnerFromFileKind, | ||
} from '.'; | ||
import { APP_ID, OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER } from '../constants'; | ||
import { HttpApiTagOperation } from '../constants/types'; | ||
|
||
describe('files index', () => { | ||
describe('constructFilesHttpOperationTag', () => { | ||
it.each([ | ||
[SECURITY_SOLUTION_OWNER, HttpApiTagOperation.Read, 'securitySolutionFilesCasesRead'], | ||
[OBSERVABILITY_OWNER, HttpApiTagOperation.Create, 'observabilityFilesCasesCreate'], | ||
[APP_ID, HttpApiTagOperation.Delete, 'casesFilesCasesDelete'], | ||
])('builds the tag for owner: %p operation: %p tag: %p', (owner, operation, tag) => { | ||
expect(constructFilesHttpOperationTag(owner, operation)).toEqual(tag); | ||
}); | ||
}); | ||
|
||
describe('constructFileKindIdByOwner', () => { | ||
it.each([ | ||
[SECURITY_SOLUTION_OWNER, 'securitySolutionFilesCases'], | ||
[OBSERVABILITY_OWNER, 'observabilityFilesCases'], | ||
[APP_ID, 'casesFilesCases'], | ||
])('builds the right file kind for owner: %p file kind: %p', (owner, fileKind) => { | ||
expect(constructFileKindIdByOwner(owner)).toEqual(fileKind); | ||
}); | ||
}); | ||
|
||
describe('constructOwnerFromFileKind', () => { | ||
it('returns undefined when the delimiter cannot be found with an empty string', () => { | ||
expect(constructOwnerFromFileKind('')).toBeUndefined(); | ||
}); | ||
|
||
it('returns undefined when the delimiter cannot be found in a non-empty string', () => { | ||
expect(constructOwnerFromFileKind('abc')).toBeUndefined(); | ||
}); | ||
|
||
it('returns undefined when the extract owner is not part of the valid owners array', () => { | ||
expect(constructOwnerFromFileKind('abcFilesCases')).toBeUndefined(); | ||
}); | ||
|
||
it('returns undefined when there is a string after the delimiter', () => { | ||
expect(constructOwnerFromFileKind('securitySolutionFilesCasesAbc')).toBeUndefined(); | ||
}); | ||
|
||
it('returns securitySolution when given the security solution file kind', () => { | ||
expect(constructOwnerFromFileKind('securitySolutionFilesCases')).toEqual('securitySolution'); | ||
}); | ||
|
||
it('returns observability when given the observability file kind', () => { | ||
expect(constructOwnerFromFileKind('observabilityFilesCases')).toEqual('observability'); | ||
}); | ||
|
||
it('returns cases when given the cases file kind', () => { | ||
expect(constructOwnerFromFileKind('casesFilesCases')).toEqual('cases'); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import * as rt from 'io-ts'; | ||
import { isEmpty } from 'lodash'; | ||
import { OWNERS } from '../constants'; | ||
import type { HttpApiTagOperation, Owner } from '../constants/types'; | ||
|
||
/** | ||
* This type is only used to validate for deletion, it does not check all the fields that should exist in the file | ||
* metadata. | ||
*/ | ||
export const CaseFileMetadataForDeletionRt = rt.type({ | ||
caseIds: rt.array(rt.string), | ||
}); | ||
|
||
export type CaseFileMetadata = rt.TypeOf<typeof CaseFileMetadataForDeletionRt>; | ||
|
||
const FILE_KIND_DELIMITER = 'FilesCases'; | ||
|
||
export const constructFilesHttpOperationTag = (owner: Owner, operation: HttpApiTagOperation) => { | ||
return `${owner}${FILE_KIND_DELIMITER}${operation}`; | ||
}; | ||
|
||
export const constructFileKindIdByOwner = (owner: Owner) => `${owner}${FILE_KIND_DELIMITER}`; | ||
|
||
export const constructOwnerFromFileKind = (fileKind: string): Owner | undefined => { | ||
const splitString = fileKind.split(FILE_KIND_DELIMITER); | ||
|
||
if (splitString.length === 2 && isEmpty(splitString[1]) && isValidOwner(splitString[0])) { | ||
return splitString[0]; | ||
} | ||
}; | ||
|
||
const isValidOwner = (ownerToValidate: string): ownerToValidate is Owner => { | ||
const foundOwner = OWNERS.find((validOwner) => validOwner === ownerToValidate); | ||
|
||
return foundOwner !== undefined; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { PathReporter } from 'io-ts/lib/PathReporter'; | ||
|
||
import { limitedArraySchema, NonEmptyString } from '.'; | ||
|
||
describe('schema', () => { | ||
it('fails when given an empty string', () => { | ||
expect(PathReporter.report(limitedArraySchema(NonEmptyString, 1, 1).decode(['']))) | ||
.toMatchInlineSnapshot(` | ||
Array [ | ||
"string must have length >= 1", | ||
] | ||
`); | ||
}); | ||
|
||
it('fails when given an empty array', () => { | ||
expect(PathReporter.report(limitedArraySchema(NonEmptyString, 1, 1).decode([]))) | ||
.toMatchInlineSnapshot(` | ||
Array [ | ||
"array must be of length >= 1", | ||
] | ||
`); | ||
}); | ||
|
||
it('fails when given an array larger than the limit of one item', () => { | ||
expect(PathReporter.report(limitedArraySchema(NonEmptyString, 1, 1).decode(['a', 'b']))) | ||
.toMatchInlineSnapshot(` | ||
Array [ | ||
"array must be of length <= 1", | ||
] | ||
`); | ||
}); | ||
|
||
it('succeeds when given an array of 1 item with a non-empty string', () => { | ||
expect(PathReporter.report(limitedArraySchema(NonEmptyString, 1, 1).decode(['a']))) | ||
.toMatchInlineSnapshot(` | ||
Array [ | ||
"No errors!", | ||
] | ||
`); | ||
}); | ||
|
||
it('succeeds when given an array of 0 item with a non-empty string when the min is 0', () => { | ||
expect(PathReporter.report(limitedArraySchema(NonEmptyString, 0, 2).decode([]))) | ||
.toMatchInlineSnapshot(` | ||
Array [ | ||
"No errors!", | ||
] | ||
`); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import * as rt from 'io-ts'; | ||
import { either } from 'fp-ts/lib/Either'; | ||
|
||
export const NonEmptyString = new rt.Type<string, string, unknown>( | ||
'NonEmptyString', | ||
rt.string.is, | ||
(input, context) => | ||
either.chain(rt.string.validate(input, context), (s) => { | ||
if (s.trim() !== '') { | ||
return rt.success(s); | ||
} else { | ||
return rt.failure(input, context, 'string must have length >= 1'); | ||
} | ||
}), | ||
rt.identity | ||
); | ||
|
||
export const limitedArraySchema = <T extends rt.Mixed>(codec: T, min: number, max: number) => | ||
new rt.Type<Array<rt.TypeOf<typeof codec>>, Array<rt.TypeOf<typeof codec>>, unknown>( | ||
'LimitedArray', | ||
(input): input is T[] => rt.array(codec).is(input), | ||
(input, context) => | ||
either.chain(rt.array(codec).validate(input, context), (s) => { | ||
if (s.length < min) { | ||
return rt.failure(input, context, `array must be of length >= ${min}`); | ||
} | ||
|
||
if (s.length > max) { | ||
return rt.failure(input, context, `array must be of length <= ${max}`); | ||
} | ||
|
||
return rt.success(s); | ||
}), | ||
rt.identity | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.