[ResponseOps][Cases] Refactoring client args and authentication (#137345)

* Refactoring services, auth

* Fixing type errors
This commit is contained in:
Jonathan Buttner 2022-07-29 08:06:06 -04:00 committed by GitHub
parent 4f77f768bd
commit 724f9264c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 329 additions and 154 deletions

View file

@ -11,10 +11,9 @@
"kibanaVersion":"kibana",
"optionalPlugins":[
"home",
"security",
"spaces",
"taskManager",
"usageCollection"
"security",
"taskManager",
"usageCollection"
],
"owner":{
"githubTeam":"response-ops",
@ -30,7 +29,8 @@
"kibanaReact",
"kibanaUtils",
"triggersActionsUi",
"management"
"management",
"spaces"
],
"requiredBundles": [
"savedObjects"

View file

@ -31,11 +31,11 @@ export class AuthorizationAuditLogger {
*/
private static createAuditMsg({ operation, error, entity }: CreateAuditMsgParams): AuditEvent {
const doc =
entity !== undefined
entity?.id !== undefined
? `${operation.savedObjectType} [id=${entity.id}]`
: `a ${operation.docType}`;
const ownerText = entity === undefined ? 'as any owners' : `as owner "${entity.owner}"`;
const ownerText = entity?.owner === undefined ? 'as any owners' : `as owner "${entity.owner}"`;
let message: string;
let outcome: EcsEventOutcome;
@ -59,7 +59,7 @@ export class AuthorizationAuditLogger {
type: [operation.ecsType],
outcome,
},
...(entity !== undefined && {
...(entity?.id !== undefined && {
kibana: {
saved_object: { type: operation.savedObjectType, id: entity.id },
},

View file

@ -9,7 +9,8 @@ import { securityMock } from '@kbn/security-plugin/server/mocks';
import { httpServerMock, loggingSystemMock } from '@kbn/core/server/mocks';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import { Authorization, Operations } from '.';
import { Space } from '@kbn/spaces-plugin/server';
import { Space, SpacesPluginStart } from '@kbn/spaces-plugin/server';
import { spacesMock } from '@kbn/spaces-plugin/server/mocks';
import { AuthorizationAuditLogger } from './audit_logger';
import { KibanaRequest } from '@kbn/core/server';
import { KibanaFeature } from '@kbn/features-plugin/common';
@ -17,6 +18,17 @@ import { AuditLogger, SecurityPluginStart } from '@kbn/security-plugin/server';
import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks';
import { PluginStartContract as FeaturesPluginStart } from '@kbn/features-plugin/server';
const createSpacesDisabledFeaturesMock = (disabledFeatures: string[] = []) => {
const spacesStart: jest.Mocked<SpacesPluginStart> = spacesMock.createStart();
(spacesStart.spacesService.getActiveSpace as jest.Mock).mockImplementation(async () => {
return {
disabledFeatures: [],
};
});
return spacesStart;
};
describe('authorization', () => {
let request: KibanaRequest;
let mockLogger: jest.Mocked<AuditLogger>;
@ -29,9 +41,12 @@ describe('authorization', () => {
describe('create', () => {
let securityStart: jest.Mocked<SecurityPluginStart>;
let featuresStart: jest.Mocked<FeaturesPluginStart>;
let spacesStart: jest.Mocked<SpacesPluginStart>;
beforeEach(() => {
securityStart = securityMock.createStart();
spacesStart = createSpacesDisabledFeaturesMock();
featuresStart = featuresPluginMock.createStart();
featuresStart.getKibanaFeatures.mockReturnValue([
{ id: '1', cases: ['a'] },
@ -41,11 +56,10 @@ describe('authorization', () => {
it('creates an Authorization object', async () => {
expect.assertions(2);
const getSpace = jest.fn();
const authPromise = Authorization.create({
request,
securityAuth: securityStart.authz,
getSpace,
spaces: spacesStart,
features: featuresStart,
auditLogger: new AuthorizationAuditLogger(),
logger: loggingSystemMock.createLogger(),
@ -58,14 +72,14 @@ describe('authorization', () => {
it('throws and error when a failure occurs', async () => {
expect.assertions(1);
const getSpace = jest.fn(async () => {
(spacesStart.spacesService.getActiveSpace as jest.Mock).mockImplementation(() => {
throw new Error('space error');
});
const authPromise = Authorization.create({
request,
securityAuth: securityStart.authz,
getSpace,
spaces: spacesStart,
features: featuresStart,
auditLogger: new AuthorizationAuditLogger(),
logger: loggingSystemMock.createLogger(),
@ -80,6 +94,7 @@ describe('authorization', () => {
let securityStart: ReturnType<typeof securityMock.createStart>;
let featuresStart: jest.Mocked<FeaturesPluginStart>;
let spacesStart: jest.Mocked<SpacesPluginStart>;
let auth: Authorization;
beforeEach(async () => {
@ -92,10 +107,12 @@ describe('authorization', () => {
featuresStart = featuresPluginMock.createStart();
featuresStart.getKibanaFeatures.mockReturnValue([feature] as unknown as KibanaFeature[]);
spacesStart = createSpacesDisabledFeaturesMock();
auth = await Authorization.create({
request,
securityAuth: securityStart.authz,
getSpace: jest.fn(),
spaces: spacesStart,
features: featuresStart,
auditLogger: new AuthorizationAuditLogger(mockLogger),
logger: loggingSystemMock.createLogger(),
@ -121,7 +138,7 @@ describe('authorization', () => {
auth = await Authorization.create({
request,
getSpace: jest.fn(),
spaces: spacesStart,
features: featuresStart,
auditLogger: new AuthorizationAuditLogger(),
logger: loggingSystemMock.createLogger(),
@ -240,10 +257,14 @@ describe('authorization', () => {
it('throws an error when owner does not exist because it was from a disabled plugin', async () => {
expect.assertions(1);
(spacesStart.spacesService.getActiveSpace as jest.Mock).mockImplementation(() => {
return { disabledFeatures: [feature.id] } as Space;
});
auth = await Authorization.create({
request,
securityAuth: securityStart.authz,
getSpace: jest.fn(async () => ({ disabledFeatures: [feature.id] } as Space)),
spaces: spacesStart,
features: featuresStart,
auditLogger: new AuthorizationAuditLogger(),
logger: loggingSystemMock.createLogger(),
@ -272,7 +293,7 @@ describe('authorization', () => {
auth = await Authorization.create({
request,
securityAuth: securityStart.authz,
getSpace: jest.fn(),
spaces: spacesStart,
features: featuresStart,
auditLogger: new AuthorizationAuditLogger(),
logger: loggingSystemMock.createLogger(),
@ -299,7 +320,7 @@ describe('authorization', () => {
auth = await Authorization.create({
request,
securityAuth: securityStart.authz,
getSpace: jest.fn(),
spaces: spacesStart,
features: featuresStart,
auditLogger: new AuthorizationAuditLogger(),
logger: loggingSystemMock.createLogger(),
@ -326,7 +347,7 @@ describe('authorization', () => {
auth = await Authorization.create({
request,
securityAuth: securityStart.authz,
getSpace: jest.fn(),
spaces: spacesStart,
features: featuresStart,
auditLogger: new AuthorizationAuditLogger(mockLogger),
logger: loggingSystemMock.createLogger(),
@ -396,6 +417,7 @@ describe('authorization', () => {
let securityStart: ReturnType<typeof securityMock.createStart>;
let featuresStart: jest.Mocked<FeaturesPluginStart>;
let spacesStart: jest.Mocked<SpacesPluginStart>;
let auth: Authorization;
beforeEach(async () => {
@ -412,10 +434,12 @@ describe('authorization', () => {
featuresStart = featuresPluginMock.createStart();
featuresStart.getKibanaFeatures.mockReturnValue([feature] as unknown as KibanaFeature[]);
spacesStart = createSpacesDisabledFeaturesMock();
auth = await Authorization.create({
request,
securityAuth: securityStart.authz,
getSpace: jest.fn(),
spaces: spacesStart,
features: featuresStart,
auditLogger: new AuthorizationAuditLogger(mockLogger),
logger: loggingSystemMock.createLogger(),
@ -430,7 +454,7 @@ describe('authorization', () => {
auth = await Authorization.create({
request,
securityAuth: securityStart.authz,
getSpace: jest.fn(),
spaces: spacesStart,
features: featuresStart,
auditLogger: new AuthorizationAuditLogger(mockLogger),
logger: loggingSystemMock.createLogger(),
@ -472,7 +496,7 @@ describe('authorization', () => {
auth = await Authorization.create({
request,
getSpace: jest.fn(),
spaces: spacesStart,
features: featuresStart,
auditLogger: new AuthorizationAuditLogger(mockLogger),
logger: loggingSystemMock.createLogger(),
@ -742,7 +766,7 @@ describe('authorization', () => {
auth = await Authorization.create({
request,
securityAuth: securityStart.authz,
getSpace: jest.fn(),
spaces: spacesStart,
features: featuresStart,
auditLogger: new AuthorizationAuditLogger(mockLogger),
logger: loggingSystemMock.createLogger(),

View file

@ -9,7 +9,8 @@ import { KibanaRequest, Logger } from '@kbn/core/server';
import Boom from '@hapi/boom';
import { SecurityPluginStart } from '@kbn/security-plugin/server';
import { PluginStartContract as FeaturesPluginStart } from '@kbn/features-plugin/server';
import { AuthFilterHelpers, GetSpaceFn, OwnerEntity } from './types';
import { Space, SpacesPluginStart } from '@kbn/spaces-plugin/server';
import { AuthFilterHelpers, OwnerEntity } from './types';
import { getOwnersFilter } from './utils';
import { AuthorizationAuditLogger, OperationDetails } from '.';
import { createCaseError } from '../common/error';
@ -47,22 +48,26 @@ export class Authorization {
static async create({
request,
securityAuth,
getSpace,
spaces,
features,
auditLogger,
logger,
}: {
request: KibanaRequest;
securityAuth?: SecurityPluginStart['authz'];
getSpace: GetSpaceFn;
spaces: SpacesPluginStart;
features: FeaturesPluginStart;
auditLogger: AuthorizationAuditLogger;
logger: Logger;
}): Promise<Authorization> {
const getSpace = async (): Promise<Space> => {
return spaces.spacesService.getActiveSpace(request);
};
// Since we need to do async operations, this static method handles that before creating the Auth class
let caseOwners: Set<string>;
try {
const disabledFeatures = new Set((await getSpace(request))?.disabledFeatures ?? []);
const disabledFeatures = new Set((await getSpace()).disabledFeatures ?? []);
caseOwners = new Set(
features
@ -87,7 +92,7 @@ export class Authorization {
}
/**
* Checks that the user making the request for the passed in owners and operation has the correct authorization. This
* Checks that the user making the request for the passed in owner, saved object, and operation has the correct authorization. This
* function will throw if the user is not authorized for the requested operation and owners.
*
* @param entities an array of entities describing the case owners in conjunction with the saved object ID attempting

View file

@ -5,9 +5,8 @@
* 2.0.
*/
import { EcsEventType, KibanaRequest } from '@kbn/core/server';
import { EcsEventType } from '@kbn/core/server';
import type { KueryNode } from '@kbn/es-query';
import { Space } from '@kbn/spaces-plugin/server';
import { CasesSupportedOperations } from '@kbn/security-plugin/server';
/**
@ -19,8 +18,6 @@ export interface Verbs {
past: string;
}
export type GetSpaceFn = (request: KibanaRequest) => Promise<Space | undefined>;
/**
* Read operations for the cases APIs.
*
@ -80,7 +77,7 @@ export interface OperationDetails {
*/
name: CasesSupportedOperations;
/**
* The ECS `event.action` field, should be in the form of <entity>_<operation> e.g comment_get, case_fined
* The ECS `event.action` field, should be in the form of <entity>_<operation> e.g comment_get, case_find
*/
action: string;
/**

View file

@ -39,14 +39,14 @@ describe('getAlerts', () => {
esClient.mget.mockResolvedValue({ docs });
it('returns an empty array if the alert info are empty', async () => {
const clientArgs = { alertsService } as unknown as CasesClientArgs;
const clientArgs = { services: { alertsService } } as unknown as CasesClientArgs;
const res = await getAlerts([], clientArgs);
expect(res).toEqual([]);
});
it('returns the alerts correctly', async () => {
const clientArgs = { alertsService } as unknown as CasesClientArgs;
const clientArgs = { services: { alertsService } } as unknown as CasesClientArgs;
const res = await getAlerts(
[
{
@ -79,7 +79,7 @@ describe('getAlerts', () => {
},
],
});
const clientArgs = { alertsService } as unknown as CasesClientArgs;
const clientArgs = { services: { alertsService } } as unknown as CasesClientArgs;
const res = await getAlerts(
[
@ -113,7 +113,7 @@ describe('getAlerts', () => {
},
],
});
const clientArgs = { alertsService } as unknown as CasesClientArgs;
const clientArgs = { services: { alertsService } } as unknown as CasesClientArgs;
const res = await getAlerts(
[

View file

@ -21,7 +21,7 @@ export const getAlerts = async (
alertsInfo: AlertInfo[],
clientArgs: CasesClientArgs
): Promise<CasesClientGetAlertsResponse> => {
const { alertsService } = clientArgs;
const { alertsService } = clientArgs.services;
if (alertsInfo.length === 0) {
return [];
}

View file

@ -51,9 +51,7 @@ export async function deleteAll(
const {
user,
unsecuredSavedObjectsClient,
caseService,
attachmentService,
userActionService,
services: { caseService, attachmentService, userActionService },
logger,
authorization,
} = clientArgs;
@ -118,8 +116,7 @@ export async function deleteComment(
const {
user,
unsecuredSavedObjectsClient,
attachmentService,
userActionService,
services: { attachmentService, userActionService },
logger,
authorization,
} = clientArgs;

View file

@ -102,7 +102,12 @@ export const getAllAlertsAttachToCase = async (
clientArgs: CasesClientArgs,
casesClient: CasesClient
): Promise<AlertResponse> => {
const { unsecuredSavedObjectsClient, authorization, attachmentService, logger } = clientArgs;
const {
unsecuredSavedObjectsClient,
authorization,
services: { attachmentService },
logger,
} = clientArgs;
try {
// This will perform an authorization check to ensure the user has access to the parent case
@ -146,7 +151,12 @@ export async function find(
{ caseID, queryParams }: FindArgs,
clientArgs: CasesClientArgs
): Promise<CommentsResponse> {
const { unsecuredSavedObjectsClient, caseService, logger, authorization } = clientArgs;
const {
unsecuredSavedObjectsClient,
services: { caseService },
logger,
authorization,
} = clientArgs;
try {
const { filter: authorizationFilter, ensureSavedObjectsAreAuthorized } =
@ -218,7 +228,12 @@ export async function get(
{ attachmentID, caseID }: GetArgs,
clientArgs: CasesClientArgs
): Promise<CommentResponse> {
const { attachmentService, unsecuredSavedObjectsClient, logger, authorization } = clientArgs;
const {
services: { attachmentService },
unsecuredSavedObjectsClient,
logger,
authorization,
} = clientArgs;
try {
const comment = await attachmentService.get({
@ -250,7 +265,11 @@ export async function getAll(
{ caseID }: GetAllArgs,
clientArgs: CasesClientArgs
): Promise<AllCommentsResponse> {
const { caseService, logger, authorization } = clientArgs;
const {
services: { caseService },
logger,
authorization,
} = clientArgs;
try {
const { filter, ensureSavedObjectsAreAuthorized } = await authorization.getAuthorizationFilter(

View file

@ -39,7 +39,12 @@ export async function update(
{ caseID, updateRequest: queryParams }: UpdateArgs,
clientArgs: CasesClientArgs
): Promise<CaseResponse> {
const { attachmentService, unsecuredSavedObjectsClient, logger, authorization } = clientArgs;
const {
services: { attachmentService },
unsecuredSavedObjectsClient,
logger,
authorization,
} = clientArgs;
try {
const {

View file

@ -41,8 +41,7 @@ export const create = async (
): Promise<CaseResponse> => {
const {
unsecuredSavedObjectsClient,
caseService,
userActionService,
services: { caseService, userActionService },
user,
logger,
authorization: auth,

View file

@ -22,10 +22,8 @@ import { Operations, OwnerEntity } from '../../authorization';
export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): Promise<void> {
const {
unsecuredSavedObjectsClient,
caseService,
attachmentService,
user,
userActionService,
services: { caseService, attachmentService, userActionService },
logger,
authorization,
} = clientArgs;

View file

@ -36,7 +36,11 @@ export const find = async (
params: CasesFindRequest,
clientArgs: CasesClientArgs
): Promise<CasesFindResponse> => {
const { caseService, authorization, logger } = clientArgs;
const {
services: { caseService },
authorization,
logger,
} = clientArgs;
try {
const fields = asArray(params.fields);

View file

@ -59,7 +59,11 @@ export const getCasesByAlertID = async (
{ alertID, options }: CasesByAlertIDParams,
clientArgs: CasesClientArgs
): Promise<CasesByAlertId> => {
const { caseService, logger, authorization } = clientArgs;
const {
services: { caseService },
logger,
authorization,
} = clientArgs;
try {
const queryParams = pipe(
@ -155,7 +159,11 @@ export const get = async (
{ id, includeComments }: GetParams,
clientArgs: CasesClientArgs
): Promise<CaseResponse> => {
const { caseService, logger, authorization } = clientArgs;
const {
services: { caseService },
logger,
authorization,
} = clientArgs;
try {
const theCase: SavedObject<CaseAttributes> = await caseService.getCase({
@ -205,7 +213,11 @@ export const resolve = async (
{ id, includeComments }: GetParams,
clientArgs: CasesClientArgs
): Promise<CaseResolveResponse> => {
const { caseService, logger, authorization } = clientArgs;
const {
services: { caseService },
logger,
authorization,
} = clientArgs;
try {
const {
@ -264,7 +276,12 @@ export async function getTags(
params: AllTagsFindRequest,
clientArgs: CasesClientArgs
): Promise<string[]> {
const { unsecuredSavedObjectsClient, caseService, logger, authorization } = clientArgs;
const {
unsecuredSavedObjectsClient,
services: { caseService },
logger,
authorization,
} = clientArgs;
try {
const queryParams = pipe(
@ -296,7 +313,12 @@ export async function getReporters(
params: AllReportersFindRequest,
clientArgs: CasesClientArgs
): Promise<User[]> {
const { unsecuredSavedObjectsClient, caseService, logger, authorization } = clientArgs;
const {
unsecuredSavedObjectsClient,
services: { caseService },
logger,
authorization,
} = clientArgs;
try {
const queryParams = pipe(

View file

@ -50,8 +50,8 @@ function shouldCloseByPush(
const changeAlertsStatusToClose = async (
caseId: string,
caseService: CasesClientArgs['caseService'],
alertsService: CasesClientArgs['alertsService']
caseService: CasesClientArgs['services']['caseService'],
alertsService: CasesClientArgs['services']['alertsService']
) => {
const alertAttachments = (await caseService.getAllCaseComments({
id: [caseId],
@ -99,11 +99,13 @@ export const push = async (
): Promise<CaseResponse> => {
const {
unsecuredSavedObjectsClient,
attachmentService,
caseService,
caseConfigureService,
userActionService,
alertsService,
services: {
attachmentService,
caseService,
caseConfigureService,
userActionService,
alertsService,
},
actionsClient,
user,
logger,

View file

@ -236,12 +236,10 @@ export const update = async (
): Promise<CasesResponse> => {
const {
unsecuredSavedObjectsClient,
caseService,
userActionService,
services: { caseService, userActionService, alertsService },
user,
logger,
authorization,
alertsService,
} = clientArgs;
const query = pipe(
excess(CasesPatchRequestRt).decode(cases),

View file

@ -132,7 +132,12 @@ async function get(
clientArgs: CasesClientArgs,
casesClientInternal: CasesClientInternal
): Promise<CasesConfigurationsResponse> {
const { unsecuredSavedObjectsClient, caseConfigureService, logger, authorization } = clientArgs;
const {
unsecuredSavedObjectsClient,
services: { caseConfigureService },
logger,
authorization,
} = clientArgs;
try {
const queryParams = pipe(
excess(GetConfigureFindRequestRt).decode(params),
@ -234,8 +239,13 @@ async function update(
clientArgs: CasesClientArgs,
casesClientInternal: CasesClientInternal
): Promise<CasesConfigureResponse> {
const { caseConfigureService, logger, unsecuredSavedObjectsClient, user, authorization } =
clientArgs;
const {
services: { caseConfigureService },
logger,
unsecuredSavedObjectsClient,
user,
authorization,
} = clientArgs;
try {
const request = pipe(
@ -343,8 +353,13 @@ async function create(
clientArgs: CasesClientArgs,
casesClientInternal: CasesClientInternal
): Promise<CasesConfigureResponse> {
const { unsecuredSavedObjectsClient, caseConfigureService, logger, user, authorization } =
clientArgs;
const {
unsecuredSavedObjectsClient,
services: { caseConfigureService },
logger,
user,
authorization,
} = clientArgs;
try {
let error = null;

View file

@ -16,7 +16,11 @@ export const createMappings = async (
{ connector, owner, refresh }: CreateMappingsArgs,
clientArgs: CasesClientArgs
): Promise<ConnectorMappingsAttributes[]> => {
const { unsecuredSavedObjectsClient, connectorMappingsService, logger } = clientArgs;
const {
unsecuredSavedObjectsClient,
services: { connectorMappingsService },
logger,
} = clientArgs;
try {
const mappings = casesConnectors.get(connector.type)?.getMapping() ?? [];

View file

@ -16,7 +16,11 @@ export const getMappings = async (
{ connector }: MappingsArgs,
clientArgs: CasesClientArgs
): Promise<SavedObjectsFindResponse<ConnectorMappings>['saved_objects']> => {
const { unsecuredSavedObjectsClient, connectorMappingsService, logger } = clientArgs;
const {
unsecuredSavedObjectsClient,
services: { connectorMappingsService },
logger,
} = clientArgs;
try {
const myConnectorMappings = await connectorMappingsService.find({

View file

@ -16,7 +16,11 @@ export const updateMappings = async (
{ connector, mappingId, refresh }: UpdateMappingsArgs,
clientArgs: CasesClientArgs
): Promise<ConnectorMappingsAttributes[]> => {
const { unsecuredSavedObjectsClient, connectorMappingsService, logger } = clientArgs;
const {
unsecuredSavedObjectsClient,
services: { connectorMappingsService },
logger,
} = clientArgs;
try {
const mappings = casesConnectors.get(connector.type)?.getMapping() ?? [];

View file

@ -10,14 +10,15 @@ import {
SavedObjectsServiceStart,
Logger,
ElasticsearchClient,
SavedObjectsClientContract,
} from '@kbn/core/server';
import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server';
import { PluginStartContract as FeaturesPluginStart } from '@kbn/features-plugin/server';
import { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server';
import { LensServerPluginSetup } from '@kbn/lens-plugin/server';
import { SpacesPluginStart } from '@kbn/spaces-plugin/server';
import { SAVED_OBJECT_TYPES } from '../../common/constants';
import { Authorization } from '../authorization/authorization';
import { GetSpaceFn } from '../authorization/types';
import {
CaseConfigureService,
CasesService,
@ -31,11 +32,12 @@ import { AuthorizationAuditLogger } from '../authorization';
import { CasesClient, createCasesClient } from '.';
import { PersistableStateAttachmentTypeRegistry } from '../attachment_framework/persistable_state_registry';
import { ExternalReferenceAttachmentTypeRegistry } from '../attachment_framework/external_reference_registry';
import { CasesServices } from './types';
interface CasesClientFactoryArgs {
securityPluginSetup?: SecurityPluginSetup;
securityPluginStart?: SecurityPluginStart;
getSpace: GetSpaceFn;
spacesPluginStart: SpacesPluginStart;
featuresPluginStart: FeaturesPluginStart;
actionsPluginStart: ActionsPluginStart;
lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory'];
@ -89,7 +91,7 @@ export class CasesClientFactory {
const auth = await Authorization.create({
request,
securityAuth: this.options.securityPluginStart?.authz,
getSpace: this.options.getSpace,
spaces: this.options.spacesPluginStart,
features: this.options.featuresPluginStart,
auditLogger: new AuthorizationAuditLogger(auditLogger),
logger: this.logger,
@ -102,6 +104,38 @@ export class CasesClientFactory {
excludedWrappers: ['security'],
});
const services = this.createServices({
unsecuredSavedObjectsClient,
esClient: scopedClusterClient,
});
const userInfo = services.caseService.getUser({ request });
return createCasesClient({
services,
unsecuredSavedObjectsClient,
// We only want these fields from the userInfo object
user: { username: userInfo.username, email: userInfo.email, full_name: userInfo.full_name },
logger: this.logger,
lensEmbeddableFactory: this.options.lensEmbeddableFactory,
authorization: auth,
actionsClient: await this.options.actionsPluginStart.getActionsClientWithRequest(request),
persistableStateAttachmentTypeRegistry: this.options.persistableStateAttachmentTypeRegistry,
externalReferenceAttachmentTypeRegistry: this.options.externalReferenceAttachmentTypeRegistry,
});
}
private createServices({
unsecuredSavedObjectsClient,
esClient,
}: {
unsecuredSavedObjectsClient: SavedObjectsClientContract;
esClient: ElasticsearchClient;
}): CasesServices {
if (!this.isInitialized || !this.options) {
throw new Error('CasesClientFactory must be initialized before calling create');
}
const attachmentService = new AttachmentService(
this.logger,
this.options.persistableStateAttachmentTypeRegistry
@ -113,13 +147,9 @@ export class CasesClientFactory {
unsecuredSavedObjectsClient,
attachmentService,
});
const userInfo = caseService.getUser({ request });
return createCasesClient({
alertsService: new AlertService(scopedClusterClient, this.logger),
unsecuredSavedObjectsClient,
// We only want these fields from the userInfo object
user: { username: userInfo.username, email: userInfo.email, full_name: userInfo.full_name },
return {
alertsService: new AlertService(esClient, this.logger),
caseService,
caseConfigureService: new CaseConfigureService(this.logger),
connectorMappingsService: new ConnectorMappingsService(this.logger),
@ -128,12 +158,6 @@ export class CasesClientFactory {
this.options.persistableStateAttachmentTypeRegistry
),
attachmentService,
logger: this.logger,
lensEmbeddableFactory: this.options.lensEmbeddableFactory,
authorization: auth,
actionsClient: await this.options.actionsPluginStart.getActionsClientWithRequest(request),
persistableStateAttachmentTypeRegistry: this.options.persistableStateAttachmentTypeRegistry,
externalReferenceAttachmentTypeRegistry: this.options.externalReferenceAttachmentTypeRegistry,
});
};
}
}

View file

@ -21,7 +21,7 @@ const getAuthorizationFilter = jest.fn().mockResolvedValue({});
const clientArgs = {
logger,
attachmentService,
services: { attachmentService },
authorization: { getAuthorizationFilter },
} as unknown as CasesClientArgs;

View file

@ -24,8 +24,12 @@ export class Actions extends SingleCaseAggregationHandler {
}
public async compute(): Promise<SingleCaseMetricsResponse> {
const { unsecuredSavedObjectsClient, authorization, attachmentService, logger } =
this.options.clientArgs;
const {
unsecuredSavedObjectsClient,
authorization,
services: { attachmentService },
logger,
} = this.options.clientArgs;
const { casesClient } = this.options;
try {

View file

@ -21,7 +21,9 @@ const getAuthorizationFilter = jest.fn().mockResolvedValue({});
const clientArgs = {
logger,
attachmentService,
services: {
attachmentService,
},
authorization: { getAuthorizationFilter },
} as unknown as CasesClientArgs;

View file

@ -17,8 +17,12 @@ export class AlertsCount extends SingleCaseBaseHandler {
}
public async compute(): Promise<SingleCaseMetricsResponse> {
const { unsecuredSavedObjectsClient, authorization, attachmentService, logger } =
this.options.clientArgs;
const {
unsecuredSavedObjectsClient,
authorization,
services: { attachmentService },
logger,
} = this.options.clientArgs;
const { casesClient } = this.options;

View file

@ -37,7 +37,7 @@ describe('AlertDetails', () => {
const handler = new AlertDetails({
caseId: '',
casesClient: client,
clientArgs: {} as CasesClientArgs,
clientArgs: { services: {} } as CasesClientArgs,
});
expect(await handler.compute()).toEqual({});
});
@ -50,7 +50,7 @@ describe('AlertDetails', () => {
const handler = new AlertDetails({
caseId: '',
casesClient: client,
clientArgs: {} as CasesClientArgs,
clientArgs: { services: {} } as CasesClientArgs,
});
handler.setupFeature('alerts.hosts');
@ -65,7 +65,7 @@ describe('AlertDetails', () => {
});
it('returns the default zero values for hosts when the count aggregation returns undefined', async () => {
mockServices.alertsService.executeAggregations.mockImplementation(async () => ({}));
mockServices.services.alertsService.executeAggregations.mockImplementation(async () => ({}));
const handler = new AlertDetails(constructorOptions);
handler.setupFeature('alerts.hosts');
@ -81,7 +81,7 @@ describe('AlertDetails', () => {
});
it('returns the default zero values for users when the count aggregation returns undefined', async () => {
mockServices.alertsService.executeAggregations.mockImplementation(async () => ({}));
mockServices.services.alertsService.executeAggregations.mockImplementation(async () => ({}));
const handler = new AlertDetails(constructorOptions);
handler.setupFeature('alerts.users');
@ -97,7 +97,7 @@ describe('AlertDetails', () => {
});
it('returns the default zero values for hosts when the top hits aggregation returns undefined', async () => {
mockServices.alertsService.executeAggregations.mockImplementation(async () => ({}));
mockServices.services.alertsService.executeAggregations.mockImplementation(async () => ({}));
const handler = new AlertDetails(constructorOptions);
handler.setupFeature('alerts.hosts');
@ -113,7 +113,7 @@ describe('AlertDetails', () => {
});
it('returns the default zero values for users when the top hits aggregation returns undefined', async () => {
mockServices.alertsService.executeAggregations.mockImplementation(async () => ({}));
mockServices.services.alertsService.executeAggregations.mockImplementation(async () => ({}));
const handler = new AlertDetails(constructorOptions);
handler.setupFeature('alerts.users');
@ -136,7 +136,7 @@ describe('AlertDetails', () => {
const handler = new AlertDetails({
caseId: '',
casesClient: client,
clientArgs: {} as CasesClientArgs,
clientArgs: { services: {} } as CasesClientArgs,
});
expect(await handler.compute()).toEqual({});
});
@ -149,7 +149,7 @@ describe('AlertDetails', () => {
const handler = new AlertDetails({
caseId: '',
casesClient: client,
clientArgs: {} as CasesClientArgs,
clientArgs: { services: {} } as CasesClientArgs,
});
expect(await handler.compute()).toEqual({});
expect(await handler.compute()).toEqual({});
@ -222,7 +222,9 @@ function createMockClientArgs() {
const clientArgs = {
logger,
alertsService,
services: {
alertsService,
},
};
return { mockServices: clientArgs, clientArgs: clientArgs as unknown as CasesClientArgs };

View file

@ -24,7 +24,10 @@ export class AlertDetails extends SingleCaseAggregationHandler {
}
public async compute(): Promise<SingleCaseMetricsResponse> {
const { alertsService, logger } = this.options.clientArgs;
const {
services: { alertsService },
logger,
} = this.options.clientArgs;
const { casesClient } = this.options;
try {

View file

@ -21,7 +21,9 @@ const getAuthorizationFilter = jest.fn().mockResolvedValue({});
const clientArgs = {
logger,
caseService,
services: {
caseService,
},
authorization: { getAuthorizationFilter },
} as unknown as CasesClientArgs;

View file

@ -22,7 +22,11 @@ export class MTTR extends AllCasesAggregationHandler {
}
public async compute(): Promise<CasesMetricsResponse> {
const { authorization, caseService, logger } = this.options.clientArgs;
const {
authorization,
services: { caseService },
logger,
} = this.options.clientArgs;
try {
const { filter: authorizationFilter } = await authorization.getAuthorizationFilter(

View file

@ -19,7 +19,9 @@ describe('Connectors', () => {
const clientArgs = {
logger,
userActionService,
services: {
userActionService,
},
authorization: { getAuthorizationFilter },
} as unknown as CasesClientArgs;

View file

@ -17,8 +17,12 @@ export class Connectors extends SingleCaseBaseHandler {
}
public async compute(): Promise<SingleCaseMetricsResponse> {
const { unsecuredSavedObjectsClient, authorization, userActionService, logger } =
this.options.clientArgs;
const {
unsecuredSavedObjectsClient,
authorization,
services: { userActionService },
logger,
} = this.options.clientArgs;
const { filter: authorizationFilter } = await authorization.getAuthorizationFilter(
Operations.getUserActionMetrics

View file

@ -151,7 +151,7 @@ describe('getCaseMetrics', () => {
clientArgs
);
expect(mockServices.alertsService.executeAggregations).toBeCalledTimes(1);
expect(mockServices.services.alertsService.executeAggregations).toBeCalledTimes(1);
});
});
@ -209,11 +209,13 @@ function createMockClientArgs() {
const clientArgs = {
authorization,
unsecuredSavedObjectsClient: soClient,
caseService,
logger,
attachmentService,
alertsService,
userActionService,
services: {
caseService,
attachmentService,
alertsService,
userActionService,
},
};
return { mockServices: clientArgs, clientArgs: clientArgs as unknown as CasesClientArgs };

View file

@ -52,7 +52,10 @@ const checkAuthorization = async (
params: SingleCaseMetricsRequest,
clientArgs: CasesClientArgs
) => {
const { caseService, authorization } = clientArgs;
const {
services: { caseService },
authorization,
} = clientArgs;
const caseInfo = await caseService.getCase({
id: params.caseId,

View file

@ -27,7 +27,9 @@ describe('getCasesMetrics', () => {
describe('MTTR', () => {
beforeEach(() => {
mockServices.caseService.executeAggregations.mockResolvedValue({ mttr: { value: 5 } });
mockServices.services.caseService.executeAggregations.mockResolvedValue({
mttr: { value: 5 },
});
});
it('returns the mttr metric', async () => {
@ -46,7 +48,8 @@ describe('getCasesMetrics', () => {
client,
clientArgs
);
expect(mockServices.caseService.executeAggregations.mock.calls[0][0]).toMatchInlineSnapshot(`
expect(mockServices.services.caseService.executeAggregations.mock.calls[0][0])
.toMatchInlineSnapshot(`
Object {
"aggregationBuilders": Array [
AverageDuration {},

View file

@ -24,7 +24,7 @@ describe('getStatusTotalsByType', () => {
describe('MTTR', () => {
beforeEach(() => {
mockServices.caseService.getCaseStatusStats.mockResolvedValue({
mockServices.services.caseService.getCaseStatusStats.mockResolvedValue({
open: 1,
'in-progress': 2,
closed: 1,
@ -50,7 +50,8 @@ describe('getStatusTotalsByType', () => {
clientArgs
);
expect(mockServices.caseService.getCaseStatusStats.mock.calls[0][0]).toMatchInlineSnapshot(`
expect(mockServices.services.caseService.getCaseStatusStats.mock.calls[0][0])
.toMatchInlineSnapshot(`
Object {
"searchOptions": Object {
"filter": Object {

View file

@ -27,7 +27,11 @@ export async function getStatusTotalsByType(
params: CasesStatusRequest,
clientArgs: CasesClientArgs
): Promise<CasesStatusResponse> {
const { caseService, logger, authorization } = clientArgs;
const {
services: { caseService },
logger,
authorization,
} = clientArgs;
try {
const queryParams = pipe(

View file

@ -26,8 +26,12 @@ export class Lifespan extends SingleCaseBaseHandler {
}
public async compute(): Promise<SingleCaseMetricsResponse> {
const { unsecuredSavedObjectsClient, authorization, userActionService, logger } =
this.options.clientArgs;
const {
unsecuredSavedObjectsClient,
authorization,
services: { userActionService },
logger,
} = this.options.clientArgs;
const { casesClient } = this.options;

View file

@ -31,7 +31,9 @@ export function createMockClientArgs() {
const clientArgs = {
authorization,
unsecuredSavedObjectsClient: soClient,
caseService,
services: {
caseService,
},
logger,
};

View file

@ -23,18 +23,22 @@ import {
import { PersistableStateAttachmentTypeRegistry } from '../attachment_framework/persistable_state_registry';
import { ExternalReferenceAttachmentTypeRegistry } from '../attachment_framework/external_reference_registry';
export interface CasesServices {
alertsService: AlertService;
caseService: CasesService;
caseConfigureService: CaseConfigureService;
connectorMappingsService: ConnectorMappingsService;
userActionService: CaseUserActionService;
attachmentService: AttachmentService;
}
/**
* Parameters for initializing a cases client
*/
export interface CasesClientArgs {
readonly caseConfigureService: CaseConfigureService;
readonly caseService: CasesService;
readonly connectorMappingsService: ConnectorMappingsService;
readonly services: CasesServices;
readonly user: User;
readonly unsecuredSavedObjectsClient: SavedObjectsClientContract;
readonly userActionService: CaseUserActionService;
readonly alertsService: AlertService;
readonly attachmentService: AttachmentService;
readonly logger: Logger;
readonly lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory'];
readonly authorization: PublicMethodsOf<Authorization>;

View file

@ -20,7 +20,12 @@ export const get = async (
{ caseId }: UserActionGet,
clientArgs: CasesClientArgs
): Promise<CaseUserActionsResponse> => {
const { unsecuredSavedObjectsClient, userActionService, logger, authorization } = clientArgs;
const {
unsecuredSavedObjectsClient,
services: { userActionService },
logger,
authorization,
} = clientArgs;
try {
const userActions = await userActionService.getAll({

View file

@ -62,7 +62,7 @@ export class CaseCommentModel {
id: string,
options: CaseCommentModelParams
): Promise<CaseCommentModel> {
const savedObject = await options.caseService.getCase({
const savedObject = await options.services.caseService.getCase({
id,
});
@ -93,7 +93,7 @@ export class CaseCommentModel {
};
if (queryRestAttributes.type === CommentType.user && queryRestAttributes?.comment) {
const currentComment = (await this.params.attachmentService.get({
const currentComment = (await this.params.services.attachmentService.get({
unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient,
attachmentId: id,
})) as SavedObject<CommentRequestUserType>;
@ -107,7 +107,7 @@ export class CaseCommentModel {
}
const [comment, commentableCase] = await Promise.all([
this.params.attachmentService.update({
this.params.services.attachmentService.update({
unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient,
attachmentId: id,
updatedAttributes: {
@ -141,7 +141,7 @@ export class CaseCommentModel {
refresh: RefreshSetting
): Promise<CaseCommentModel> {
try {
const updatedCase = await this.params.caseService.patchCase({
const updatedCase = await this.params.services.caseService.patchCase({
originalCase: this.caseInfo,
caseId: this.caseInfo.id,
updatedAttributes: {
@ -180,7 +180,7 @@ export class CaseCommentModel {
) {
const { id, version, ...queryRestAttributes } = updateRequest;
await this.params.userActionService.createUserAction({
await this.params.services.userActionService.createUserAction({
type: ActionTypes.comment,
action: Actions.update,
unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient,
@ -210,7 +210,7 @@ export class CaseCommentModel {
const references = [...this.buildRefsToCase(), ...this.getCommentReferences(commentReq)];
const [comment, commentableCase] = await Promise.all([
this.params.attachmentService.create({
this.params.services.attachmentService.create({
unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient,
attributes: transformNewComment({
createdDate,
@ -273,10 +273,11 @@ export class CaseCommentModel {
}
private async validateAlertsLimitOnCase(totalAlertsInReq: number) {
const alertsValueCount = await this.params.attachmentService.valueCountAlertsAttachedToCase({
unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient,
caseId: this.caseInfo.id,
});
const alertsValueCount =
await this.params.services.attachmentService.valueCountAlertsAttachedToCase({
unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient,
caseId: this.caseInfo.id,
});
if (alertsValueCount + totalAlertsInReq > MAX_ALERTS_PER_CASE) {
throw Boom.badRequest(ALERT_LIMIT_MSG);
@ -326,14 +327,14 @@ export class CaseCommentModel {
)
.flat();
await this.params.alertsService.updateAlertsStatus(alertsToUpdate);
await this.params.services.alertsService.updateAlertsStatus(alertsToUpdate);
}
private async createCommentUserAction(
comment: SavedObject<CommentAttributes>,
req: CommentRequest
) {
await this.params.userActionService.createUserAction({
await this.params.services.userActionService.createUserAction({
type: ActionTypes.comment,
action: Actions.create,
unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient,
@ -348,7 +349,7 @@ export class CaseCommentModel {
}
private async bulkCreateCommentUserAction(attachments: Array<{ id: string } & CommentRequest>) {
await this.params.userActionService.bulkCreateAttachmentCreation({
await this.params.services.userActionService.bulkCreateAttachmentCreation({
unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient,
caseId: this.caseInfo.id,
attachments: attachments.map(({ id, ...attachment }) => ({
@ -371,7 +372,7 @@ export class CaseCommentModel {
public async encodeWithComments(): Promise<CaseResponse> {
try {
const comments = await this.params.caseService.getAllCaseComments({
const comments = await this.params.services.caseService.getAllCaseComments({
id: this.caseInfo.id,
options: {
fields: [],
@ -409,7 +410,7 @@ export class CaseCommentModel {
const caseReference = this.buildRefsToCase();
const [newlyCreatedAttachments, commentableCase] = await Promise.all([
this.params.attachmentService.bulkCreate({
this.params.services.attachmentService.bulkCreate({
unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient,
attachments: attachments.map(({ id, ...attachment }) => {
return {

View file

@ -66,7 +66,7 @@ export interface PluginsStart {
features: FeaturesPluginStart;
taskManager?: TaskManagerStartContract;
security?: SecurityPluginStart;
spaces?: SpacesPluginStart;
spaces: SpacesPluginStart;
}
export class CasePlugin {
@ -166,9 +166,7 @@ export class CasePlugin {
this.clientFactory.initialize({
securityPluginSetup: this.securityPluginSetup,
securityPluginStart: plugins.security,
getSpace: async (request: KibanaRequest) => {
return plugins.spaces?.spacesService.getActiveSpace(request);
},
spacesPluginStart: plugins.spaces,
featuresPluginStart: plugins.features,
actionsPluginStart: plugins.actions,
/**