Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cases] API to return a case's connectors information #147295

Merged
merged 18 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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 @@ -61,6 +61,36 @@ const existsSchema = s.object({
})
),
});

const rangeSchema = s.object({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

range: s.recordOf(
s.string(),
s.object({
lt: s.maybe(s.string()),
lte: s.maybe(s.string()),
gt: s.maybe(s.string()),
gte: s.maybe(s.string()),
})
),
});

const termValueSchema = s.object({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

term: s.recordOf(s.string(), s.object({ value: s.string() })),
});

const nestedSchema = s.object({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nested: s.object({
path: s.string(),
query: s.object({
bool: s.object({
filter: s.arrayOf(termValueSchema),
}),
}),
}),
});

const arraySchema = s.arrayOf(s.oneOf([nestedSchema, rangeSchema]));

// TODO: it would be great if we could recursively build the schema since the aggregation have be nested
// For more details see how the types are defined in the elasticsearch javascript client:
// https://github.com/elastic/elasticsearch-js/blob/4ad5daeaf401ce8ebb28b940075e0a67e56ff9ce/src/api/typesWithBodyKey.ts#L5295
Expand All @@ -70,7 +100,7 @@ const boolSchema = s.object({
must_not: s.oneOf([termSchema, existsSchema]),
}),
s.object({
filter: s.oneOf([termSchema, existsSchema]),
filter: s.oneOf([termSchema, existsSchema, arraySchema]),
}),
]),
});
Expand Down
11 changes: 11 additions & 0 deletions x-pack/plugins/cases/common/api/connectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,15 @@ export const CaseConnectorRt = rt.intersection([
CaseUserActionConnectorRt,
]);

export const GetCaseConnectorsResponseRt = rt.record(
rt.string,
rt.intersection([
rt.type({ needsToBePushed: rt.boolean, hasBeenPushed: rt.boolean }),
rt.partial(rt.type({ latestPushDate: rt.string }).props),
CaseConnectorRt,
])
);

export type CaseUserActionConnector = rt.TypeOf<typeof CaseUserActionConnectorRt>;
export type CaseConnector = rt.TypeOf<typeof CaseConnectorRt>;
export type ConnectorTypeFields = rt.TypeOf<typeof ConnectorTypeFieldsRt>;
Expand All @@ -130,3 +139,5 @@ export type ConnectorServiceNowSIRTypeFields = rt.TypeOf<typeof ConnectorService

// we need to change these types back and forth for storing in ES (arrays overwrite, objects merge)
export type ConnectorFields = rt.TypeOf<typeof ConnectorFieldsRt>;

export type GetCaseConnectorsResponse = rt.TypeOf<typeof GetCaseConnectorsResponseRt>;
1 change: 1 addition & 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_CONNECTORS_URL = `${CASES_INTERNAL_URL}/{case_id}/_connectors` as const;
export const INTERNAL_BULK_GET_CASES_URL = `${CASES_INTERNAL_URL}/_bulk_get` as const;

