Skip to content

Commit

Permalink
[Cases] Create Bulk get cases internal API (#147674)
Browse files Browse the repository at this point in the history
## Summary

This PR creates the bulk get cases internal API. The endpoint is needed
for the alerts table to be able to get all cases the alerts are attached
to with one call.

Reference: #146864

### Request

- ids: (Required, array) An array of IDs of the retrieved cases.
- fields: (Optional, array) The fields to return in the attributes key
of the object response.

```
POST <kibana host>:<port>/internal/cases/_bulk_get
{
    "ids": ["case-id-1", "case-id-2", "123", "not-authorized"],
    "fields": ["title"]
}
```

### Response
```
{
    "cases": [
     {
        "title": "case1",
        "owner": "securitySolution",
        "id": "case-id-1",
        "version": "WzIzMTU0NSwxNV0="
     },
     {
        "title": "case2",
        "owner": "observability",
        "id": "case-id-2",
        "version": "WzIzMTU0NSwxNV0="
      }
    ],
    "errors": [
        {
            "error": "Not Found",
            "message": "Saved object [cases/123] not found",
            "status": 404,
            "caseId": "123"
        },
        {
            "error": "Forbidden",
            "message": "Unauthorized to access case with owner: \"cases\"",
            "status": 403,
            "caseId": "not-authorized"
        }
    ]
}
```

### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios


### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
  • Loading branch information
cnasikas authored Jan 11, 2023
1 parent 4522e04 commit a8902e1
Show file tree
Hide file tree
Showing 23 changed files with 1,279 additions and 18 deletions.
4 changes: 4 additions & 0 deletions docs/user/security/audit-logging.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,10 @@ Refer to the corresponding {es} logs for potential write errors.
| `success` | User has accessed a case.
| `failure` | User is not authorized to access a case.

.2+| `case_bulk_get`
| `success` | User has accessed multiple cases.
| `failure` | User is not authorized to access multiple cases.

.2+| `case_resolve`
| `success` | User has accessed a case.
| `failure` | User is not authorized to access a case.
Expand Down
34 changes: 34 additions & 0 deletions x-pack/plugins/cases/common/api/cases/case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,27 @@ export const AllTagsFindRequestRt = rt.partial({

export const AllReportersFindRequestRt = AllTagsFindRequestRt;

export const CasesBulkGetRequestRt = rt.intersection([
rt.type({
ids: rt.array(rt.string),
}),
rt.partial({
fields: rt.union([rt.undefined, rt.array(rt.string), rt.string]),
}),
]);

export const CasesBulkGetResponseRt = rt.type({
cases: CasesResponseRt,
errors: rt.array(
rt.type({
error: rt.string,
message: rt.string,
status: rt.union([rt.undefined, rt.number]),
caseId: rt.string,
})
),
});

export type CaseAttributes = rt.TypeOf<typeof CaseAttributesRt>;

export type CasePostRequest = rt.TypeOf<typeof CasePostRequestRt>;
Expand All @@ -347,3 +368,16 @@ export type AllReportersFindRequest = AllTagsFindRequest;
export type AttachmentTotals = rt.TypeOf<typeof AttachmentTotalsRt>;
export type RelatedCaseInfo = rt.TypeOf<typeof RelatedCaseInfoRt>;
export type CasesByAlertId = rt.TypeOf<typeof CasesByAlertIdRt>;

export type CasesBulkGetRequest = rt.TypeOf<typeof CasesBulkGetRequestRt>;
export type CasesBulkGetResponse = rt.TypeOf<typeof CasesBulkGetResponseRt>;
export type CasesBulkGetRequestCertainFields<
Field extends keyof CaseResponse = keyof CaseResponse
> = Omit<CasesBulkGetRequest, 'fields'> & {
fields?: Field[];
};
export type CasesBulkGetResponseCertainFields<
Field extends keyof CaseResponse = keyof CaseResponse
> = Omit<CasesBulkGetResponse, 'cases'> & {
cases: Array<Pick<CaseResponse, Field | 'id' | 'version' | 'owner'>>;
};
40 changes: 35 additions & 5 deletions x-pack/plugins/cases/common/api/runtime_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const decodeOrThrow =
(inputValue: I) =>
pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity));

const getProps = (
export const getTypeProps = (
codec:
| rt.HasProps
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -51,18 +51,18 @@ const getProps = (
return codec.codomain.props;
}
const dTypes: rt.HasProps[] = codec.codomain.types;
return dTypes.reduce<rt.Props>((props, type) => Object.assign(props, getProps(type)), {});
return dTypes.reduce<rt.Props>((props, type) => Object.assign(props, getTypeProps(type)), {});
case 'RefinementType':
case 'ReadonlyType':
return getProps(codec.type);
return getTypeProps(codec.type);
case 'InterfaceType':
case 'StrictType':
case 'PartialType':
return codec.props;
case 'IntersectionType':
const iTypes = codec.types as rt.HasProps[];
return iTypes.reduce<rt.Props>((props, type) => {
return Object.assign(props, getProps(type) as rt.Props);
return Object.assign(props, getTypeProps(type) as rt.Props);
}, {} as rt.Props) as rt.Props;
default:
return null;
Expand All @@ -82,7 +82,7 @@ const getExcessProps = (props: rt.Props, r: Record<string, unknown>): string[] =
export function excess<
C extends rt.InterfaceType<rt.Props> | GenericIntersectionC | rt.PartialType<rt.Props>
>(codec: C): C {
const codecProps = getProps(codec);
const codecProps = getTypeProps(codec);

const r = new rt.InterfaceType(
codec.name,
Expand Down Expand Up @@ -123,3 +123,33 @@ export const jsonArrayRt: rt.Type<JsonArray> = rt.recursion('JsonArray', () =>
export const jsonObjectRt: rt.Type<JsonObject> = rt.recursion('JsonObject', () =>
rt.record(rt.string, jsonValueRt)
);

type Type = rt.InterfaceType<rt.Props> | GenericIntersectionC;

export const getTypeForCertainFields = (type: Type, fields: string[] = []): Type => {
if (fields.length === 0) {
return type;
}

const codecProps = getTypeProps(type) ?? {};
const typeProps: rt.Props = {};

for (const field of fields) {
if (codecProps[field]) {
typeProps[field] = codecProps[field];
}
}

return rt.type(typeProps);
};

export const getTypeForCertainFieldsFromArray = (
type: rt.ArrayType<Type>,
fields: string[] = []
): rt.ArrayType<Type> => {
if (fields.length === 0) {
return type;
}

return rt.array(getTypeForCertainFields(type.type, fields));
};
2 changes: 2 additions & 0 deletions x-pack/plugins/cases/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export const INTERNAL_BULK_CREATE_ATTACHMENTS_URL =
`${CASES_INTERNAL_URL}/{case_id}/attachments/_bulk_create` as const;
export const INTERNAL_SUGGEST_USER_PROFILES_URL =
`${CASES_INTERNAL_URL}/_suggest_user_profiles` as const;
export const INTERNAL_BULK_GET_CASES_URL = `${CASES_INTERNAL_URL}/_bulk_get` as const;

/**
* Action routes
Expand Down Expand Up @@ -136,6 +137,7 @@ export const OWNER_INFO = {
*/
export const MAX_DOCS_PER_PAGE = 10000 as const;
export const MAX_CONCURRENT_SEARCHES = 10 as const;
export const MAX_BULK_GET_CASES = 1000 as const;

/**
* Validation
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a8902e1

Please sign in to comment.