/**
Expand Down

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

8 changes: 8 additions & 0 deletions x-pack/plugins/cases/server/authorization/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,14 @@ export const Operations: Record<ReadOperations | WriteOperations, OperationDetai
docType: 'user actions',
savedObjectType: CASE_USER_ACTION_SAVED_OBJECT,
},
[ReadOperations.GetConnectors]: {
ecsType: EVENT_TYPES.access,
name: ACCESS_USER_ACTION_OPERATION,
action: 'case_connectors_get',
verbs: accessVerbs,
docType: 'user actions',
savedObjectType: CASE_USER_ACTION_SAVED_OBJECT,
},
[ReadOperations.GetUserActionMetrics]: {
ecsType: EVENT_TYPES.access,
name: ACCESS_USER_ACTION_OPERATION,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/cases/server/authorization/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export enum ReadOperations {
GetReporters = 'getReporters',
FindConfigurations = 'findConfigurations',
GetUserActions = 'getUserActions',
GetConnectors = 'getConnectors',
GetAlertsAttachedToCase = 'getAlertsAttachedToCase',
GetAttachmentMetrics = 'getAttachmentMetrics',
GetCaseMetrics = 'getCaseMetrics',
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/cases/server/client/attachments/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export async function deleteAll(
concurrency: MAX_CONCURRENT_SEARCHES,
});

await userActionService.bulkCreateAttachmentDeletion({
await userActionService.creator.bulkCreateAttachmentDeletion({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All methods related to creating a user action or audit logging were moved to a new class. This class is accessible through the original user action service class via the creator public accessor method.

caseId: caseID,
attachments: comments.saved_objects.map((comment) => ({
id: comment.id,
Expand Down Expand Up @@ -150,7 +150,7 @@ export async function deleteComment(
refresh: false,
});

await userActionService.createUserAction({
await userActionService.creator.createUserAction({
type: ActionTypes.comment,
action: Actions.delete,
caseId: id,
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/cases/server/client/cases/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export const create = async (
refresh: false,
});

await userActionService.createUserAction({
await userActionService.creator.createUserAction({
type: ActionTypes.create_case,
caseId: newCase.id,
user,
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/cases/server/client/cases/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P
options: { refresh: 'wait_for' },
});

await userActionService.bulkAuditLogCaseDeletion(
await userActionService.creator.bulkAuditLogCaseDeletion(
cases.saved_objects.map((caseInfo) => caseInfo.id)
);
} catch (error) {
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/cases/server/client/cases/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ export const push = async (
]);

if (shouldMarkAsClosed) {
await userActionService.createUserAction({
await userActionService.creator.createUserAction({
type: ActionTypes.status,
payload: { status: CaseStatuses.closed },
user,
Expand All @@ -271,7 +271,7 @@ export const push = async (
}
}

await userActionService.createUserAction({
await userActionService.creator.createUserAction({
type: ActionTypes.pushed,
payload: { externalService },
user,
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/cases/server/client/cases/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ export const update = async (
];
}, [] as CaseResponse[]);

await userActionService.bulkCreateUpdateCase({
await userActionService.creator.bulkCreateUpdateCase({
originalCases: myCases.saved_objects,
updatedCases: updatedCases.saved_objects,
user,
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/cases/server/client/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
SavedObjectsClientContract,
IBasePath,
} from '@kbn/core/server';
import type { ISavedObjectsSerializer } from '@kbn/core-saved-objects-server';
import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server';
import type {
AuditLogger,
Expand Down Expand Up @@ -119,8 +120,11 @@ export class CasesClientFactory {
excludedExtensions: [SECURITY_EXTENSION_ID],
});

const savedObjectsSerializer = savedObjectsService.createSerializer();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now use top hits aggregations that return a raw elasticsearch version of a saved object document. To interact with that document we need to convert it to a saved object. This serializer does the conversion for us so we're passing it through to the user action service.


const services = this.createServices({
unsecuredSavedObjectsClient,
savedObjectsSerializer,
esClient: scopedClusterClient,
request,
auditLogger,
Expand Down Expand Up @@ -152,11 +156,13 @@ export class CasesClientFactory {

private createServices({
unsecuredSavedObjectsClient,
savedObjectsSerializer,
esClient,
request,
auditLogger,
}: {
unsecuredSavedObjectsClient: SavedObjectsClientContract;
savedObjectsSerializer: ISavedObjectsSerializer;
esClient: ElasticsearchClient;
request: KibanaRequest;
auditLogger: AuditLogger;
Expand Down Expand Up @@ -201,6 +207,7 @@ export class CasesClientFactory {
log: this.logger,
persistableStateAttachmentTypeRegistry: this.options.persistableStateAttachmentTypeRegistry,
unsecuredSavedObjectsClient,
savedObjectsSerializer,
auditLogger,
}),
attachmentService,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/cases/server/client/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type UserActionsSubClientMock = jest.Mocked<UserActionsSubClient>;
const createUserActionsSubClientMock = (): UserActionsSubClientMock => {
return {
getAll: jest.fn(),
getConnectors: jest.fn(),
};
};

Expand Down
20 changes: 8 additions & 12 deletions x-pack/plugins/cases/server/client/user_actions/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,12 @@
* 2.0.
*/

import type { GetCaseConnectorsResponse } from '../../../common/api';
import type { ICaseUserActionsResponse } from '../typedoc_interfaces';
import type { CasesClientArgs } from '../types';
import { get } from './get';

/**
* Parameters for retrieving user actions for a particular case
*/
export interface UserActionGet {
/**
* The ID of the case
*/
caseId: string;
}
import { getConnectors } from './connectors';
import type { GetConnectorsRequest, UserActionGet } from './types';

/**
* API for interacting the actions performed by a user when interacting with the cases entities.
Expand All @@ -27,16 +20,19 @@ export interface UserActionsSubClient {
* Retrieves all user actions for a particular case.
*/
getAll(clientArgs: UserActionGet): Promise<ICaseUserActionsResponse>;
/**
* Retrieves all the connectors used within a given case
*/
getConnectors(clientArgs: GetConnectorsRequest): Promise<GetCaseConnectorsResponse>;
}

/**
* Creates an API object for interacting with the user action entities
*
* @ignore
*/
export const createUserActionsSubClient = (clientArgs: CasesClientArgs): UserActionsSubClient => {
const attachmentSubClient: UserActionsSubClient = {
getAll: (params: UserActionGet) => get(params, clientArgs),
getConnectors: (params: GetConnectorsRequest) => getConnectors(params, clientArgs),
};

return Object.freeze(attachmentSubClient);
Expand Down
Loading