From 0c5ab0632786f9798efb16d783208a3bd2170ecc Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Mon, 25 Nov 2024 05:57:29 +0000 Subject: [PATCH 01/32] WIP stack v3 cases working --- .../feature_privilege_builder/cases.ts | 3 + .../cases/common/constants/application.ts | 4 +- .../plugins/cases/common/constants/index.ts | 1 + x-pack/plugins/cases/common/index.ts | 1 + x-pack/plugins/cases/common/ui/types.ts | 3 + .../cases/common/utils/capabilities.ts | 3 + .../public/client/helpers/can_use_cases.ts | 12 +- .../public/client/helpers/capabilities.ts | 6 +- .../cases/public/common/lib/kibana/hooks.ts | 10 +- .../public/common/use_cases_features.tsx | 15 +- .../cases/public/components/app/index.tsx | 2 +- .../case_view/components/assign_users.tsx | 2 +- .../public/components/cases_context/index.tsx | 1 + .../cases/server/authorization/index.ts | 8 + .../cases/server/authorization/types.ts | 1 + .../cases/server/client/cases/bulk_update.ts | 54 ++++- .../cases/server/connectors/cases/utils.ts | 1 + .../cases/server/features/constants.ts | 1 + x-pack/plugins/cases/server/features/index.ts | 4 +- x-pack/plugins/cases/server/features/v1.ts | 16 +- x-pack/plugins/cases/server/features/v2.ts | 37 ++- x-pack/plugins/cases/server/features/v3.ts | 223 ++++++++++++++++++ x-pack/plugins/cases/server/plugin.ts | 1 + .../common/feature_kibana_privileges.ts | 10 + .../feature_privilege_iterator.ts | 4 + .../plugins/features/server/feature_schema.ts | 1 + 26 files changed, 390 insertions(+), 34 deletions(-) create mode 100644 x-pack/plugins/cases/server/features/v3.ts diff --git a/x-pack/packages/security/authorization_core/src/privileges/feature_privilege_builder/cases.ts b/x-pack/packages/security/authorization_core/src/privileges/feature_privilege_builder/cases.ts index 3cf293b935b36..ed75ecc0d7d17 100644 --- a/x-pack/packages/security/authorization_core/src/privileges/feature_privilege_builder/cases.ts +++ b/x-pack/packages/security/authorization_core/src/privileges/feature_privilege_builder/cases.ts @@ -37,6 +37,7 @@ const deleteOperations = ['deleteCase', 'deleteComment'] as const; const settingsOperations = ['createConfiguration', 'updateConfiguration'] as const; const createCommentOperations = ['createComment'] as const; const reopenOperations = ['reopenCase'] as const; +const assignOperations = ['assignCase'] as const; const allOperations = [ ...pushOperations, ...createOperations, @@ -46,6 +47,7 @@ const allOperations = [ ...settingsOperations, ...createCommentOperations, ...reopenOperations, + ...assignOperations, ] as const; export class FeaturePrivilegeCasesBuilder extends BaseFeaturePrivilegeBuilder { @@ -71,6 +73,7 @@ export class FeaturePrivilegeCasesBuilder extends BaseFeaturePrivilegeBuilder { ...getCasesPrivilege(settingsOperations, privilegeDefinition.cases?.settings), ...getCasesPrivilege(createCommentOperations, privilegeDefinition.cases?.createComment), ...getCasesPrivilege(reopenOperations, privilegeDefinition.cases?.reopenCase), + ...getCasesPrivilege(assignOperations, privilegeDefinition.cases?.assignCase), ]); } } diff --git a/x-pack/plugins/cases/common/constants/application.ts b/x-pack/plugins/cases/common/constants/application.ts index 01bbea157e7d2..18afd107e378e 100644 --- a/x-pack/plugins/cases/common/constants/application.ts +++ b/x-pack/plugins/cases/common/constants/application.ts @@ -12,9 +12,11 @@ import { CASE_VIEW_PAGE_TABS } from '../types'; */ export const APP_ID = 'cases' as const; -/** @deprecated Please use FEATURE_ID_V2 instead */ +/** @deprecated Please use FEATURE_ID_V3 instead */ export const FEATURE_ID = 'generalCases' as const; +/** @deprecated Please use FEATURE_ID_V3 instead */ export const FEATURE_ID_V2 = 'generalCasesV2' as const; +export const FEATURE_ID_V3 = 'generalCasesV3' as const; export const APP_OWNER = 'cases' as const; export const APP_PATH = '/app/management/insightsAndAlerting/cases' as const; export const CASES_CREATE_PATH = '/create' as const; diff --git a/x-pack/plugins/cases/common/constants/index.ts b/x-pack/plugins/cases/common/constants/index.ts index 1fee73f8608c8..5611e94f547e4 100644 --- a/x-pack/plugins/cases/common/constants/index.ts +++ b/x-pack/plugins/cases/common/constants/index.ts @@ -176,6 +176,7 @@ export const CASES_SETTINGS_CAPABILITY = 'cases_settings' as const; export const CASES_CONNECTORS_CAPABILITY = 'cases_connectors' as const; export const CASES_REOPEN_CAPABILITY = 'case_reopen' as const; export const CREATE_COMMENT_CAPABILITY = 'create_comment' as const; +export const ASSIGN_CASE_CAPABILITY = 'case_assign' as const; /** * Cases API Tags diff --git a/x-pack/plugins/cases/common/index.ts b/x-pack/plugins/cases/common/index.ts index 8e3b2644ee01a..d818cdbf0d734 100644 --- a/x-pack/plugins/cases/common/index.ts +++ b/x-pack/plugins/cases/common/index.ts @@ -59,6 +59,7 @@ export { CASES_SETTINGS_CAPABILITY, CREATE_COMMENT_CAPABILITY, CASES_REOPEN_CAPABILITY, + ASSIGN_CASE_CAPABILITY, } from './constants'; export type { AttachmentAttributes } from './types/domain'; diff --git a/x-pack/plugins/cases/common/ui/types.ts b/x-pack/plugins/cases/common/ui/types.ts index 99c92e0dbb55b..4611e3bb4da65 100644 --- a/x-pack/plugins/cases/common/ui/types.ts +++ b/x-pack/plugins/cases/common/ui/types.ts @@ -13,6 +13,7 @@ import type { UPDATE_CASES_CAPABILITY, CREATE_COMMENT_CAPABILITY, CASES_REOPEN_CAPABILITY, + ASSIGN_CASE_CAPABILITY, } from '..'; import type { CASES_CONNECTORS_CAPABILITY, @@ -309,6 +310,7 @@ export interface CasesPermissions { settings: boolean; reopenCase: boolean; createComment: boolean; + assignCase: boolean; } export interface CasesCapabilities { @@ -321,4 +323,5 @@ export interface CasesCapabilities { [CASES_SETTINGS_CAPABILITY]: boolean; [CREATE_COMMENT_CAPABILITY]: boolean; [CASES_REOPEN_CAPABILITY]: boolean; + [ASSIGN_CASE_CAPABILITY]: boolean; } diff --git a/x-pack/plugins/cases/common/utils/capabilities.ts b/x-pack/plugins/cases/common/utils/capabilities.ts index 6897dc6bae774..173e45e59338a 100644 --- a/x-pack/plugins/cases/common/utils/capabilities.ts +++ b/x-pack/plugins/cases/common/utils/capabilities.ts @@ -15,6 +15,7 @@ import { CASES_SETTINGS_CAPABILITY, CASES_REOPEN_CAPABILITY, CREATE_COMMENT_CAPABILITY, + ASSIGN_CASE_CAPABILITY, } from '../constants'; export interface CasesUiCapabilities { @@ -24,6 +25,7 @@ export interface CasesUiCapabilities { settings: readonly string[]; reopenCase: readonly string[]; createComment: readonly string[]; + assignCase: readonly string[]; } /** * Return the UI capabilities for each type of operation. These strings must match the values defined in the UI @@ -43,4 +45,5 @@ export const createUICapabilities = (): CasesUiCapabilities => ({ settings: [CASES_SETTINGS_CAPABILITY] as const, reopenCase: [CASES_REOPEN_CAPABILITY] as const, createComment: [CREATE_COMMENT_CAPABILITY] as const, + assignCase: [ASSIGN_CASE_CAPABILITY] as const, }); diff --git a/x-pack/plugins/cases/public/client/helpers/can_use_cases.ts b/x-pack/plugins/cases/public/client/helpers/can_use_cases.ts index 3e318132f8adf..8fb9403febdde 100644 --- a/x-pack/plugins/cases/public/client/helpers/can_use_cases.ts +++ b/x-pack/plugins/cases/public/client/helpers/can_use_cases.ts @@ -7,7 +7,7 @@ import type { ApplicationStart } from '@kbn/core/public'; import { - FEATURE_ID_V2, + FEATURE_ID_V3, GENERAL_CASES_OWNER, OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER, @@ -32,6 +32,7 @@ export const canUseCases = owners: CasesOwners[] = [OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER, GENERAL_CASES_OWNER] ): CasesPermissions => { const aggregatedPermissions = owners.reduce( + // eslint-disable-next-line complexity (acc, owner) => { const userCapabilitiesForOwner = getUICapabilities(capabilities[getFeatureID(owner)]); @@ -44,6 +45,7 @@ export const canUseCases = acc.settings = acc.settings || userCapabilitiesForOwner.settings; acc.reopenCase = acc.reopenCase || userCapabilitiesForOwner.reopenCase; acc.createComment = acc.createComment || userCapabilitiesForOwner.createComment; + acc.assignCase = acc.assignCase || userCapabilitiesForOwner.assignCase; const allFromAcc = acc.create && @@ -54,7 +56,8 @@ export const canUseCases = acc.connectors && acc.settings && acc.reopenCase && - acc.createComment; + acc.createComment && + acc.assignCase; acc.all = acc.all || userCapabilitiesForOwner.all || allFromAcc; @@ -71,6 +74,7 @@ export const canUseCases = settings: false, reopenCase: false, createComment: false, + assignCase: false, } ); @@ -81,8 +85,8 @@ export const canUseCases = const getFeatureID = (owner: CasesOwners) => { if (owner === GENERAL_CASES_OWNER) { - return FEATURE_ID_V2; + return FEATURE_ID_V3; } - return `${owner}CasesV2`; + return `${owner}CasesV3`; }; diff --git a/x-pack/plugins/cases/public/client/helpers/capabilities.ts b/x-pack/plugins/cases/public/client/helpers/capabilities.ts index 634cb3188602d..5aaed11394eff 100644 --- a/x-pack/plugins/cases/public/client/helpers/capabilities.ts +++ b/x-pack/plugins/cases/public/client/helpers/capabilities.ts @@ -16,6 +16,7 @@ import { UPDATE_CASES_CAPABILITY, CASES_REOPEN_CAPABILITY, CREATE_COMMENT_CAPABILITY, + ASSIGN_CASE_CAPABILITY, } from '../../../common/constants'; export const getUICapabilities = ( @@ -30,6 +31,7 @@ export const getUICapabilities = ( const settings = !!featureCapabilities?.[CASES_SETTINGS_CAPABILITY]; const reopenCase = !!featureCapabilities?.[CASES_REOPEN_CAPABILITY]; const createComment = !!featureCapabilities?.[CREATE_COMMENT_CAPABILITY]; + const assignCase = !!featureCapabilities?.[ASSIGN_CASE_CAPABILITY]; const all = create && @@ -40,7 +42,8 @@ export const getUICapabilities = ( connectors && settings && reopenCase && - createComment; + createComment && + assignCase; return { all, @@ -53,5 +56,6 @@ export const getUICapabilities = ( settings, reopenCase, createComment, + assignCase, }; }; diff --git a/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts b/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts index 6a309111ceddb..a3b6e94b1a76b 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts +++ b/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts @@ -15,7 +15,7 @@ import type { NavigateToAppOptions } from '@kbn/core/public'; import { getUICapabilities } from '../../../client/helpers/capabilities'; import { convertToCamelCase } from '../../../api/utils'; import { - FEATURE_ID_V2, + FEATURE_ID_V3, DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ, } from '../../../../common/constants'; @@ -166,7 +166,7 @@ interface Capabilities { } interface UseApplicationCapabilities { actions: Capabilities; - generalCasesV2: CasesPermissions; + generalCasesV3: CasesPermissions; visualize: Capabilities; dashboard: Capabilities; } @@ -178,13 +178,13 @@ interface UseApplicationCapabilities { export const useApplicationCapabilities = (): UseApplicationCapabilities => { const capabilities = useKibana().services?.application?.capabilities; - const casesCapabilities = capabilities[FEATURE_ID_V2]; + const casesCapabilities = capabilities[FEATURE_ID_V3]; const permissions = getUICapabilities(casesCapabilities); return useMemo( () => ({ actions: { crud: !!capabilities.actions?.save, read: !!capabilities.actions?.show }, - generalCasesV2: { + generalCasesV3: { all: permissions.all, create: permissions.create, read: permissions.read, @@ -195,6 +195,7 @@ export const useApplicationCapabilities = (): UseApplicationCapabilities => { settings: permissions.settings, reopenCase: permissions.reopenCase, createComment: permissions.createComment, + assignCase: permissions.assignCase, }, visualize: { crud: !!capabilities.visualize?.save, read: !!capabilities.visualize?.show }, dashboard: { @@ -219,6 +220,7 @@ export const useApplicationCapabilities = (): UseApplicationCapabilities => { permissions.settings, permissions.reopenCase, permissions.createComment, + permissions.assignCase, ] ); }; diff --git a/x-pack/plugins/cases/public/common/use_cases_features.tsx b/x-pack/plugins/cases/public/common/use_cases_features.tsx index 2f064df9a97a9..974b33c744272 100644 --- a/x-pack/plugins/cases/public/common/use_cases_features.tsx +++ b/x-pack/plugins/cases/public/common/use_cases_features.tsx @@ -19,7 +19,10 @@ export interface UseCasesFeatures { } export const useCasesFeatures = (): UseCasesFeatures => { - const { features } = useCasesContext(); + const { + features, + permissions: { assignCase }, + } = useCasesContext(); const { isAtLeastPlatinum } = useLicense(); const hasLicenseGreaterThanPlatinum = isAtLeastPlatinum(); @@ -36,10 +39,16 @@ export const useCasesFeatures = (): UseCasesFeatures => { */ isSyncAlertsEnabled: !features.alerts.enabled ? false : features.alerts.sync, metricsFeatures: features.metrics, - caseAssignmentAuthorized: hasLicenseGreaterThanPlatinum, + caseAssignmentAuthorized: hasLicenseGreaterThanPlatinum && assignCase, pushToServiceAuthorized: hasLicenseGreaterThanPlatinum, }), - [features.alerts.enabled, features.alerts.sync, features.metrics, hasLicenseGreaterThanPlatinum] + [ + features.alerts.enabled, + features.alerts.sync, + features.metrics, + hasLicenseGreaterThanPlatinum, + assignCase, + ] ); return casesFeatures; diff --git a/x-pack/plugins/cases/public/components/app/index.tsx b/x-pack/plugins/cases/public/components/app/index.tsx index eaa334470ab0f..86cf83064e4b5 100644 --- a/x-pack/plugins/cases/public/components/app/index.tsx +++ b/x-pack/plugins/cases/public/components/app/index.tsx @@ -39,7 +39,7 @@ const CasesAppComponent: React.FC = ({ getFilesClient, owner: [APP_OWNER], useFetchAlertData: () => [false, {}], - permissions: userCapabilities.generalCasesV2, + permissions: userCapabilities.generalCasesV3, basePath: '/', features: { alerts: { enabled: true, sync: false } }, })} diff --git a/x-pack/plugins/cases/public/components/case_view/components/assign_users.tsx b/x-pack/plugins/cases/public/components/case_view/components/assign_users.tsx index 6b29a9df14d91..513666c803b22 100644 --- a/x-pack/plugins/cases/public/components/case_view/components/assign_users.tsx +++ b/x-pack/plugins/cases/public/components/case_view/components/assign_users.tsx @@ -180,7 +180,7 @@ const AssignUsersComponent: React.FC = ({ {isLoading && } - {!isLoading && permissions.update && ( + {!isLoading && permissions.assignCase && ( , arr2?: Array<{ uid: string }>): boolean { + if (!arr1 || !arr2 || arr1.length !== arr2.length) return false; + const set1 = new Set(arr1); + return arr2.every((item) => set1.has(item)); +} + /** * Returns what status the alert comment should have based on whether it is associated to a case. */ @@ -273,10 +279,12 @@ function partitionPatchRequest( // This will be a deduped array of case IDs with their corresponding owner casesToAuthorize: OwnerEntity[]; reopenedCases: CasePatchRequest[]; + changedAssignees: CasePatchRequest[]; } { const nonExistingCases: CasePatchRequest[] = []; const conflictedCases: CasePatchRequest[] = []; const reopenedCases: CasePatchRequest[] = []; + const changedAssignees: CasePatchRequest[] = []; const casesToAuthorize: Map = new Map(); for (const reqCase of patchReqCases) { @@ -288,15 +296,24 @@ function partitionPatchRequest( conflictedCases.push(reqCase); // let's try to authorize the conflicted case even though we'll fail after afterwards just in case casesToAuthorize.set(foundCase.id, { id: foundCase.id, owner: foundCase.attributes.owner }); - } else if ( + } else { + casesToAuthorize.set(foundCase.id, { id: foundCase.id, owner: foundCase.attributes.owner }); + } + + if ( reqCase.status != null && + foundCase != null && + foundCase.attributes.status != null && foundCase.attributes.status !== reqCase.status && foundCase.attributes.status === CaseStatuses.closed ) { // Track cases that are closed and a user is attempting to reopen reopenedCases.push(reqCase); - } else { - casesToAuthorize.set(foundCase.id, { id: foundCase.id, owner: foundCase.attributes.owner }); + } + if (reqCase.assignees) { + if (!haveSameElements(reqCase.assignees, foundCase?.attributes.assignees)) { + changedAssignees.push(reqCase); + } } } @@ -304,10 +321,32 @@ function partitionPatchRequest( nonExistingCases, conflictedCases, reopenedCases, + changedAssignees, casesToAuthorize: Array.from(casesToAuthorize.values()), }; } +function getOperationsToAuthorize( + reopenedCases: CasePatchRequest[], + changedAssignees: CasePatchRequest[] +): OperationDetails[] { + const operations: OperationDetails[] = []; + + if (reopenedCases.length > 0) { + operations.push(Operations.reopenCase); + } + + if (changedAssignees.length > 0) { + operations.push(Operations.assignCase); + } + + if (changedAssignees.length === 0 && reopenedCases.length === 0) { + return [Operations.updateCase]; + } + + return operations; +} + export interface UpdateRequestWithOriginalCase { updateReq: CasePatchRequest; originalCase: CaseSavedObjectTransformed; @@ -354,13 +393,10 @@ export const bulkUpdate = async ( return acc; }, new Map()); - const { nonExistingCases, conflictedCases, casesToAuthorize, reopenedCases } = + const { nonExistingCases, conflictedCases, casesToAuthorize, reopenedCases, changedAssignees } = partitionPatchRequest(casesMap, query.cases); - const operationsToAuthorize = - reopenedCases.length > 0 - ? [Operations.reopenCase, Operations.updateCase] - : [Operations.updateCase]; + const operationsToAuthorize = getOperationsToAuthorize(reopenedCases, changedAssignees); await authorization.ensureAuthorized({ entities: casesToAuthorize, diff --git a/x-pack/plugins/cases/server/connectors/cases/utils.ts b/x-pack/plugins/cases/server/connectors/cases/utils.ts index b9cd2982553e3..aaee01ba6ec34 100644 --- a/x-pack/plugins/cases/server/connectors/cases/utils.ts +++ b/x-pack/plugins/cases/server/connectors/cases/utils.ts @@ -121,5 +121,6 @@ export const constructRequiredKibanaPrivileges = (owner: string): string[] => { `cases:${owner}/deleteComment`, `cases:${owner}/findConfigurations`, `cases:${owner}/reopenCase`, + `cases:${owner}/assignCase`, ]; }; diff --git a/x-pack/plugins/cases/server/features/constants.ts b/x-pack/plugins/cases/server/features/constants.ts index fb0a0f4554dee..8d73f1105a0c7 100644 --- a/x-pack/plugins/cases/server/features/constants.ts +++ b/x-pack/plugins/cases/server/features/constants.ts @@ -16,3 +16,4 @@ export const CASES_DELETE_SUB_PRIVILEGE_ID = 'cases_delete'; export const CASES_SETTINGS_SUB_PRIVILEGE_ID = 'cases_settings'; export const CASES_CREATE_COMMENT_SUB_PRIVILEGE_ID = 'create_comment'; export const CASES_REOPEN_SUB_PRIVILEGE_ID = 'case_reopen'; +export const CASES_ASSIGN_SUB_PRIVILEGE_ID = 'case_assign'; diff --git a/x-pack/plugins/cases/server/features/index.ts b/x-pack/plugins/cases/server/features/index.ts index afa3dfab9b311..f2f19b7d270f2 100644 --- a/x-pack/plugins/cases/server/features/index.ts +++ b/x-pack/plugins/cases/server/features/index.ts @@ -8,8 +8,10 @@ import type { KibanaFeatureConfig } from '@kbn/features-plugin/common'; import { getV1 } from './v1'; import { getV2 } from './v2'; +import { getV3 } from './v3'; export const getCasesKibanaFeatures = (): { v1: KibanaFeatureConfig; v2: KibanaFeatureConfig; -} => ({ v1: getV1(), v2: getV2() }); + v3: KibanaFeatureConfig; +} => ({ v1: getV1(), v2: getV2(), v3: getV3() }); diff --git a/x-pack/plugins/cases/server/features/v1.ts b/x-pack/plugins/cases/server/features/v1.ts index 25a43434f3723..99038760336dc 100644 --- a/x-pack/plugins/cases/server/features/v1.ts +++ b/x-pack/plugins/cases/server/features/v1.ts @@ -12,7 +12,7 @@ import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/s import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; import { KibanaFeatureScope } from '@kbn/features-plugin/common'; -import { APP_ID, FEATURE_ID, FEATURE_ID_V2 } from '../../common/constants'; +import { APP_ID, FEATURE_ID, FEATURE_ID_V3 } from '../../common/constants'; import { createUICapabilities, getApiTags } from '../../common'; import { CASES_DELETE_SUB_PRIVILEGE_ID, CASES_SETTINGS_SUB_PRIVILEGE_ID } from './constants'; @@ -35,7 +35,7 @@ export const getV1 = (): KibanaFeatureConfig => { 'The {currentId} permissions are deprecated, please see {casesFeatureIdV2}.', values: { currentId: FEATURE_ID, - casesFeatureIdV2: FEATURE_ID_V2, + casesFeatureIdV2: FEATURE_ID_V3, }, }), }, @@ -71,10 +71,10 @@ export const getV1 = (): KibanaFeatureConfig => { }, ui: capabilities.all, replacedBy: { - default: [{ feature: FEATURE_ID_V2, privileges: ['all'] }], + default: [{ feature: FEATURE_ID_V3, privileges: ['all'] }], minimal: [ { - feature: FEATURE_ID_V2, + feature: FEATURE_ID_V3, privileges: ['minimal_all', 'create_comment', 'case_reopen'], }, ], @@ -94,8 +94,8 @@ export const getV1 = (): KibanaFeatureConfig => { }, ui: capabilities.read, replacedBy: { - default: [{ feature: FEATURE_ID_V2, privileges: ['read'] }], - minimal: [{ feature: FEATURE_ID_V2, privileges: ['minimal_read'] }], + default: [{ feature: FEATURE_ID_V3, privileges: ['read'] }], + minimal: [{ feature: FEATURE_ID_V3, privileges: ['minimal_read'] }], }, }, }, @@ -124,7 +124,7 @@ export const getV1 = (): KibanaFeatureConfig => { }, ui: capabilities.delete, replacedBy: [ - { feature: FEATURE_ID_V2, privileges: [CASES_DELETE_SUB_PRIVILEGE_ID] }, + { feature: FEATURE_ID_V3, privileges: [CASES_DELETE_SUB_PRIVILEGE_ID] }, ], }, ], @@ -154,7 +154,7 @@ export const getV1 = (): KibanaFeatureConfig => { }, ui: capabilities.settings, replacedBy: [ - { feature: FEATURE_ID_V2, privileges: [CASES_SETTINGS_SUB_PRIVILEGE_ID] }, + { feature: FEATURE_ID_V3, privileges: [CASES_SETTINGS_SUB_PRIVILEGE_ID] }, ], }, ], diff --git a/x-pack/plugins/cases/server/features/v2.ts b/x-pack/plugins/cases/server/features/v2.ts index fca97303f02ab..2e5a820cd7421 100644 --- a/x-pack/plugins/cases/server/features/v2.ts +++ b/x-pack/plugins/cases/server/features/v2.ts @@ -12,7 +12,7 @@ import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/s import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; import { KibanaFeatureScope } from '@kbn/features-plugin/common'; -import { APP_ID, FEATURE_ID_V2 } from '../../common/constants'; +import { APP_ID, FEATURE_ID_V2, FEATURE_ID_V3 } from '../../common/constants'; import { createUICapabilities, getApiTags } from '../../common'; import { CASES_DELETE_SUB_PRIVILEGE_ID, @@ -34,6 +34,16 @@ export const getV2 = (): KibanaFeatureConfig => { const apiTags = getApiTags(APP_ID); return { + deprecated: { + notice: i18n.translate('xpack.cases.features.casesFeature.deprecationMessage', { + defaultMessage: + 'The {currentId} permissions are deprecated, please see {casesFeatureIdV2}.', + values: { + currentId: FEATURE_ID_V2, + casesFeatureIdV2: FEATURE_ID_V3, + }, + }), + }, id: FEATURE_ID_V2, name: i18n.translate('xpack.cases.features.casesFeatureName', { defaultMessage: 'Cases', @@ -63,6 +73,15 @@ export const getV2 = (): KibanaFeatureConfig => { read: [...filesSavedObjectTypes], }, ui: capabilities.all, + replacedBy: { + default: [{ feature: FEATURE_ID_V3, privileges: ['all'] }], + minimal: [ + { + feature: FEATURE_ID_V3, + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'case_assign'], + }, + ], + }, }, read: { api: apiTags.read, @@ -77,6 +96,10 @@ export const getV2 = (): KibanaFeatureConfig => { read: [...filesSavedObjectTypes], }, ui: capabilities.read, + replacedBy: { + default: [{ feature: FEATURE_ID_V3, privileges: ['read'] }], + minimal: [{ feature: FEATURE_ID_V3, privileges: ['minimal_read'] }], + }, }, }, subFeatures: [ @@ -103,6 +126,9 @@ export const getV2 = (): KibanaFeatureConfig => { delete: [APP_ID], }, ui: capabilities.delete, + replacedBy: [ + { feature: FEATURE_ID_V3, privileges: [CASES_DELETE_SUB_PRIVILEGE_ID] }, + ], }, ], }, @@ -130,6 +156,9 @@ export const getV2 = (): KibanaFeatureConfig => { settings: [APP_ID], }, ui: capabilities.settings, + replacedBy: [ + { feature: FEATURE_ID_V3, privileges: [CASES_SETTINGS_SUB_PRIVILEGE_ID] }, + ], }, ], }, @@ -158,6 +187,9 @@ export const getV2 = (): KibanaFeatureConfig => { createComment: [APP_ID], }, ui: capabilities.createComment, + replacedBy: [ + { feature: FEATURE_ID_V3, privileges: [CASES_CREATE_COMMENT_SUB_PRIVILEGE_ID] }, + ], }, ], }, @@ -185,6 +217,9 @@ export const getV2 = (): KibanaFeatureConfig => { reopenCase: [APP_ID], }, ui: capabilities.reopenCase, + replacedBy: [ + { feature: FEATURE_ID_V3, privileges: [CASES_REOPEN_SUB_PRIVILEGE_ID] }, + ], }, ], }, diff --git a/x-pack/plugins/cases/server/features/v3.ts b/x-pack/plugins/cases/server/features/v3.ts new file mode 100644 index 0000000000000..873a138eb9a55 --- /dev/null +++ b/x-pack/plugins/cases/server/features/v3.ts @@ -0,0 +1,223 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +import type { KibanaFeatureConfig } from '@kbn/features-plugin/common'; +import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects'; +import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; + +import { KibanaFeatureScope } from '@kbn/features-plugin/common'; +import { APP_ID, FEATURE_ID_V3 } from '../../common/constants'; +import { createUICapabilities, getApiTags } from '../../common'; +import { + CASES_DELETE_SUB_PRIVILEGE_ID, + CASES_SETTINGS_SUB_PRIVILEGE_ID, + CASES_CREATE_COMMENT_SUB_PRIVILEGE_ID, + CASES_REOPEN_SUB_PRIVILEGE_ID, + CASES_ASSIGN_SUB_PRIVILEGE_ID, +} from './constants'; + +/** + * The order of appearance in the feature privilege page + * under the management section. Cases should be under + * the Actions and Connectors feature + */ + +const FEATURE_ORDER = 3100; + +export const getV3 = (): KibanaFeatureConfig => { + const capabilities = createUICapabilities(); + const apiTags = getApiTags(APP_ID); + + return { + id: FEATURE_ID_V3, + name: i18n.translate('xpack.cases.features.casesFeatureName', { + defaultMessage: 'Cases', + }), + category: DEFAULT_APP_CATEGORIES.management, + scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security], + app: [], + order: FEATURE_ORDER, + management: { + insightsAndAlerting: [APP_ID], + }, + cases: [APP_ID], + privileges: { + all: { + api: apiTags.all, + cases: { + create: [APP_ID], + read: [APP_ID], + update: [APP_ID], + push: [APP_ID], + }, + management: { + insightsAndAlerting: [APP_ID], + }, + savedObject: { + all: [...filesSavedObjectTypes], + read: [...filesSavedObjectTypes], + }, + ui: capabilities.all, + }, + read: { + api: apiTags.read, + cases: { + read: [APP_ID], + }, + management: { + insightsAndAlerting: [APP_ID], + }, + savedObject: { + all: [], + read: [...filesSavedObjectTypes], + }, + ui: capabilities.read, + }, + }, + subFeatures: [ + { + name: i18n.translate('xpack.cases.features.deleteSubFeatureName', { + defaultMessage: 'Delete', + }), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + api: apiTags.delete, + id: CASES_DELETE_SUB_PRIVILEGE_ID, + name: i18n.translate('xpack.cases.features.deleteSubFeatureDetails', { + defaultMessage: 'Delete cases and comments', + }), + includeIn: 'all', + savedObject: { + all: [...filesSavedObjectTypes], + read: [...filesSavedObjectTypes], + }, + cases: { + delete: [APP_ID], + }, + ui: capabilities.delete, + }, + ], + }, + ], + }, + { + name: i18n.translate('xpack.cases.features.casesSettingsSubFeatureName', { + defaultMessage: 'Case settings', + }), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + id: CASES_SETTINGS_SUB_PRIVILEGE_ID, + name: i18n.translate('xpack.cases.features.casesSettingsSubFeatureDetails', { + defaultMessage: 'Edit case settings', + }), + includeIn: 'all', + savedObject: { + all: [...filesSavedObjectTypes], + read: [...filesSavedObjectTypes], + }, + cases: { + settings: [APP_ID], + }, + ui: capabilities.settings, + }, + ], + }, + ], + }, + { + name: i18n.translate('xpack.cases.features.addCommentsSubFeatureName', { + defaultMessage: 'Create comments & attachments', + }), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + api: apiTags.createComment, + id: CASES_CREATE_COMMENT_SUB_PRIVILEGE_ID, + name: i18n.translate('xpack.cases.features.addCommentsSubFeatureDetails', { + defaultMessage: 'Add comments to cases', + }), + includeIn: 'all', + savedObject: { + all: [...filesSavedObjectTypes], + read: [...filesSavedObjectTypes], + }, + cases: { + createComment: [APP_ID], + }, + ui: capabilities.createComment, + }, + ], + }, + ], + }, + { + name: i18n.translate('xpack.cases.features.reopenCaseSubFeatureName', { + defaultMessage: 'Re-open', + }), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + id: CASES_REOPEN_SUB_PRIVILEGE_ID, + name: i18n.translate('xpack.cases.features.reopenCaseSubFeatureDetails', { + defaultMessage: 'Re-open closed cases', + }), + includeIn: 'all', + savedObject: { + all: [], + read: [], + }, + cases: { + reopenCase: [APP_ID], + }, + ui: capabilities.reopenCase, + }, + ], + }, + ], + }, + { + name: i18n.translate('xpack.cases.features.assignUsersSubFeatureName', { + defaultMessage: 'Assign users', + }), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + id: CASES_ASSIGN_SUB_PRIVILEGE_ID, + name: i18n.translate('xpack.cases.features.assignUsersSubFeatureName', { + defaultMessage: 'Assign users to cases', + }), + includeIn: 'all', + savedObject: { + all: [], + read: [], + }, + cases: { + assignCase: [APP_ID], + }, + ui: capabilities.assignCase, + }, + ], + }, + ], + }, + ], + }; +}; diff --git a/x-pack/plugins/cases/server/plugin.ts b/x-pack/plugins/cases/server/plugin.ts index dfd4c013f0d58..f6e18dd124903 100644 --- a/x-pack/plugins/cases/server/plugin.ts +++ b/x-pack/plugins/cases/server/plugin.ts @@ -97,6 +97,7 @@ export class CasePlugin const casesFeatures = getCasesKibanaFeatures(); plugins.features.registerKibanaFeature(casesFeatures.v1); plugins.features.registerKibanaFeature(casesFeatures.v2); + plugins.features.registerKibanaFeature(casesFeatures.v3); } registerSavedObjects({ diff --git a/x-pack/plugins/features/common/feature_kibana_privileges.ts b/x-pack/plugins/features/common/feature_kibana_privileges.ts index 1939d0b5e4e49..081b46dbedc40 100644 --- a/x-pack/plugins/features/common/feature_kibana_privileges.ts +++ b/x-pack/plugins/features/common/feature_kibana_privileges.ts @@ -237,6 +237,16 @@ export interface FeatureKibanaPrivileges { * ``` */ reopenCase?: readonly string[]; + /** + * List of case owners whose users should have assignCase access when granted this privilege. + * @example + * ```ts + * { + * assignCase: ['securitySolution'] + * } + * ``` + */ + assignCase?: readonly string[]; }; /** diff --git a/x-pack/plugins/features/server/feature_privilege_iterator/feature_privilege_iterator.ts b/x-pack/plugins/features/server/feature_privilege_iterator/feature_privilege_iterator.ts index a9d7336ea0a22..2b1d364e21227 100644 --- a/x-pack/plugins/features/server/feature_privilege_iterator/feature_privilege_iterator.ts +++ b/x-pack/plugins/features/server/feature_privilege_iterator/feature_privilege_iterator.ts @@ -159,6 +159,10 @@ function mergeWithSubFeatures( mergedConfig.cases?.reopenCase ?? [], subFeaturePrivilege.cases?.reopenCase ?? [] ), + assignCase: mergeArrays( + mergedConfig.cases?.assignCase ?? [], + subFeaturePrivilege.cases?.assignCase ?? [] + ), }; } return mergedConfig; diff --git a/x-pack/plugins/features/server/feature_schema.ts b/x-pack/plugins/features/server/feature_schema.ts index ce444c41e477d..08488a5702224 100644 --- a/x-pack/plugins/features/server/feature_schema.ts +++ b/x-pack/plugins/features/server/feature_schema.ts @@ -85,6 +85,7 @@ const casesSchemaObject = schema.maybe( settings: schema.maybe(casesSchema), createComment: schema.maybe(casesSchema), reopenCase: schema.maybe(casesSchema), + assignCase: schema.maybe(casesSchema), }) ); From 3318b46eab313a1e62d5392d661939b2457e3b3e Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 10 Dec 2024 15:05:33 +0000 Subject: [PATCH 02/32] Add sec/obs case assignee sub feature + tests --- .../features/product_features.ts | 2 +- .../features/src/cases/index.ts | 13 ++ .../src/cases/v1_features/kibana_features.ts | 12 +- .../cases/v1_features/kibana_sub_features.ts | 6 +- .../src/cases/v2_features/kibana_features.ts | 33 ++- .../cases/v2_features/kibana_sub_features.ts | 6 +- .../src/cases/v3_features/kibana_features.ts | 67 ++++++ .../cases/v3_features/kibana_sub_features.ts | 206 +++++++++++++++++ .../features/src/constants.ts | 3 + .../features/src/product_features_keys.ts | 1 + .../cases/public/common/mock/permissions.ts | 11 + .../components/actions/use_items_action.tsx | 2 +- .../components/all_cases/use_actions.test.tsx | 3 + .../components/all_cases/use_actions.tsx | 9 +- .../components/all_cases/use_bulk_actions.tsx | 7 + .../components/all_cases/utility_bar.tsx | 5 +- .../plugins/cases/public/utils/permissions.ts | 1 + .../server/client/cases/bulk_update.test.ts | 50 ++++- .../cases/server/client/cases/bulk_update.ts | 2 +- .../observability/common/index.ts | 1 + .../observability/server/features/cases_v1.ts | 16 +- .../observability/server/features/cases_v2.ts | 33 ++- .../observability/server/features/cases_v3.ts | 208 ++++++++++++++++++ .../observability/server/plugin.ts | 2 + .../observability_shared/common/index.ts | 2 +- .../security_solution/common/constants.ts | 2 +- .../product_features_service.ts | 16 ++ .../apis/cases/common/roles.ts | 78 +++++++ .../apis/cases/common/users.ts | 24 ++ .../api_integration/apis/cases/privileges.ts | 18 ++ .../apis/features/features/features.ts | 6 +- .../apis/security/privileges.ts | 33 +++ .../apis/security/privileges_basic.ts | 36 +++ .../security_solution/cases_privileges.ts | 4 +- .../common/lib/api/case.ts | 29 +++ .../security_solution/server/plugin.ts | 23 ++ .../functional/services/ml/security_common.ts | 4 +- .../services/observability/users.ts | 2 +- .../apps/cases/common/roles.ts | 6 +- .../plugins/cases/public/application.tsx | 1 + .../cypress/tasks/privileges.ts | 4 + .../common/suites/create.ts | 2 + .../common/suites/get.ts | 2 + .../common/suites/get_all.ts | 2 + .../spaces_only/telemetry/telemetry.ts | 3 + 45 files changed, 955 insertions(+), 41 deletions(-) create mode 100644 x-pack/packages/security-solution/features/src/cases/v3_features/kibana_features.ts create mode 100644 x-pack/packages/security-solution/features/src/cases/v3_features/kibana_sub_features.ts create mode 100644 x-pack/plugins/observability_solution/observability/server/features/cases_v3.ts diff --git a/x-pack/packages/security-solution/features/product_features.ts b/x-pack/packages/security-solution/features/product_features.ts index 67d61f21fae5e..1b068ec7d4f98 100644 --- a/x-pack/packages/security-solution/features/product_features.ts +++ b/x-pack/packages/security-solution/features/product_features.ts @@ -6,6 +6,6 @@ */ export { getSecurityFeature } from './src/security'; -export { getCasesFeature, getCasesV2Feature } from './src/cases'; +export { getCasesFeature, getCasesV2Feature, getCasesV3Feature } from './src/cases'; export { getAssistantFeature } from './src/assistant'; export { getAttackDiscoveryFeature } from './src/attack_discovery'; diff --git a/x-pack/packages/security-solution/features/src/cases/index.ts b/x-pack/packages/security-solution/features/src/cases/index.ts index 17e5110538b37..8cab878d0dff8 100644 --- a/x-pack/packages/security-solution/features/src/cases/index.ts +++ b/x-pack/packages/security-solution/features/src/cases/index.ts @@ -17,6 +17,11 @@ import { getCasesBaseKibanaSubFeatureIdsV2, getCasesSubFeaturesMapV2, } from './v2_features/kibana_sub_features'; +import { getCasesBaseKibanaFeatureV3 } from './v3_features/kibana_features'; +import { + getCasesBaseKibanaSubFeatureIdsV3, + getCasesSubFeaturesMapV3, +} from './v3_features/kibana_sub_features'; /** * @deprecated Use getCasesV2Feature instead @@ -36,3 +41,11 @@ export const getCasesV2Feature = ( baseKibanaSubFeatureIds: getCasesBaseKibanaSubFeatureIdsV2(), subFeaturesMap: getCasesSubFeaturesMapV2(params), }); + +export const getCasesV3Feature = ( + params: CasesFeatureParams +): ProductFeatureParams => ({ + baseKibanaFeature: getCasesBaseKibanaFeatureV3(params), + baseKibanaSubFeatureIds: getCasesBaseKibanaSubFeatureIdsV3(), + subFeaturesMap: getCasesSubFeaturesMapV3(params), +}); diff --git a/x-pack/packages/security-solution/features/src/cases/v1_features/kibana_features.ts b/x-pack/packages/security-solution/features/src/cases/v1_features/kibana_features.ts index db442d894363a..205f8a3e1dc6a 100644 --- a/x-pack/packages/security-solution/features/src/cases/v1_features/kibana_features.ts +++ b/x-pack/packages/security-solution/features/src/cases/v1_features/kibana_features.ts @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; import { KibanaFeatureScope } from '@kbn/features-plugin/common'; import type { BaseKibanaFeatureConfig } from '../../types'; -import { APP_ID, CASES_FEATURE_ID, CASES_FEATURE_ID_V2 } from '../../constants'; +import { APP_ID, CASES_FEATURE_ID, CASES_FEATURE_ID_V3 } from '../../constants'; import type { CasesFeatureParams } from '../types'; /** @@ -30,7 +30,7 @@ export const getCasesBaseKibanaFeature = ({ 'The {currentId} permissions are deprecated, please see {casesFeatureIdV2}.', values: { currentId: CASES_FEATURE_ID, - casesFeatureIdV2: CASES_FEATURE_ID_V2, + casesFeatureIdV2: CASES_FEATURE_ID_V3, }, } ), @@ -67,10 +67,10 @@ export const getCasesBaseKibanaFeature = ({ }, ui: uiCapabilities.all, replacedBy: { - default: [{ feature: CASES_FEATURE_ID_V2, privileges: ['all'] }], + default: [{ feature: CASES_FEATURE_ID_V3, privileges: ['all'] }], minimal: [ { - feature: CASES_FEATURE_ID_V2, + feature: CASES_FEATURE_ID_V3, privileges: ['minimal_all', 'create_comment', 'case_reopen'], }, ], @@ -89,8 +89,8 @@ export const getCasesBaseKibanaFeature = ({ }, ui: uiCapabilities.read, replacedBy: { - default: [{ feature: CASES_FEATURE_ID_V2, privileges: ['read'] }], - minimal: [{ feature: CASES_FEATURE_ID_V2, privileges: ['minimal_read'] }], + default: [{ feature: CASES_FEATURE_ID_V3, privileges: ['read'] }], + minimal: [{ feature: CASES_FEATURE_ID_V3, privileges: ['minimal_read'] }], }, }, }, diff --git a/x-pack/packages/security-solution/features/src/cases/v1_features/kibana_sub_features.ts b/x-pack/packages/security-solution/features/src/cases/v1_features/kibana_sub_features.ts index ade0dbab2bfea..07c33a31800e1 100644 --- a/x-pack/packages/security-solution/features/src/cases/v1_features/kibana_sub_features.ts +++ b/x-pack/packages/security-solution/features/src/cases/v1_features/kibana_sub_features.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import type { SubFeatureConfig } from '@kbn/features-plugin/common'; import { CasesSubFeatureId } from '../../product_features_keys'; -import { APP_ID, CASES_FEATURE_ID_V2 } from '../../constants'; +import { APP_ID, CASES_FEATURE_ID_V3 } from '../../constants'; import type { CasesFeatureParams } from '../types'; /** @@ -56,7 +56,7 @@ export const getCasesSubFeaturesMap = ({ delete: [APP_ID], }, ui: uiCapabilities.delete, - replacedBy: [{ feature: CASES_FEATURE_ID_V2, privileges: ['cases_delete'] }], + replacedBy: [{ feature: CASES_FEATURE_ID_V3, privileges: ['cases_delete'] }], }, ], }, @@ -91,7 +91,7 @@ export const getCasesSubFeaturesMap = ({ settings: [APP_ID], }, ui: uiCapabilities.settings, - replacedBy: [{ feature: CASES_FEATURE_ID_V2, privileges: ['cases_settings'] }], + replacedBy: [{ feature: CASES_FEATURE_ID_V3, privileges: ['cases_settings'] }], }, ], }, diff --git a/x-pack/packages/security-solution/features/src/cases/v2_features/kibana_features.ts b/x-pack/packages/security-solution/features/src/cases/v2_features/kibana_features.ts index c0c025335d054..cdc02b91b68cf 100644 --- a/x-pack/packages/security-solution/features/src/cases/v2_features/kibana_features.ts +++ b/x-pack/packages/security-solution/features/src/cases/v2_features/kibana_features.ts @@ -10,7 +10,12 @@ import { i18n } from '@kbn/i18n'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; import { KibanaFeatureScope } from '@kbn/features-plugin/common'; import type { BaseKibanaFeatureConfig } from '../../types'; -import { APP_ID, CASES_FEATURE_ID_V2, CASES_FEATURE_ID } from '../../constants'; +import { + APP_ID, + CASES_FEATURE_ID_V2, + CASES_FEATURE_ID, + CASES_FEATURE_ID_V3, +} from '../../constants'; import type { CasesFeatureParams } from '../types'; export const getCasesBaseKibanaFeatureV2 = ({ @@ -19,6 +24,19 @@ export const getCasesBaseKibanaFeatureV2 = ({ savedObjects, }: CasesFeatureParams): BaseKibanaFeatureConfig => { return { + deprecated: { + notice: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.casesFeature.deprecationMessage', + { + defaultMessage: + 'The {currentId} permissions are deprecated, please see {casesFeatureIdV2}.', + values: { + currentId: CASES_FEATURE_ID_V2, + casesFeatureIdV2: CASES_FEATURE_ID_V3, + }, + } + ), + }, id: CASES_FEATURE_ID_V2, name: i18n.translate( 'securitySolutionPackages.features.featureRegistry.linkSecuritySolutionCaseTitle', @@ -48,6 +66,15 @@ export const getCasesBaseKibanaFeatureV2 = ({ read: [...savedObjects.files], }, ui: uiCapabilities.all, + replacedBy: { + default: [{ feature: CASES_FEATURE_ID_V3, privileges: ['all'] }], + minimal: [ + { + feature: CASES_FEATURE_ID_V3, + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'case_assign'], + }, + ], + }, }, read: { api: apiTags.read, @@ -61,6 +88,10 @@ export const getCasesBaseKibanaFeatureV2 = ({ read: [...savedObjects.files], }, ui: uiCapabilities.read, + replacedBy: { + default: [{ feature: CASES_FEATURE_ID_V3, privileges: ['read'] }], + minimal: [{ feature: CASES_FEATURE_ID_V3, privileges: ['minimal_read'] }], + }, }, }, }; diff --git a/x-pack/packages/security-solution/features/src/cases/v2_features/kibana_sub_features.ts b/x-pack/packages/security-solution/features/src/cases/v2_features/kibana_sub_features.ts index 59aeb866039d4..ddc369ab45927 100644 --- a/x-pack/packages/security-solution/features/src/cases/v2_features/kibana_sub_features.ts +++ b/x-pack/packages/security-solution/features/src/cases/v2_features/kibana_sub_features.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import type { SubFeatureConfig } from '@kbn/features-plugin/common'; import { CasesSubFeatureId } from '../../product_features_keys'; -import { APP_ID } from '../../constants'; +import { APP_ID, CASES_FEATURE_ID_V3 } from '../../constants'; import type { CasesFeatureParams } from '../types'; /** @@ -57,6 +57,7 @@ export const getCasesSubFeaturesMapV2 = ({ delete: [APP_ID], }, ui: uiCapabilities.delete, + replacedBy: [{ feature: CASES_FEATURE_ID_V3, privileges: ['cases_delete'] }], }, ], }, @@ -91,6 +92,7 @@ export const getCasesSubFeaturesMapV2 = ({ settings: [APP_ID], }, ui: uiCapabilities.settings, + replacedBy: [{ feature: CASES_FEATURE_ID_V3, privileges: ['cases_settings'] }], }, ], }, @@ -128,6 +130,7 @@ export const getCasesSubFeaturesMapV2 = ({ createComment: [APP_ID], }, ui: uiCapabilities.createComment, + replacedBy: [{ feature: CASES_FEATURE_ID_V3, privileges: ['create_comment'] }], }, ], }, @@ -161,6 +164,7 @@ export const getCasesSubFeaturesMapV2 = ({ reopenCase: [APP_ID], }, ui: uiCapabilities.reopenCase, + replacedBy: [{ feature: CASES_FEATURE_ID_V3, privileges: ['case_reopen'] }], }, ], }, diff --git a/x-pack/packages/security-solution/features/src/cases/v3_features/kibana_features.ts b/x-pack/packages/security-solution/features/src/cases/v3_features/kibana_features.ts new file mode 100644 index 0000000000000..c9a08ebb8614d --- /dev/null +++ b/x-pack/packages/security-solution/features/src/cases/v3_features/kibana_features.ts @@ -0,0 +1,67 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; +import { KibanaFeatureScope } from '@kbn/features-plugin/common'; +import type { BaseKibanaFeatureConfig } from '../../types'; +import { APP_ID, CASES_FEATURE_ID_V3, CASES_FEATURE_ID } from '../../constants'; +import type { CasesFeatureParams } from '../types'; + +export const getCasesBaseKibanaFeatureV3 = ({ + uiCapabilities, + apiTags, + savedObjects, +}: CasesFeatureParams): BaseKibanaFeatureConfig => { + return { + id: CASES_FEATURE_ID_V3, + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.linkSecuritySolutionCaseTitle', + { + defaultMessage: 'Cases', + } + ), + order: 1100, + category: DEFAULT_APP_CATEGORIES.security, + scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security], + app: [CASES_FEATURE_ID, 'kibana'], + catalogue: [APP_ID], + cases: [APP_ID], + privileges: { + all: { + api: apiTags.all, + app: [CASES_FEATURE_ID, 'kibana'], + catalogue: [APP_ID], + cases: { + create: [APP_ID], + read: [APP_ID], + update: [APP_ID], + push: [APP_ID], + }, + savedObject: { + all: [...savedObjects.files], + read: [...savedObjects.files], + }, + ui: uiCapabilities.all, + }, + read: { + api: apiTags.read, + app: [CASES_FEATURE_ID, 'kibana'], + catalogue: [APP_ID], + cases: { + read: [APP_ID], + }, + savedObject: { + all: [], + read: [...savedObjects.files], + }, + ui: uiCapabilities.read, + }, + }, + }; +}; diff --git a/x-pack/packages/security-solution/features/src/cases/v3_features/kibana_sub_features.ts b/x-pack/packages/security-solution/features/src/cases/v3_features/kibana_sub_features.ts new file mode 100644 index 0000000000000..1926a25b77a97 --- /dev/null +++ b/x-pack/packages/security-solution/features/src/cases/v3_features/kibana_sub_features.ts @@ -0,0 +1,206 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import type { SubFeatureConfig } from '@kbn/features-plugin/common'; +import { CasesSubFeatureId } from '../../product_features_keys'; +import { APP_ID } from '../../constants'; +import type { CasesFeatureParams } from '../types'; + +/** + * Sub-features that will always be available for Security Cases + * regardless of the product type. + */ +export const getCasesBaseKibanaSubFeatureIdsV3 = (): CasesSubFeatureId[] => [ + CasesSubFeatureId.deleteCases, + CasesSubFeatureId.casesSettings, + CasesSubFeatureId.createComment, + CasesSubFeatureId.reopenCase, + CasesSubFeatureId.assignUsers, +]; + +/** + * Defines all the Security Solution Cases subFeatures available. + * The order of the subFeatures is the order they will be displayed + */ +export const getCasesSubFeaturesMapV3 = ({ + uiCapabilities, + apiTags, + savedObjects, +}: CasesFeatureParams) => { + const deleteCasesSubFeature: SubFeatureConfig = { + name: i18n.translate('securitySolutionPackages.features.featureRegistry.deleteSubFeatureName', { + defaultMessage: 'Delete', + }), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + api: apiTags.delete, + id: 'cases_delete', + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.deleteSubFeatureDetails', + { + defaultMessage: 'Delete cases and comments', + } + ), + includeIn: 'all', + savedObject: { + all: [...savedObjects.files], + read: [...savedObjects.files], + }, + cases: { + delete: [APP_ID], + }, + ui: uiCapabilities.delete, + }, + ], + }, + ], + }; + + const casesSettingsCasesSubFeature: SubFeatureConfig = { + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.casesSettingsSubFeatureName', + { + defaultMessage: 'Case settings', + } + ), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + id: 'cases_settings', + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.casesSettingsSubFeatureDetails', + { + defaultMessage: 'Edit case settings', + } + ), + includeIn: 'all', + savedObject: { + all: [...savedObjects.files], + read: [...savedObjects.files], + }, + cases: { + settings: [APP_ID], + }, + ui: uiCapabilities.settings, + }, + ], + }, + ], + }; + + /* The below sub features were newly added in v2 (8.17) */ + + const casesAddCommentsCasesSubFeature: SubFeatureConfig = { + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.addCommentsSubFeatureName', + { + defaultMessage: 'Create comments & attachments', + } + ), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + api: apiTags.createComment, + id: 'create_comment', + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.addCommentsSubFeatureDetails', + { + defaultMessage: 'Add comments to cases', + } + ), + includeIn: 'all', + savedObject: { + all: [...savedObjects.files], + read: [...savedObjects.files], + }, + cases: { + createComment: [APP_ID], + }, + ui: uiCapabilities.createComment, + }, + ], + }, + ], + }; + const casesreopenCaseSubFeature: SubFeatureConfig = { + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.reopenCaseSubFeatureName', + { + defaultMessage: 'Re-open', + } + ), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + id: 'case_reopen', + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.reopenCaseSubFeatureDetails', + { + defaultMessage: 'Re-open closed cases', + } + ), + includeIn: 'all', + savedObject: { + all: [], + read: [], + }, + cases: { + reopenCase: [APP_ID], + }, + ui: uiCapabilities.reopenCase, + }, + ], + }, + ], + }; + + const casesAssignUsersCasesSubFeature: SubFeatureConfig = { + name: i18n.translate('xpack.observability.features.assignUsersSubFeatureName', { + defaultMessage: 'Assign users', + }), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + id: 'case_assign', + name: i18n.translate('xpack.observability.features.assignUsersSubFeatureName', { + defaultMessage: 'Assign users to cases', + }), + includeIn: 'all', + savedObject: { + all: [], + read: [], + }, + cases: { + assignCase: [APP_ID], + }, + ui: uiCapabilities.assignCase, + }, + ], + }, + ], + }; + + return new Map([ + [CasesSubFeatureId.deleteCases, deleteCasesSubFeature], + [CasesSubFeatureId.casesSettings, casesSettingsCasesSubFeature], + [CasesSubFeatureId.createComment, casesAddCommentsCasesSubFeature], + [CasesSubFeatureId.reopenCase, casesreopenCaseSubFeature], + [CasesSubFeatureId.assignUsers, casesAssignUsersCasesSubFeature], + ]); +}; diff --git a/x-pack/packages/security-solution/features/src/constants.ts b/x-pack/packages/security-solution/features/src/constants.ts index c6acab28c4860..24ca28738fe4d 100644 --- a/x-pack/packages/security-solution/features/src/constants.ts +++ b/x-pack/packages/security-solution/features/src/constants.ts @@ -17,6 +17,9 @@ export const CASES_FEATURE_ID = 'securitySolutionCases' as const; // New version created in 8.17 to adopt the roles migration changes export const CASES_FEATURE_ID_V2 = 'securitySolutionCasesV2' as const; +// New version created in 8.18 for case assignees +export const CASES_FEATURE_ID_V3 = 'securitySolutionCasesV3' as const; + export const SECURITY_SOLUTION_CASES_APP_ID = 'securitySolutionCases' as const; export const ASSISTANT_FEATURE_ID = 'securitySolutionAssistant' as const; diff --git a/x-pack/packages/security-solution/features/src/product_features_keys.ts b/x-pack/packages/security-solution/features/src/product_features_keys.ts index 42a190b189234..250f46ec53fc3 100644 --- a/x-pack/packages/security-solution/features/src/product_features_keys.ts +++ b/x-pack/packages/security-solution/features/src/product_features_keys.ts @@ -150,6 +150,7 @@ export enum CasesSubFeatureId { casesSettings = 'casesSettingsSubFeature', createComment = 'createCommentSubFeature', reopenCase = 'reopenCaseSubFeature', + assignUsers = 'assignUsersSubFeature', } /** Sub-features IDs for Security Assistant */ diff --git a/x-pack/plugins/cases/public/common/mock/permissions.ts b/x-pack/plugins/cases/public/common/mock/permissions.ts index 9e08120a8c275..ecde960bfe0fc 100644 --- a/x-pack/plugins/cases/public/common/mock/permissions.ts +++ b/x-pack/plugins/cases/public/common/mock/permissions.ts @@ -19,6 +19,7 @@ export const noCasesPermissions = () => settings: false, createComment: false, reopenCase: false, + assignCase: false, }); export const readCasesPermissions = () => @@ -32,6 +33,7 @@ export const readCasesPermissions = () => settings: false, createComment: false, reopenCase: false, + assignCase: false, }); export const noCreateCasesPermissions = () => buildCasesPermissions({ create: false }); export const noCreateCommentCasesPermissions = () => @@ -51,6 +53,7 @@ export const onlyCreateCommentPermissions = () => push: false, createComment: true, reopenCase: false, + assignCase: false, }); export const onlyDeleteCasesPermission = () => buildCasesPermissions({ @@ -61,6 +64,7 @@ export const onlyDeleteCasesPermission = () => push: false, createComment: false, reopenCase: false, + assignCase: false, }); // In practice, a real life user should never have this configuration, but testing for thoroughness export const onlyReopenCasesPermission = () => @@ -72,6 +76,7 @@ export const onlyReopenCasesPermission = () => push: false, createComment: false, reopenCase: true, + assignCase: false, }); export const noConnectorsCasePermission = () => buildCasesPermissions({ connectors: false }); export const noCasesSettingsPermission = () => buildCasesPermissions({ settings: false }); @@ -87,6 +92,7 @@ export const buildCasesPermissions = (overrides: Partial cases_settings: false, create_comment: false, case_reopen: false, + case_assign: false, }); export const readCasesCapabilities = () => buildCasesCapabilities({ @@ -134,6 +143,7 @@ export const readCasesCapabilities = () => cases_settings: false, create_comment: false, case_reopen: false, + case_assign: false, }); export const writeCasesCapabilities = () => { return buildCasesCapabilities({ @@ -152,5 +162,6 @@ export const buildCasesCapabilities = (overrides?: Partial) = cases_settings: overrides?.cases_settings ?? true, create_comment: overrides?.create_comment ?? true, case_reopen: overrides?.case_reopen ?? true, + case_assign: overrides?.case_assign ?? true, }; }; diff --git a/x-pack/plugins/cases/public/components/actions/use_items_action.tsx b/x-pack/plugins/cases/public/components/actions/use_items_action.tsx index 238aec45781c0..3ccb596bc8466 100644 --- a/x-pack/plugins/cases/public/components/actions/use_items_action.tsx +++ b/x-pack/plugins/cases/public/components/actions/use_items_action.tsx @@ -33,7 +33,7 @@ export const useItemsAction = ({ const { permissions } = useCasesContext(); const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); const [selectedCasesToEdit, setSelectedCasesToEdit] = useState([]); - const canUpdateStatus = permissions.update; + const canUpdateStatus = permissions.update || permissions.assignCase; const isActionDisabled = isDisabled || !canUpdateStatus; const onFlyoutClosed = useCallback(() => setIsFlyoutOpen(false), []); diff --git a/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx index e98926bcd0b40..c9f1b53cf112f 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx @@ -396,6 +396,7 @@ describe('useActions', () => { connectors: true, settings: false, createComment: false, + assignCase: false, }, }); @@ -431,6 +432,7 @@ describe('useActions', () => { connectors: true, settings: false, createComment: false, + assignCase: false, }, }); @@ -466,6 +468,7 @@ describe('useActions', () => { connectors: true, settings: false, createComment: false, + assignCase: false, }, }); diff --git a/x-pack/plugins/cases/public/components/all_cases/use_actions.tsx b/x-pack/plugins/cases/public/components/all_cases/use_actions.tsx index e34f64a2a6283..f596cff11579f 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_actions.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_actions.tsx @@ -38,6 +38,7 @@ const ActionColumnComponent: React.FC<{ theCase: CaseUI; disableActions: boolean const tooglePopover = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]); const closePopover = useCallback(() => setIsPopoverOpen(false), []); const refreshCases = useRefreshCases(); + const { permissions } = useCasesContext(); const shouldDisable = useShouldDisableStatus(); @@ -83,6 +84,7 @@ const ActionColumnComponent: React.FC<{ theCase: CaseUI; disableActions: boolean const canDelete = deleteAction.canDelete; const canUpdate = statusAction.canUpdateStatus; + const canAssign = permissions.assignCase; const panels = useMemo((): EuiContextMenuPanelDescriptor[] => { const mainPanelItems: EuiContextMenuPanelItemDescriptor[] = []; @@ -136,6 +138,9 @@ const ActionColumnComponent: React.FC<{ theCase: CaseUI; disableActions: boolean if (canUpdate) { mainPanelItems.push(tagsAction.getAction([theCase])); + } + + if (canAssign) { mainPanelItems.push(assigneesAction.getAction([theCase])); } @@ -164,6 +169,7 @@ const ActionColumnComponent: React.FC<{ theCase: CaseUI; disableActions: boolean }, [ assigneesAction, canDelete, + canAssign, canUpdate, copyIDAction, deleteAction, @@ -242,7 +248,8 @@ interface UseBulkActionsProps { export const useActions = ({ disableActions }: UseBulkActionsProps): UseBulkActionsReturnValue => { const { permissions } = useCasesContext(); - const shouldShowActions = permissions.update || permissions.delete || permissions.reopenCase; + const shouldShowActions = + permissions.update || permissions.delete || permissions.reopenCase || permissions.assignCase; return { actions: shouldShowActions diff --git a/x-pack/plugins/cases/public/components/all_cases/use_bulk_actions.tsx b/x-pack/plugins/cases/public/components/all_cases/use_bulk_actions.tsx index 98828b00369f5..98adead85250b 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_bulk_actions.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_bulk_actions.tsx @@ -18,6 +18,7 @@ import { useStatusAction } from '../actions/status/use_status_action'; import { EditTagsFlyout } from '../actions/tags/edit_tags_flyout'; import { useTagsAction } from '../actions/tags/use_tags_action'; import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; +import { useCasesContext } from '../cases_context/use_cases_context'; import { useAssigneesAction } from '../actions/assignees/use_assignees_action'; import { EditAssigneesFlyout } from '../actions/assignees/edit_assignees_flyout'; import * as i18n from './translations'; @@ -40,6 +41,7 @@ export const useBulkActions = ({ onActionSuccess, }: UseBulkActionsProps): UseBulkActionsReturnValue => { const isDisabled = selectedCases.length === 0; + const { permissions } = useCasesContext(); const deleteAction = useDeleteAction({ isDisabled, @@ -73,6 +75,7 @@ export const useBulkActions = ({ const canDelete = deleteAction.canDelete; const canUpdate = statusAction.canUpdateStatus; + const canAssign = permissions.assignCase; const panels = useMemo((): EuiContextMenuPanelDescriptor[] => { const mainPanelItems: EuiContextMenuPanelItemDescriptor[] = []; @@ -110,6 +113,9 @@ export const useBulkActions = ({ if (canUpdate) { mainPanelItems.push(tagsAction.getAction(selectedCases)); + } + + if (canAssign) { mainPanelItems.push(assigneesAction.getAction(selectedCases)); } @@ -141,6 +147,7 @@ export const useBulkActions = ({ }, [ canDelete, canUpdate, + canAssign, deleteAction, isDisabled, selectedCases, diff --git a/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx b/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx index 389de5068ed51..1e8a15017d806 100644 --- a/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx @@ -95,7 +95,10 @@ export const CasesTableUtilityBar: FunctionComponent = React.memo( * in the useBulkActions hook. */ const showBulkActions = - (permissions.update || permissions.delete || permissions.reopenCase) && + (permissions.update || + permissions.delete || + permissions.reopenCase || + permissions.assignCase) && selectedCases.length > 0; const visibleCases = diff --git a/x-pack/plugins/cases/public/utils/permissions.ts b/x-pack/plugins/cases/public/utils/permissions.ts index 29aba5648abd9..c96f6ef4f64d1 100644 --- a/x-pack/plugins/cases/public/utils/permissions.ts +++ b/x-pack/plugins/cases/public/utils/permissions.ts @@ -14,6 +14,7 @@ export const isReadOnlyPermissions = (permissions: CasesPermissions) => { !permissions.update && !permissions.delete && !permissions.push && + !permissions.assignCase && permissions.read ); }; diff --git a/x-pack/plugins/cases/server/client/cases/bulk_update.test.ts b/x-pack/plugins/cases/server/client/cases/bulk_update.test.ts index 5cdd4c943b944..7a159aceb1f75 100644 --- a/x-pack/plugins/cases/server/client/cases/bulk_update.test.ts +++ b/x-pack/plugins/cases/server/client/cases/bulk_update.test.ts @@ -20,7 +20,7 @@ import { import { mockCases } from '../../mocks'; import { createCasesClientMock, createCasesClientMockArgs } from '../mocks'; import { Operations } from '../../authorization'; -import { bulkUpdate } from './bulk_update'; +import { bulkUpdate, getOperationsToAuthorize } from './bulk_update'; describe('update', () => { const cases = { @@ -315,6 +315,50 @@ describe('update', () => { 'Failed to update case, ids: [{"id":"mock-id-1","version":"WzAsMV0="}]: Error: The length of the field assignees is too long. Array must be of length <= 10.' ); }); + + it('returns updateCase operation when no reopened cases or changed assignees', () => { + const operations = getOperationsToAuthorize([], []); + expect(operations).toEqual([Operations.updateCase]); + }); + + it('returns reopenCase operation when there are reopened cases', () => { + const reopenedCases = [ + { + id: 'case-1', + version: '1', + }, + ]; + const operations = getOperationsToAuthorize(reopenedCases, []); + expect(operations).toEqual([Operations.reopenCase]); + }); + + it('returns assignCase operation when there are changed assignees', () => { + const changedAssignees = [ + { + id: 'case-1', + version: '1', + }, + ]; + const operations = getOperationsToAuthorize([], changedAssignees); + expect(operations).toEqual([Operations.assignCase]); + }); + + it('returns both reopenCase and assignCase operations when both arrays have cases', () => { + const reopenedCases = [ + { + id: 'case-1', + version: '1', + }, + ]; + const changedAssignees = [ + { + id: 'case-2', + version: '1', + }, + ]; + const operations = getOperationsToAuthorize(reopenedCases, changedAssignees); + expect(operations).toEqual([Operations.reopenCase, Operations.assignCase]); + }); }); describe('Category', () => { @@ -1725,7 +1769,7 @@ describe('update', () => { }); }); - it('checks authorization for both reopenCase and updateCase operations when reopening a case', async () => { + it('checks authorization for only reopenCase', async () => { // Mock a closed case const closedCase = { ...mockCases[0], @@ -1757,7 +1801,7 @@ describe('update', () => { expect(clientArgs.authorization.ensureAuthorized).toHaveBeenCalledWith({ entities: [{ id: closedCase.id, owner: closedCase.attributes.owner }], - operation: [Operations.reopenCase, Operations.updateCase], + operation: [Operations.reopenCase], }); }); diff --git a/x-pack/plugins/cases/server/client/cases/bulk_update.ts b/x-pack/plugins/cases/server/client/cases/bulk_update.ts index 1e116d0b57b2e..de6c940a898da 100644 --- a/x-pack/plugins/cases/server/client/cases/bulk_update.ts +++ b/x-pack/plugins/cases/server/client/cases/bulk_update.ts @@ -323,7 +323,7 @@ function partitionPatchRequest( }; } -function getOperationsToAuthorize( +export function getOperationsToAuthorize( reopenedCases: CasePatchRequest[], changedAssignees: CasePatchRequest[] ): OperationDetails[] { diff --git a/x-pack/plugins/observability_solution/observability/common/index.ts b/x-pack/plugins/observability_solution/observability/common/index.ts index f43090d799fdf..53279ee33d0d4 100644 --- a/x-pack/plugins/observability_solution/observability/common/index.ts +++ b/x-pack/plugins/observability_solution/observability/common/index.ts @@ -64,6 +64,7 @@ export { /** @deprecated deprecated in 8.17. Please use casesFeatureIdV2 instead */ export const casesFeatureId = 'observabilityCases'; export const casesFeatureIdV2 = 'observabilityCasesV2'; +export const casesFeatureIdV3 = 'observabilityCasesV3'; export const sloFeatureId = 'slo'; // The ID of the observability app. Should more appropriately be called // 'observability' but it's used in telemetry by applicationUsage so we don't diff --git a/x-pack/plugins/observability_solution/observability/server/features/cases_v1.ts b/x-pack/plugins/observability_solution/observability/server/features/cases_v1.ts index 836d1f44731e9..632da9a767f4b 100644 --- a/x-pack/plugins/observability_solution/observability/server/features/cases_v1.ts +++ b/x-pack/plugins/observability_solution/observability/server/features/cases_v1.ts @@ -10,7 +10,7 @@ import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/s import { i18n } from '@kbn/i18n'; import { KibanaFeatureConfig, KibanaFeatureScope } from '@kbn/features-plugin/common'; import { CasesUiCapabilities, CasesApiTags } from '@kbn/cases-plugin/common'; -import { casesFeatureId, casesFeatureIdV2, observabilityFeatureId } from '../../common'; +import { casesFeatureId, casesFeatureIdV3, observabilityFeatureId } from '../../common'; export const getCasesFeature = ( casesCapabilities: CasesUiCapabilities, @@ -25,7 +25,7 @@ export const getCasesFeature = ( 'The {currentId} permissions are deprecated, please see {casesFeatureIdV2}.', values: { currentId: casesFeatureId, - casesFeatureIdV2, + casesFeatureIdV3, }, } ), @@ -59,10 +59,10 @@ export const getCasesFeature = ( }, ui: casesCapabilities.all, replacedBy: { - default: [{ feature: casesFeatureIdV2, privileges: ['all'] }], + default: [{ feature: casesFeatureIdV3, privileges: ['all'] }], minimal: [ { - feature: casesFeatureIdV2, + feature: casesFeatureIdV3, privileges: ['minimal_all', 'create_comment', 'case_reopen'], }, ], @@ -81,8 +81,8 @@ export const getCasesFeature = ( }, ui: casesCapabilities.read, replacedBy: { - default: [{ feature: casesFeatureIdV2, privileges: ['read'] }], - minimal: [{ feature: casesFeatureIdV2, privileges: ['minimal_read'] }], + default: [{ feature: casesFeatureIdV3, privileges: ['read'] }], + minimal: [{ feature: casesFeatureIdV3, privileges: ['minimal_read'] }], }, }, }, @@ -110,7 +110,7 @@ export const getCasesFeature = ( delete: [observabilityFeatureId], }, ui: casesCapabilities.delete, - replacedBy: [{ feature: casesFeatureIdV2, privileges: ['cases_delete'] }], + replacedBy: [{ feature: casesFeatureIdV3, privileges: ['cases_delete'] }], }, ], }, @@ -141,7 +141,7 @@ export const getCasesFeature = ( settings: [observabilityFeatureId], }, ui: casesCapabilities.settings, - replacedBy: [{ feature: casesFeatureIdV2, privileges: ['cases_settings'] }], + replacedBy: [{ feature: casesFeatureIdV3, privileges: ['cases_settings'] }], }, ], }, diff --git a/x-pack/plugins/observability_solution/observability/server/features/cases_v2.ts b/x-pack/plugins/observability_solution/observability/server/features/cases_v2.ts index 52b501a62bb2e..9dab9076fa31b 100644 --- a/x-pack/plugins/observability_solution/observability/server/features/cases_v2.ts +++ b/x-pack/plugins/observability_solution/observability/server/features/cases_v2.ts @@ -10,12 +10,26 @@ import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/s import { i18n } from '@kbn/i18n'; import { KibanaFeatureConfig, KibanaFeatureScope } from '@kbn/features-plugin/common'; import { CasesUiCapabilities, CasesApiTags } from '@kbn/cases-plugin/common'; -import { casesFeatureIdV2, casesFeatureId, observabilityFeatureId } from '../../common'; +import { + casesFeatureIdV2, + casesFeatureId, + observabilityFeatureId, + casesFeatureIdV3, +} from '../../common'; export const getCasesFeatureV2 = ( casesCapabilities: CasesUiCapabilities, casesApiTags: CasesApiTags ): KibanaFeatureConfig => ({ + deprecated: { + notice: i18n.translate('xpack.observability.featureRegistry.casesFeature.deprecationMessage', { + defaultMessage: 'The {currentId} permissions are deprecated, please see {casesFeatureIdV2}.', + values: { + currentId: casesFeatureIdV2, + casesFeatureIdV2: casesFeatureIdV3, + }, + }), + }, id: casesFeatureIdV2, name: i18n.translate('xpack.observability.featureRegistry.linkObservabilityTitle', { defaultMessage: 'Cases', @@ -42,6 +56,15 @@ export const getCasesFeatureV2 = ( read: [...filesSavedObjectTypes], }, ui: casesCapabilities.all, + replacedBy: { + default: [{ feature: casesFeatureIdV3, privileges: ['all'] }], + minimal: [ + { + feature: casesFeatureIdV3, + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'case_assign'], + }, + ], + }, }, read: { api: casesApiTags.read, @@ -55,6 +78,10 @@ export const getCasesFeatureV2 = ( read: [...filesSavedObjectTypes], }, ui: casesCapabilities.read, + replacedBy: { + default: [{ feature: casesFeatureIdV3, privileges: ['read'] }], + minimal: [{ feature: casesFeatureIdV3, privileges: ['minimal_read'] }], + }, }, }, subFeatures: [ @@ -81,6 +108,7 @@ export const getCasesFeatureV2 = ( delete: [observabilityFeatureId], }, ui: casesCapabilities.delete, + replacedBy: [{ feature: casesFeatureIdV3, privileges: ['cases_delete'] }], }, ], }, @@ -111,6 +139,7 @@ export const getCasesFeatureV2 = ( settings: [observabilityFeatureId], }, ui: casesCapabilities.settings, + replacedBy: [{ feature: casesFeatureIdV3, privileges: ['cases_settings'] }], }, ], }, @@ -142,6 +171,7 @@ export const getCasesFeatureV2 = ( createComment: [observabilityFeatureId], }, ui: casesCapabilities.createComment, + replacedBy: [{ feature: casesFeatureIdV3, privileges: ['create_comment'] }], }, ], }, @@ -172,6 +202,7 @@ export const getCasesFeatureV2 = ( reopenCase: [observabilityFeatureId], }, ui: casesCapabilities.reopenCase, + replacedBy: [{ feature: casesFeatureIdV3, privileges: ['case_reopen'] }], }, ], }, diff --git a/x-pack/plugins/observability_solution/observability/server/features/cases_v3.ts b/x-pack/plugins/observability_solution/observability/server/features/cases_v3.ts new file mode 100644 index 0000000000000..eae54e51cbd16 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability/server/features/cases_v3.ts @@ -0,0 +1,208 @@ +/* + * 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 { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; +import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects'; +import { i18n } from '@kbn/i18n'; +import { KibanaFeatureConfig, KibanaFeatureScope } from '@kbn/features-plugin/common'; +import { CasesUiCapabilities, CasesApiTags } from '@kbn/cases-plugin/common'; +import { casesFeatureIdV3, casesFeatureId, observabilityFeatureId } from '../../common'; + +export const getCasesFeatureV3 = ( + casesCapabilities: CasesUiCapabilities, + casesApiTags: CasesApiTags +): KibanaFeatureConfig => ({ + id: casesFeatureIdV3, + name: i18n.translate('xpack.observability.featureRegistry.linkObservabilityTitle', { + defaultMessage: 'Cases', + }), + order: 1100, + category: DEFAULT_APP_CATEGORIES.observability, + scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security], + app: [casesFeatureId, 'kibana'], + catalogue: [observabilityFeatureId], + cases: [observabilityFeatureId], + privileges: { + all: { + api: casesApiTags.all, + app: [casesFeatureId, 'kibana'], + catalogue: [observabilityFeatureId], + cases: { + create: [observabilityFeatureId], + read: [observabilityFeatureId], + update: [observabilityFeatureId], + push: [observabilityFeatureId], + }, + savedObject: { + all: [...filesSavedObjectTypes], + read: [...filesSavedObjectTypes], + }, + ui: casesCapabilities.all, + }, + read: { + api: casesApiTags.read, + app: [casesFeatureId, 'kibana'], + catalogue: [observabilityFeatureId], + cases: { + read: [observabilityFeatureId], + }, + savedObject: { + all: [], + read: [...filesSavedObjectTypes], + }, + ui: casesCapabilities.read, + }, + }, + subFeatures: [ + { + name: i18n.translate('xpack.observability.featureRegistry.deleteSubFeatureName', { + defaultMessage: 'Delete', + }), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + api: casesApiTags.delete, + id: 'cases_delete', + name: i18n.translate('xpack.observability.featureRegistry.deleteSubFeatureDetails', { + defaultMessage: 'Delete cases and comments', + }), + includeIn: 'all', + savedObject: { + all: [...filesSavedObjectTypes], + read: [...filesSavedObjectTypes], + }, + cases: { + delete: [observabilityFeatureId], + }, + ui: casesCapabilities.delete, + }, + ], + }, + ], + }, + { + name: i18n.translate('xpack.observability.featureRegistry.casesSettingsSubFeatureName', { + defaultMessage: 'Case settings', + }), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + id: 'cases_settings', + name: i18n.translate( + 'xpack.observability.featureRegistry.casesSettingsSubFeatureDetails', + { + defaultMessage: 'Edit case settings', + } + ), + includeIn: 'all', + savedObject: { + all: [...filesSavedObjectTypes], + read: [...filesSavedObjectTypes], + }, + cases: { + settings: [observabilityFeatureId], + }, + ui: casesCapabilities.settings, + }, + ], + }, + ], + }, + { + name: i18n.translate('xpack.observability.featureRegistry.addCommentsSubFeatureName', { + defaultMessage: 'Create comments & attachments', + }), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + api: casesApiTags.createComment, + id: 'create_comment', + name: i18n.translate( + 'xpack.observability.featureRegistry.addCommentsSubFeatureDetails', + { + defaultMessage: 'Add comments to cases', + } + ), + includeIn: 'all', + savedObject: { + all: [...filesSavedObjectTypes], + read: [...filesSavedObjectTypes], + }, + cases: { + createComment: [observabilityFeatureId], + }, + ui: casesCapabilities.createComment, + }, + ], + }, + ], + }, + { + name: i18n.translate('xpack.observability.featureRegistry.reopenCaseSubFeatureName', { + defaultMessage: 'Re-open', + }), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + id: 'case_reopen', + name: i18n.translate( + 'xpack.observability.featureRegistry.reopenCaseSubFeatureDetails', + { + defaultMessage: 'Re-open closed cases', + } + ), + includeIn: 'all', + savedObject: { + all: [], + read: [], + }, + cases: { + reopenCase: [observabilityFeatureId], + }, + ui: casesCapabilities.reopenCase, + }, + ], + }, + ], + }, + { + name: i18n.translate('xpack.observability.features.assignUsersSubFeatureName', { + defaultMessage: 'Assign users', + }), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + id: 'case_assign', + name: i18n.translate('xpack.observability.features.assignUsersSubFeatureName', { + defaultMessage: 'Assign users to cases', + }), + includeIn: 'all', + savedObject: { + all: [], + read: [], + }, + cases: { + assignCase: [observabilityFeatureId], + }, + ui: casesCapabilities.assignCase, + }, + ], + }, + ], + }, + ], +}); diff --git a/x-pack/plugins/observability_solution/observability/server/plugin.ts b/x-pack/plugins/observability_solution/observability/server/plugin.ts index 4d0e809f882e7..6cee6bd3ad9ca 100644 --- a/x-pack/plugins/observability_solution/observability/server/plugin.ts +++ b/x-pack/plugins/observability_solution/observability/server/plugin.ts @@ -59,6 +59,7 @@ import { AlertDetailsContextualInsightsService } from './services'; import { uiSettings } from './ui_settings'; import { getCasesFeature } from './features/cases_v1'; import { getCasesFeatureV2 } from './features/cases_v2'; +import { getCasesFeatureV3 } from './features/cases_v3'; export type ObservabilityPluginSetup = ReturnType; @@ -113,6 +114,7 @@ export class ObservabilityPlugin implements Plugin { plugins.features.registerKibanaFeature(getCasesFeature(casesCapabilities, casesApiTags)); plugins.features.registerKibanaFeature(getCasesFeatureV2(casesCapabilities, casesApiTags)); + plugins.features.registerKibanaFeature(getCasesFeatureV3(casesCapabilities, casesApiTags)); let annotationsApiPromise: Promise | undefined; diff --git a/x-pack/plugins/observability_solution/observability_shared/common/index.ts b/x-pack/plugins/observability_solution/observability_shared/common/index.ts index a8e26366ab4b3..065744193f0bd 100644 --- a/x-pack/plugins/observability_solution/observability_shared/common/index.ts +++ b/x-pack/plugins/observability_solution/observability_shared/common/index.ts @@ -8,7 +8,7 @@ import { AlertConsumers } from '@kbn/rule-data-utils'; export const observabilityFeatureId = 'observability'; export const observabilityAppId = 'observability-overview'; -export const casesFeatureId = 'observabilityCasesV2'; +export const casesFeatureId = 'observabilityCasesV3'; export const sloFeatureId = 'slo'; // SLO alerts table in slo detail page diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 265af5a47e1fe..0ce896d85d795 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -21,7 +21,7 @@ export const APP_ID = 'securitySolution' as const; export const APP_UI_ID = 'securitySolutionUI' as const; export const ASSISTANT_FEATURE_ID = 'securitySolutionAssistant' as const; export const ATTACK_DISCOVERY_FEATURE_ID = 'securitySolutionAttackDiscovery' as const; -export const CASES_FEATURE_ID = 'securitySolutionCasesV2' as const; +export const CASES_FEATURE_ID = 'securitySolutionCasesV3' as const; export const SERVER_APP_ID = 'siem' as const; export const APP_NAME = 'Security' as const; export const APP_ICON = 'securityAnalyticsApp' as const; diff --git a/x-pack/plugins/security_solution/server/lib/product_features_service/product_features_service.ts b/x-pack/plugins/security_solution/server/lib/product_features_service/product_features_service.ts index 2901734527a93..569bdbacae3ea 100644 --- a/x-pack/plugins/security_solution/server/lib/product_features_service/product_features_service.ts +++ b/x-pack/plugins/security_solution/server/lib/product_features_service/product_features_service.ts @@ -21,6 +21,7 @@ import { getCasesFeature, getSecurityFeature, getCasesV2Feature, + getCasesV3Feature, } from '@kbn/security-solution-features/product_features'; import type { RecursiveReadonly } from '@kbn/utility-types'; import type { ExperimentalFeatures } from '../../../common'; @@ -37,6 +38,7 @@ export class ProductFeaturesService { private securityProductFeatures: ProductFeatures; private casesProductFeatures: ProductFeatures; private casesProductV2Features: ProductFeatures; + private casesProductFeaturesV3: ProductFeatures; private securityAssistantProductFeatures: ProductFeatures; private attackDiscoveryProductFeatures: ProductFeatures; private productFeatures?: Set; @@ -82,6 +84,18 @@ export class ProductFeaturesService { casesV2Feature.baseKibanaSubFeatureIds ); + const casesV3Feature = getCasesV3Feature({ + uiCapabilities: casesUiCapabilities, + apiTags: casesApiTags, + savedObjects: { files: filesSavedObjectTypes }, + }); + this.casesProductFeaturesV3 = new ProductFeatures( + this.logger, + casesV3Feature.subFeaturesMap, + casesV3Feature.baseKibanaFeature, + casesV3Feature.baseKibanaSubFeatureIds + ); + const assistantFeature = getAssistantFeature(this.experimentalFeatures); this.securityAssistantProductFeatures = new ProductFeatures( this.logger, @@ -103,6 +117,7 @@ export class ProductFeaturesService { this.securityProductFeatures.init(featuresSetup); this.casesProductFeatures.init(featuresSetup); this.casesProductV2Features.init(featuresSetup); + this.casesProductFeaturesV3.init(featuresSetup); this.securityAssistantProductFeatures.init(featuresSetup); this.attackDiscoveryProductFeatures.init(featuresSetup); } @@ -114,6 +129,7 @@ export class ProductFeaturesService { const casesProductFeaturesConfig = configurator.cases(); this.casesProductFeatures.setConfig(casesProductFeaturesConfig); this.casesProductV2Features.setConfig(casesProductFeaturesConfig); + this.casesProductFeaturesV3.setConfig(casesProductFeaturesConfig); const securityAssistantProductFeaturesConfig = configurator.securityAssistant(); this.securityAssistantProductFeatures.setConfig(securityAssistantProductFeaturesConfig); diff --git a/x-pack/test/api_integration/apis/cases/common/roles.ts b/x-pack/test/api_integration/apis/cases/common/roles.ts index c50076b301f7c..27c7e60c78ac5 100644 --- a/x-pack/test/api_integration/apis/cases/common/roles.ts +++ b/x-pack/test/api_integration/apis/cases/common/roles.ts @@ -136,6 +136,31 @@ export const secCasesV2All: Role = { }, }; +export const secCasesV3All: Role = { + name: 'sec_cases_v3_all_role_api_int', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + feature: { + siem: ['all'], + securitySolutionCasesV3: ['all'], + actions: ['all'], + actionsSimulators: ['all'], + }, + spaces: ['*'], + }, + ], + }, +}; + export const secCasesV2NoReopenWithCreateComment: Role = { name: 'sec_cases_v2_no_reopen_role_api_int', privileges: { @@ -484,6 +509,31 @@ export const casesV2All: Role = { }, }; +export const casesV3All: Role = { + name: 'cases_v3_all_role_api_int', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + spaces: ['*'], + base: [], + feature: { + generalCasesV3: ['all'], + actions: ['all'], + actionsSimulators: ['all'], + }, + }, + ], + }, +}; + export const casesV2NoReopenWithCreateComment: Role = { name: 'cases_v2_no_reopen_role_api_int', privileges: { @@ -683,6 +733,31 @@ export const obsCasesV2All: Role = { }, }; +export const obsCasesV3All: Role = { + name: 'obs_cases_v3_all_role_api_int', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + spaces: ['*'], + base: [], + feature: { + observabilityCasesV3: ['all'], + actions: ['all'], + actionsSimulators: ['all'], + }, + }, + ], + }, +}; + export const obsCasesV2NoReopenWithCreateComment: Role = { name: 'obs_cases_v2_no_reopen_role_api_int', privileges: { @@ -763,6 +838,7 @@ export const roles = [ secAllCasesNoDelete, secAll, secCasesV2All, + secCasesV3All, secCasesV2NoReopenWithCreateComment, secCasesV2NoCreateCommentWithReopen, secAllSpace1, @@ -777,6 +853,7 @@ export const roles = [ casesNoDelete, casesAll, casesV2All, + casesV3All, casesV2NoReopenWithCreateComment, casesV2NoCreateCommentWithReopen, casesRead, @@ -785,6 +862,7 @@ export const roles = [ obsCasesNoDelete, obsCasesAll, obsCasesV2All, + obsCasesV3All, obsCasesV2NoReopenWithCreateComment, obsCasesV2NoCreateCommentWithReopen, obsCasesRead, diff --git a/x-pack/test/api_integration/apis/cases/common/users.ts b/x-pack/test/api_integration/apis/cases/common/users.ts index d3b05c5d3ddf6..550350971717f 100644 --- a/x-pack/test/api_integration/apis/cases/common/users.ts +++ b/x-pack/test/api_integration/apis/cases/common/users.ts @@ -9,18 +9,21 @@ import { User } from '../../../../cases_api_integration/common/lib/authenticatio import { casesAll, casesV2All, + casesV3All, casesNoDelete, casesOnlyDelete, casesOnlyReadDelete, casesRead, obsCasesAll, obsCasesV2All, + obsCasesV3All, obsCasesNoDelete, obsCasesOnlyDelete, obsCasesOnlyReadDelete, obsCasesRead, secAll, secCasesV2All, + secCasesV3All, secAllCasesNoDelete, secAllCasesNone, secAllCasesOnlyDelete, @@ -73,6 +76,12 @@ export const secCasesV2AllUser: User = { roles: [secCasesV2All.name], }; +export const secCasesV3AllUser: User = { + username: 'sec_cases_v3_all_user_api_int', + password: 'password', + roles: [secCasesV3All.name], +}; + export const secCasesV2NoReopenWithCreateCommentUser: User = { username: 'sec_cases_v2_no_reopen_with_create_comment_user_api_int', password: 'password', @@ -161,6 +170,12 @@ export const casesV2AllUser: User = { roles: [casesV2All.name], }; +export const casesV3AllUser: User = { + username: 'cases_v3_all_user_api_int', + password: 'password', + roles: [casesV3All.name], +}; + export const casesV2NoReopenWithCreateCommentUser: User = { username: 'cases_v2_no_reopen_with_create_comment_user_api_int', password: 'password', @@ -213,6 +228,12 @@ export const obsCasesV2AllUser: User = { roles: [obsCasesV2All.name], }; +export const obsCasesV3AllUser: User = { + username: 'obs_cases_v3_all_user_api_int', + password: 'password', + roles: [obsCasesV3All.name], +}; + export const obsCasesV2NoReopenWithCreateCommentUser: User = { username: 'obs_cases_v2_no_reopen_with_create_comment_user_api_int', password: 'password', @@ -253,6 +274,7 @@ export const users = [ secAllCasesNoDeleteUser, secAllUser, secCasesV2AllUser, + secCasesV3AllUser, secCasesV2NoReopenWithCreateCommentUser, secCasesV2NoCreateCommentWithReopenUser, secAllSpace1User, @@ -267,6 +289,7 @@ export const users = [ casesNoDeleteUser, casesAllUser, casesV2AllUser, + casesV3AllUser, casesV2NoReopenWithCreateCommentUser, casesV2NoCreateCommentWithReopenUser, casesReadUser, @@ -275,6 +298,7 @@ export const users = [ obsCasesNoDeleteUser, obsCasesAllUser, obsCasesV2AllUser, + obsCasesV3AllUser, obsCasesV2NoReopenWithCreateCommentUser, obsCasesV2NoCreateCommentWithReopenUser, obsCasesReadUser, diff --git a/x-pack/test/api_integration/apis/cases/privileges.ts b/x-pack/test/api_integration/apis/cases/privileges.ts index 4e2baeeffa515..5a0031610fed5 100644 --- a/x-pack/test/api_integration/apis/cases/privileges.ts +++ b/x-pack/test/api_integration/apis/cases/privileges.ts @@ -24,10 +24,12 @@ import { import { casesAllUser, casesV2AllUser, + casesV3AllUser, casesNoDeleteUser, casesOnlyDeleteUser, obsCasesAllUser, obsCasesV2AllUser, + obsCasesV3AllUser, obsCasesNoDeleteUser, obsCasesOnlyDeleteUser, secAllCasesNoDeleteUser, @@ -36,6 +38,7 @@ import { secAllCasesReadUser, secAllUser, secCasesV2AllUser, + secCasesV3AllUser, secReadCasesAllUser, secReadCasesNoneUser, secReadCasesReadUser, @@ -69,6 +72,9 @@ export default ({ getService }: FtrProviderContext): void => { { user: obsCasesAllUser, owner: OBSERVABILITY_APP_ID }, { user: obsCasesV2AllUser, owner: OBSERVABILITY_APP_ID }, { user: obsCasesNoDeleteUser, owner: OBSERVABILITY_APP_ID }, + { user: secCasesV3AllUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: casesV3AllUser, owner: CASES_APP_ID }, + { user: obsCasesV3AllUser, owner: OBSERVABILITY_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can create a case`, async () => { await createCase(supertestWithoutAuth, getPostCaseRequest({ owner }), 200, { @@ -89,6 +95,9 @@ export default ({ getService }: FtrProviderContext): void => { { user: obsCasesAllUser, owner: OBSERVABILITY_APP_ID }, { user: obsCasesV2AllUser, owner: OBSERVABILITY_APP_ID }, { user: obsCasesNoDeleteUser, owner: OBSERVABILITY_APP_ID }, + { user: secCasesV3AllUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: casesV3AllUser, owner: CASES_APP_ID }, + { user: obsCasesV3AllUser, owner: OBSERVABILITY_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can get a case`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); @@ -151,6 +160,9 @@ export default ({ getService }: FtrProviderContext): void => { { user: obsCasesAllUser, owner: OBSERVABILITY_APP_ID }, { user: obsCasesV2AllUser, owner: OBSERVABILITY_APP_ID }, { user: obsCasesOnlyDeleteUser, owner: OBSERVABILITY_APP_ID }, + { user: secCasesV3AllUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: casesV3AllUser, owner: CASES_APP_ID }, + { user: obsCasesV3AllUser, owner: OBSERVABILITY_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can delete a case`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); @@ -192,6 +204,9 @@ export default ({ getService }: FtrProviderContext): void => { { user: casesV2NoCreateCommentWithReopenUser, owner: CASES_APP_ID }, { user: obsCasesV2NoCreateCommentWithReopenUser, owner: OBSERVABILITY_APP_ID }, { user: secCasesV2NoCreateCommentWithReopenUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: secCasesV3AllUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: casesV3AllUser, owner: CASES_APP_ID }, + { user: obsCasesV3AllUser, owner: OBSERVABILITY_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can reopen a case`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); @@ -254,6 +269,9 @@ export default ({ getService }: FtrProviderContext): void => { { user: casesV2NoReopenWithCreateCommentUser, owner: CASES_APP_ID }, { user: obsCasesV2NoReopenWithCreateCommentUser, owner: OBSERVABILITY_APP_ID }, { user: secCasesV2NoReopenWithCreateCommentUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: secCasesV3AllUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: casesV3AllUser, owner: CASES_APP_ID }, + { user: obsCasesV3AllUser, owner: OBSERVABILITY_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can add comments`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); diff --git a/x-pack/test/api_integration/apis/features/features/features.ts b/x-pack/test/api_integration/apis/features/features/features.ts index 4ded1782c9086..c6492a805607f 100644 --- a/x-pack/test/api_integration/apis/features/features/features.ts +++ b/x-pack/test/api_integration/apis/features/features/features.ts @@ -111,7 +111,7 @@ export default function ({ getService }: FtrProviderContext) { 'guidedOnboardingFeature', 'monitoring', 'observabilityAIAssistant', - 'observabilityCasesV2', + 'observabilityCasesV3', 'savedObjectsManagement', 'savedQueryManagement', 'savedObjectsTagging', @@ -119,7 +119,7 @@ export default function ({ getService }: FtrProviderContext) { 'apm', 'stackAlerts', 'canvas', - 'generalCasesV2', + 'generalCasesV3', 'infrastructure', 'inventory', 'logs', @@ -133,7 +133,7 @@ export default function ({ getService }: FtrProviderContext) { 'slo', 'securitySolutionAssistant', 'securitySolutionAttackDiscovery', - 'securitySolutionCasesV2', + 'securitySolutionCasesV3', 'fleet', 'fleetv2', ].sort() diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index dc84f6c33d200..508648e5f5d97 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -40,6 +40,17 @@ export default function ({ getService }: FtrProviderContext) { 'create_comment', 'case_reopen', ], + generalCasesV3: [ + 'all', + 'read', + 'minimal_all', + 'minimal_read', + 'cases_delete', + 'cases_settings', + 'create_comment', + 'case_reopen', + 'case_assign', + ], observabilityCases: [ 'all', 'read', @@ -58,6 +69,17 @@ export default function ({ getService }: FtrProviderContext) { 'create_comment', 'case_reopen', ], + observabilityCasesV3: [ + 'all', + 'read', + 'minimal_all', + 'minimal_read', + 'cases_delete', + 'cases_settings', + 'create_comment', + 'case_reopen', + 'case_assign', + ], observabilityAIAssistant: ['all', 'read', 'minimal_all', 'minimal_read'], slo: ['all', 'read', 'minimal_all', 'minimal_read'], searchInferenceEndpoints: ['all', 'read', 'minimal_all', 'minimal_read'], @@ -126,6 +148,17 @@ export default function ({ getService }: FtrProviderContext) { 'create_comment', 'case_reopen', ], + securitySolutionCasesV3: [ + 'all', + 'read', + 'minimal_all', + 'minimal_read', + 'cases_delete', + 'cases_settings', + 'create_comment', + 'case_reopen', + 'case_assign', + ], infrastructure: ['all', 'read', 'minimal_all', 'minimal_read'], logs: ['all', 'read', 'minimal_all', 'minimal_read'], dataQuality: ['all', 'read', 'minimal_all', 'minimal_read'], diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts index 2bbd70fcf730d..5c9c72616f597 100644 --- a/x-pack/test/api_integration/apis/security/privileges_basic.ts +++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts @@ -33,8 +33,10 @@ export default function ({ getService }: FtrProviderContext) { maps: ['all', 'read', 'minimal_all', 'minimal_read'], generalCases: ['all', 'read', 'minimal_all', 'minimal_read'], generalCasesV2: ['all', 'read', 'minimal_all', 'minimal_read'], + generalCasesV3: ['all', 'read', 'minimal_all', 'minimal_read'], observabilityCases: ['all', 'read', 'minimal_all', 'minimal_read'], observabilityCasesV2: ['all', 'read', 'minimal_all', 'minimal_read'], + observabilityCasesV3: ['all', 'read', 'minimal_all', 'minimal_read'], observabilityAIAssistant: ['all', 'read', 'minimal_all', 'minimal_read'], slo: ['all', 'read', 'minimal_all', 'minimal_read'], canvas: ['all', 'read', 'minimal_all', 'minimal_read'], @@ -50,6 +52,7 @@ export default function ({ getService }: FtrProviderContext) { securitySolutionAttackDiscovery: ['all', 'read', 'minimal_all', 'minimal_read'], securitySolutionCases: ['all', 'read', 'minimal_all', 'minimal_read'], securitySolutionCasesV2: ['all', 'read', 'minimal_all', 'minimal_read'], + securitySolutionCasesV3: ['all', 'read', 'minimal_all', 'minimal_read'], searchInferenceEndpoints: ['all', 'read', 'minimal_all', 'minimal_read'], fleetv2: ['all', 'read', 'minimal_all', 'minimal_read'], fleet: ['all', 'read', 'minimal_all', 'minimal_read'], @@ -125,6 +128,17 @@ export default function ({ getService }: FtrProviderContext) { 'create_comment', 'case_reopen', ], + generalCasesV3: [ + 'all', + 'read', + 'minimal_all', + 'minimal_read', + 'cases_delete', + 'cases_settings', + 'create_comment', + 'case_reopen', + 'case_assign', + ], observabilityCases: [ 'all', 'read', @@ -143,6 +157,17 @@ export default function ({ getService }: FtrProviderContext) { 'create_comment', 'case_reopen', ], + observabilityCasesV3: [ + 'all', + 'read', + 'minimal_all', + 'minimal_read', + 'cases_delete', + 'cases_settings', + 'create_comment', + 'case_reopen', + 'case_assign', + ], observabilityAIAssistant: ['all', 'read', 'minimal_all', 'minimal_read'], slo: ['all', 'read', 'minimal_all', 'minimal_read'], searchInferenceEndpoints: ['all', 'read', 'minimal_all', 'minimal_read'], @@ -211,6 +236,17 @@ export default function ({ getService }: FtrProviderContext) { 'create_comment', 'case_reopen', ], + securitySolutionCasesV3: [ + 'all', + 'read', + 'minimal_all', + 'minimal_read', + 'cases_delete', + 'cases_settings', + 'create_comment', + 'case_reopen', + 'case_assign', + ], infrastructure: ['all', 'read', 'minimal_all', 'minimal_read'], logs: ['all', 'read', 'minimal_all', 'minimal_read'], dataQuality: ['all', 'read', 'minimal_all', 'minimal_read'], diff --git a/x-pack/test/api_integration_basic/apis/security_solution/cases_privileges.ts b/x-pack/test/api_integration_basic/apis/security_solution/cases_privileges.ts index 2a85320d14edf..a1c2eb5f7c03f 100644 --- a/x-pack/test/api_integration_basic/apis/security_solution/cases_privileges.ts +++ b/x-pack/test/api_integration_basic/apis/security_solution/cases_privileges.ts @@ -37,7 +37,7 @@ const secAll: Role = { { feature: { siem: ['all'], - securitySolutionCasesV2: ['all'], + securitySolutionCasesV3: ['all'], actions: ['all'], actionsSimulators: ['all'], }, @@ -68,7 +68,7 @@ const secRead: Role = { { feature: { siem: ['read'], - securitySolutionCasesV2: ['read'], + securitySolutionCasesV3: ['read'], actions: ['all'], actionsSimulators: ['all'], }, diff --git a/x-pack/test/cases_api_integration/common/lib/api/case.ts b/x-pack/test/cases_api_integration/common/lib/api/case.ts index 9f03a62032c89..12a5190300fc0 100644 --- a/x-pack/test/cases_api_integration/common/lib/api/case.ts +++ b/x-pack/test/cases_api_integration/common/lib/api/case.ts @@ -124,3 +124,32 @@ export const updateCaseStatus = async ({ .send(updateRequest); return updatedCase; }; + +export const updateCaseAssignee = async ({ + supertest, + caseId, + version = '2', + assigneeId, + expectedHttpCode = 204, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.Agent; + caseId: string; + version?: string; + assigneeId: string; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}) => { + const updateRequest: CasePatchRequest = { + version, + assignees: [{ uid: assigneeId }], + id: caseId, + }; + + const { body: updatedCase } = await supertest + .patch(`/api/cases/${caseId}`) + .auth(auth.user.username, auth.user.password) + .set('kbn-xsrf', 'xxx') + .send(updateRequest); + return updatedCase; +}; diff --git a/x-pack/test/cases_api_integration/common/plugins/security_solution/server/plugin.ts b/x-pack/test/cases_api_integration/common/plugins/security_solution/server/plugin.ts index 34f4c6d7423c0..c503af124b295 100644 --- a/x-pack/test/cases_api_integration/common/plugins/security_solution/server/plugin.ts +++ b/x-pack/test/cases_api_integration/common/plugins/security_solution/server/plugin.ts @@ -161,6 +161,29 @@ export class FixturePlugin implements Plugin) 'logs', 'observabilityCases', 'observabilityCasesV2', + 'observabilityCasesV3', 'securitySolutionAssistant', 'securitySolutionAttackDiscovery', 'securitySolutionCases', 'securitySolutionCasesV2', + 'securitySolutionCasesV3', 'siem', 'slo', 'uptime', diff --git a/x-pack/test/spaces_api_integration/common/suites/get_all.ts b/x-pack/test/spaces_api_integration/common/suites/get_all.ts index 9d51cbb12e469..f9f6f39f06f8f 100644 --- a/x-pack/test/spaces_api_integration/common/suites/get_all.ts +++ b/x-pack/test/spaces_api_integration/common/suites/get_all.ts @@ -81,10 +81,12 @@ const ALL_SPACE_RESULTS: Space[] = [ 'logs', 'observabilityCases', 'observabilityCasesV2', + 'observabilityCasesV3', 'securitySolutionAssistant', 'securitySolutionAttackDiscovery', 'securitySolutionCases', 'securitySolutionCasesV2', + 'securitySolutionCasesV3', 'siem', 'slo', 'uptime', diff --git a/x-pack/test/spaces_api_integration/spaces_only/telemetry/telemetry.ts b/x-pack/test/spaces_api_integration/spaces_only/telemetry/telemetry.ts index 4a43c3831627c..8af702f4e3829 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/telemetry/telemetry.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/telemetry/telemetry.ts @@ -67,6 +67,7 @@ export default function ({ getService }: FtrProviderContext) { stackAlerts: 0, generalCases: 0, generalCasesV2: 0, + generalCasesV3: 0, maps: 2, canvas: 2, ml: 0, @@ -75,6 +76,7 @@ export default function ({ getService }: FtrProviderContext) { osquery: 0, observabilityCases: 0, observabilityCasesV2: 0, + observabilityCasesV3: 0, uptime: 0, slo: 0, infrastructure: 0, @@ -87,6 +89,7 @@ export default function ({ getService }: FtrProviderContext) { siem: 0, securitySolutionCases: 0, securitySolutionCasesV2: 0, + securitySolutionCasesV3: 0, securitySolutionAssistant: 0, securitySolutionAttackDiscovery: 0, discover: 0, From 86f28295e09a39ddf0cb4d7998e8999d941c93f5 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 10 Dec 2024 18:24:32 +0000 Subject: [PATCH 03/32] Fix tests and i18n warning --- .../src/cases/v1_features/kibana_features.ts | 1 + .../server/client/cases/bulk_update.test.ts | 108 ++++++++++++------ .../cases/server/client/cases/bulk_update.ts | 27 +++-- x-pack/plugins/cases/server/features/v1.ts | 1 + .../observability/server/features/cases_v1.ts | 3 +- .../apis/cases/common/roles.ts | 52 +++++++++ .../apis/cases/common/users.ts | 16 +++ .../api_integration/apis/cases/privileges.ts | 33 ++++++ 8 files changed, 199 insertions(+), 42 deletions(-) diff --git a/x-pack/packages/security-solution/features/src/cases/v1_features/kibana_features.ts b/x-pack/packages/security-solution/features/src/cases/v1_features/kibana_features.ts index 205f8a3e1dc6a..5fdda1c8928c6 100644 --- a/x-pack/packages/security-solution/features/src/cases/v1_features/kibana_features.ts +++ b/x-pack/packages/security-solution/features/src/cases/v1_features/kibana_features.ts @@ -60,6 +60,7 @@ export const getCasesBaseKibanaFeature = ({ push: [APP_ID], createComment: [APP_ID], reopenCase: [APP_ID], + assignCase: [APP_ID], }, savedObject: { all: [...savedObjects.files], diff --git a/x-pack/plugins/cases/server/client/cases/bulk_update.test.ts b/x-pack/plugins/cases/server/client/cases/bulk_update.test.ts index 7a159aceb1f75..283d569626898 100644 --- a/x-pack/plugins/cases/server/client/cases/bulk_update.test.ts +++ b/x-pack/plugins/cases/server/client/cases/bulk_update.test.ts @@ -316,48 +316,88 @@ describe('update', () => { ); }); - it('returns updateCase operation when no reopened cases or changed assignees', () => { - const operations = getOperationsToAuthorize([], []); + it('returns only updateCase operation when no reopened cases or changed assignees', () => { + const operations = getOperationsToAuthorize({ + reopenedCases: [], + changedAssignees: [], + allCases: cases.cases, + }); expect(operations).toEqual([Operations.updateCase]); }); - it('returns reopenCase operation when there are reopened cases', () => { - const reopenedCases = [ - { - id: 'case-1', - version: '1', - }, - ]; - const operations = getOperationsToAuthorize(reopenedCases, []); + it('returns only assignCase operation when all cases are assignee changes', () => { + const operations = getOperationsToAuthorize({ + reopenedCases: [], + changedAssignees: cases.cases, + allCases: cases.cases, + }); + expect(operations).toEqual([Operations.assignCase]); + }); + + it('returns only reopenCase operation when all cases are being reopened', () => { + const operations = getOperationsToAuthorize({ + reopenedCases: cases.cases, + changedAssignees: [], + allCases: cases.cases, + }); expect(operations).toEqual([Operations.reopenCase]); }); - it('returns assignCase operation when there are changed assignees', () => { - const changedAssignees = [ - { - id: 'case-1', - version: '1', - }, - ]; - const operations = getOperationsToAuthorize([], changedAssignees); - expect(operations).toEqual([Operations.assignCase]); + it('returns assignCase and updateCase when some cases have non-assignee changes', () => { + const case2 = { id: 'case-2', version: '1' }; + const operations = getOperationsToAuthorize({ + reopenedCases: [], + changedAssignees: cases.cases, + allCases: [...cases.cases, case2], + }); + expect(operations).toEqual([Operations.assignCase, Operations.updateCase]); }); - it('returns both reopenCase and assignCase operations when both arrays have cases', () => { - const reopenedCases = [ - { - id: 'case-1', - version: '1', - }, - ]; - const changedAssignees = [ - { - id: 'case-2', - version: '1', - }, - ]; - const operations = getOperationsToAuthorize(reopenedCases, changedAssignees); - expect(operations).toEqual([Operations.reopenCase, Operations.assignCase]); + it('returns reopenCase and updateCase when some cases have non-reopen changes', () => { + const case2 = { id: 'case-2', version: '1' }; + const operations = getOperationsToAuthorize({ + reopenedCases: cases.cases, + changedAssignees: [], + allCases: [...cases.cases, case2], + }); + expect(operations).toEqual([Operations.reopenCase, Operations.updateCase]); + }); + + it('returns all operations when cases have mixed changes', () => { + const case2 = { id: 'case-2', version: '1' }; + const case3 = { id: 'case-3', version: '1' }; + const operations = getOperationsToAuthorize({ + reopenedCases: cases.cases, + changedAssignees: [case2], + allCases: [...cases.cases, case2, case3], + }); + expect(operations).toEqual([ + Operations.reopenCase, + Operations.assignCase, + Operations.updateCase, + ]); + }); + + it('handles empty casesToAuthorize array', () => { + const operations = getOperationsToAuthorize({ + reopenedCases: [], + changedAssignees: [], + allCases: [], + }); + expect(operations).toEqual([]); + }); + + it('returns only combined operations when all cases have both reopen and assignee changes', () => { + const operations = getOperationsToAuthorize({ + reopenedCases: cases.cases, + changedAssignees: cases.cases, + allCases: cases.cases, + }); + expect(operations).toEqual([ + Operations.reopenCase, + Operations.assignCase, + Operations.updateCase, + ]); }); }); diff --git a/x-pack/plugins/cases/server/client/cases/bulk_update.ts b/x-pack/plugins/cases/server/client/cases/bulk_update.ts index de6c940a898da..549caabd3e573 100644 --- a/x-pack/plugins/cases/server/client/cases/bulk_update.ts +++ b/x-pack/plugins/cases/server/client/cases/bulk_update.ts @@ -323,11 +323,20 @@ function partitionPatchRequest( }; } -export function getOperationsToAuthorize( - reopenedCases: CasePatchRequest[], - changedAssignees: CasePatchRequest[] -): OperationDetails[] { +export function getOperationsToAuthorize({ + reopenedCases, + changedAssignees, + allCases, +}: { + reopenedCases: CasePatchRequest[]; + changedAssignees: CasePatchRequest[]; + allCases: CasePatchRequest[]; +}): OperationDetails[] { const operations: OperationDetails[] = []; + const onlyAssigneeOperations = + reopenedCases.length === 0 && changedAssignees.length === allCases.length; + const onlyReopenOperations = + changedAssignees.length === 0 && reopenedCases.length === allCases.length; if (reopenedCases.length > 0) { operations.push(Operations.reopenCase); @@ -337,8 +346,8 @@ export function getOperationsToAuthorize( operations.push(Operations.assignCase); } - if (changedAssignees.length === 0 && reopenedCases.length === 0) { - return [Operations.updateCase]; + if (!onlyAssigneeOperations && !onlyReopenOperations) { + operations.push(Operations.updateCase); } return operations; @@ -393,7 +402,11 @@ export const bulkUpdate = async ( const { nonExistingCases, conflictedCases, casesToAuthorize, reopenedCases, changedAssignees } = partitionPatchRequest(casesMap, query.cases); - const operationsToAuthorize = getOperationsToAuthorize(reopenedCases, changedAssignees); + const operationsToAuthorize = getOperationsToAuthorize({ + reopenedCases, + changedAssignees, + allCases: query.cases, + }); await authorization.ensureAuthorized({ entities: casesToAuthorize, diff --git a/x-pack/plugins/cases/server/features/v1.ts b/x-pack/plugins/cases/server/features/v1.ts index 99038760336dc..9e69db119cf99 100644 --- a/x-pack/plugins/cases/server/features/v1.ts +++ b/x-pack/plugins/cases/server/features/v1.ts @@ -61,6 +61,7 @@ export const getV1 = (): KibanaFeatureConfig => { push: [APP_ID], createComment: [APP_ID], reopenCase: [APP_ID], + assignCase: [APP_ID], }, management: { insightsAndAlerting: [APP_ID], diff --git a/x-pack/plugins/observability_solution/observability/server/features/cases_v1.ts b/x-pack/plugins/observability_solution/observability/server/features/cases_v1.ts index 632da9a767f4b..8ce54ff36349a 100644 --- a/x-pack/plugins/observability_solution/observability/server/features/cases_v1.ts +++ b/x-pack/plugins/observability_solution/observability/server/features/cases_v1.ts @@ -22,7 +22,7 @@ export const getCasesFeature = ( 'xpack.observability.featureRegistry.linkObservabilityTitle.deprecationMessage', { defaultMessage: - 'The {currentId} permissions are deprecated, please see {casesFeatureIdV2}.', + 'The {currentId} permissions are deprecated, please see {casesFeatureIdV3}.', values: { currentId: casesFeatureId, casesFeatureIdV3, @@ -52,6 +52,7 @@ export const getCasesFeature = ( push: [observabilityFeatureId], createComment: [observabilityFeatureId], reopenCase: [observabilityFeatureId], + assignCase: [observabilityFeatureId], }, savedObject: { all: [...filesSavedObjectTypes], diff --git a/x-pack/test/api_integration/apis/cases/common/roles.ts b/x-pack/test/api_integration/apis/cases/common/roles.ts index 27c7e60c78ac5..8f5cf95bc095c 100644 --- a/x-pack/test/api_integration/apis/cases/common/roles.ts +++ b/x-pack/test/api_integration/apis/cases/common/roles.ts @@ -534,6 +534,56 @@ export const casesV3All: Role = { }, }; +export const casesV3NoAssignee: Role = { + name: 'cases_v3_no_assignee_role_api_int', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + spaces: ['*'], + base: [], + feature: { + generalCasesV3: ['read', 'update', 'create', 'delete', 'create_comment', 'case_reopen'], + actions: ['all'], + actionsSimulators: ['all'], + }, + }, + ], + }, +}; + +export const casesV3ReadAndAssignee: Role = { + name: 'cases_v3_read_assignee_role_api_int', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + spaces: ['*'], + base: [], + feature: { + generalCasesV3: ['read', 'case_assign'], + actions: ['all'], + actionsSimulators: ['all'], + }, + }, + ], + }, +}; + export const casesV2NoReopenWithCreateComment: Role = { name: 'cases_v2_no_reopen_role_api_int', privileges: { @@ -854,6 +904,8 @@ export const roles = [ casesAll, casesV2All, casesV3All, + casesV3NoAssignee, + casesV3ReadAndAssignee, casesV2NoReopenWithCreateComment, casesV2NoCreateCommentWithReopen, casesRead, diff --git a/x-pack/test/api_integration/apis/cases/common/users.ts b/x-pack/test/api_integration/apis/cases/common/users.ts index 550350971717f..b4f8d3d6c4f5e 100644 --- a/x-pack/test/api_integration/apis/cases/common/users.ts +++ b/x-pack/test/api_integration/apis/cases/common/users.ts @@ -10,6 +10,8 @@ import { casesAll, casesV2All, casesV3All, + casesV3NoAssignee, + casesV3ReadAndAssignee, casesNoDelete, casesOnlyDelete, casesOnlyReadDelete, @@ -176,6 +178,18 @@ export const casesV3AllUser: User = { roles: [casesV3All.name], }; +export const casesV3NoAssigneeUser: User = { + username: 'cases_v3_no_assignee_user_api_int', + password: 'password', + roles: [casesV3NoAssignee.name], +}; + +export const casesV3ReadAndAssignUser: User = { + username: 'cases_v3_read_and_assignee_user_api_int', + password: 'password', + roles: [casesV3ReadAndAssignee.name], +}; + export const casesV2NoReopenWithCreateCommentUser: User = { username: 'cases_v2_no_reopen_with_create_comment_user_api_int', password: 'password', @@ -290,6 +304,8 @@ export const users = [ casesAllUser, casesV2AllUser, casesV3AllUser, + casesV3NoAssigneeUser, + casesV3ReadAndAssignUser, casesV2NoReopenWithCreateCommentUser, casesV2NoCreateCommentWithReopenUser, casesReadUser, diff --git a/x-pack/test/api_integration/apis/cases/privileges.ts b/x-pack/test/api_integration/apis/cases/privileges.ts index 5a0031610fed5..ffe477f74997f 100644 --- a/x-pack/test/api_integration/apis/cases/privileges.ts +++ b/x-pack/test/api_integration/apis/cases/privileges.ts @@ -20,11 +20,14 @@ import { getCase, createComment, updateCaseStatus, + updateCaseAssignee, } from '../../../cases_api_integration/common/lib/api'; import { casesAllUser, casesV2AllUser, casesV3AllUser, + casesV3NoAssigneeUser, + casesV3ReadAndAssignUser, casesNoDeleteUser, casesOnlyDeleteUser, obsCasesAllUser, @@ -290,6 +293,36 @@ export default ({ getService }: FtrProviderContext): void => { }); } + for (const { user, owner } of [{ user: casesV3NoAssigneeUser, owner: CASES_APP_ID }]) { + it(`User ${ + user.username + } with role(s) ${user.roles.join()} CANNOT change assignee`, async () => { + const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); + await updateCaseAssignee({ + supertest: supertestWithoutAuth, + caseId: caseInfo.id, + assigneeId: caseInfo.created_by.profile_uid ?? '', + expectedHttpCode: 403, + auth: { user, space: null }, + }); + }); + } + + for (const { user, owner } of [{ user: casesV3ReadAndAssignUser, owner: CASES_APP_ID }]) { + it(`User ${ + user.username + } with role(s) ${user.roles.join()} CAN change assignee`, async () => { + const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); + await updateCaseAssignee({ + supertest: supertestWithoutAuth, + caseId: caseInfo.id, + assigneeId: caseInfo.created_by.profile_uid ?? '', + expectedHttpCode: 200, + auth: { user, space: null }, + }); + }); + } + for (const { user, owner } of [ { user: casesV2NoCreateCommentWithReopenUser, owner: CASES_APP_ID }, { user: obsCasesV2NoCreateCommentWithReopenUser, owner: OBSERVABILITY_APP_ID }, From fbb8427ef2ad5071a26e5828f702c4855f25e919 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Thu, 12 Dec 2024 21:13:07 +0000 Subject: [PATCH 04/32] Fix types --- .../features/src/cases/v3_features/kibana_sub_features.ts | 4 ++-- .../public/pages/cases/components/cases.stories.tsx | 2 ++ .../observability_shared/public/utils/cases_permissions.ts | 2 ++ .../plugins/security_solution/public/cases_test_utils.ts | 7 +++++++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/x-pack/packages/security-solution/features/src/cases/v3_features/kibana_sub_features.ts b/x-pack/packages/security-solution/features/src/cases/v3_features/kibana_sub_features.ts index 1926a25b77a97..64167fba65376 100644 --- a/x-pack/packages/security-solution/features/src/cases/v3_features/kibana_sub_features.ts +++ b/x-pack/packages/security-solution/features/src/cases/v3_features/kibana_sub_features.ts @@ -169,7 +169,7 @@ export const getCasesSubFeaturesMapV3 = ({ }; const casesAssignUsersCasesSubFeature: SubFeatureConfig = { - name: i18n.translate('xpack.observability.features.assignUsersSubFeatureName', { + name: i18n.translate('securitySolutionPackages.features.assignUsersSubFeatureName', { defaultMessage: 'Assign users', }), privilegeGroups: [ @@ -178,7 +178,7 @@ export const getCasesSubFeaturesMapV3 = ({ privileges: [ { id: 'case_assign', - name: i18n.translate('xpack.observability.features.assignUsersSubFeatureName', { + name: i18n.translate('securitySolutionPackages.features.assignUsersSubFeatureName', { defaultMessage: 'Assign users to cases', }), includeIn: 'all', diff --git a/x-pack/plugins/observability_solution/observability/public/pages/cases/components/cases.stories.tsx b/x-pack/plugins/observability_solution/observability/public/pages/cases/components/cases.stories.tsx index a0fa1368d28f6..ae91737c5b526 100644 --- a/x-pack/plugins/observability_solution/observability/public/pages/cases/components/cases.stories.tsx +++ b/x-pack/plugins/observability_solution/observability/public/pages/cases/components/cases.stories.tsx @@ -30,6 +30,7 @@ const defaultProps: CasesProps = { settings: true, reopenCase: true, createComment: true, + assignCase: true, }, }; @@ -49,5 +50,6 @@ CasesPageWithNoPermissions.args = { settings: false, reopenCase: false, createComment: false, + assignCase: false, }, }; diff --git a/x-pack/plugins/observability_solution/observability_shared/public/utils/cases_permissions.ts b/x-pack/plugins/observability_solution/observability_shared/public/utils/cases_permissions.ts index 0b3699e49b40c..97011f84300e0 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/utils/cases_permissions.ts +++ b/x-pack/plugins/observability_solution/observability_shared/public/utils/cases_permissions.ts @@ -16,6 +16,7 @@ export const noCasesPermissions = () => ({ settings: false, createComment: false, reopenCase: false, + assignCase: false, }); export const allCasesPermissions = () => ({ @@ -29,4 +30,5 @@ export const allCasesPermissions = () => ({ settings: true, createComment: true, reopenCase: true, + assignCase: true, }); diff --git a/x-pack/plugins/security_solution/public/cases_test_utils.ts b/x-pack/plugins/security_solution/public/cases_test_utils.ts index f3c356507bcfe..d88921bf06907 100644 --- a/x-pack/plugins/security_solution/public/cases_test_utils.ts +++ b/x-pack/plugins/security_solution/public/cases_test_utils.ts @@ -17,6 +17,7 @@ export const noCasesCapabilities = (): CasesCapabilities => ({ cases_settings: false, case_reopen: false, create_comment: false, + case_assign: false, }); export const readCasesCapabilities = (): CasesCapabilities => ({ @@ -29,6 +30,7 @@ export const readCasesCapabilities = (): CasesCapabilities => ({ cases_settings: false, case_reopen: false, create_comment: false, + case_assign: false, }); export const allCasesCapabilities = (): CasesCapabilities => ({ @@ -41,6 +43,7 @@ export const allCasesCapabilities = (): CasesCapabilities => ({ cases_settings: true, case_reopen: true, create_comment: true, + case_assign: true, }); export const noCasesPermissions = (): CasesPermissions => ({ @@ -54,6 +57,7 @@ export const noCasesPermissions = (): CasesPermissions => ({ settings: false, reopenCase: false, createComment: false, + assignCase: false, }); export const readCasesPermissions = (): CasesPermissions => ({ @@ -67,6 +71,7 @@ export const readCasesPermissions = (): CasesPermissions => ({ settings: false, reopenCase: false, createComment: false, + assignCase: false, }); export const writeCasesPermissions = (): CasesPermissions => ({ @@ -80,6 +85,7 @@ export const writeCasesPermissions = (): CasesPermissions => ({ settings: true, reopenCase: true, createComment: true, + assignCase: true, }); export const allCasesPermissions = (): CasesPermissions => ({ @@ -93,4 +99,5 @@ export const allCasesPermissions = (): CasesPermissions => ({ settings: true, reopenCase: true, createComment: true, + assignCase: true, }); From d3ac4f281a03f1c67cbcd2af8a537dab4137ad99 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Thu, 12 Dec 2024 22:16:54 +0000 Subject: [PATCH 05/32] Move security cases priv definition to right location --- .../packages}/features/src/cases/v3_features/kibana_features.ts | 0 .../features/src/cases/v3_features/kibana_sub_features.ts | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename x-pack/{packages/security-solution => solutions/security/packages}/features/src/cases/v3_features/kibana_features.ts (100%) rename x-pack/{packages/security-solution => solutions/security/packages}/features/src/cases/v3_features/kibana_sub_features.ts (100%) diff --git a/x-pack/packages/security-solution/features/src/cases/v3_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_features.ts similarity index 100% rename from x-pack/packages/security-solution/features/src/cases/v3_features/kibana_features.ts rename to x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_features.ts diff --git a/x-pack/packages/security-solution/features/src/cases/v3_features/kibana_sub_features.ts b/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts similarity index 100% rename from x-pack/packages/security-solution/features/src/cases/v3_features/kibana_sub_features.ts rename to x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts From 2d0383f20908c40ab7392932945a9324383f4c6d Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Thu, 12 Dec 2024 17:54:31 -0500 Subject: [PATCH 06/32] Fix test types --- .../observability/feature_controls/observability_security.ts | 4 ++-- .../apps/observability/pages/alerts/add_to_case.ts | 4 ++-- .../apps/observability/pages/cases/case_details.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/test/observability_functional/apps/observability/feature_controls/observability_security.ts b/x-pack/test/observability_functional/apps/observability/feature_controls/observability_security.ts index 81fb1d23ba33e..3d15d77cc471d 100644 --- a/x-pack/test/observability_functional/apps/observability/feature_controls/observability_security.ts +++ b/x-pack/test/observability_functional/apps/observability/feature_controls/observability_security.ts @@ -43,7 +43,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); await observability.users.setTestUserRole( observability.users.defineBasicObservabilityRole({ - observabilityCasesV2: ['all'], + observabilityCasesV3: ['all'], logs: ['all'], }) ); @@ -96,7 +96,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); await observability.users.setTestUserRole( observability.users.defineBasicObservabilityRole({ - observabilityCasesV2: ['read'], + observabilityCasesV3: ['read'], logs: ['all'], }) ); diff --git a/x-pack/test/observability_functional/apps/observability/pages/alerts/add_to_case.ts b/x-pack/test/observability_functional/apps/observability/pages/alerts/add_to_case.ts index ccb4264147523..9ce962ea4f4b9 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/alerts/add_to_case.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/alerts/add_to_case.ts @@ -29,7 +29,7 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { before(async () => { await observability.users.setTestUserRole( observability.users.defineBasicObservabilityRole({ - observabilityCasesV2: ['all'], + observabilityCasesV3: ['all'], logs: ['all'], }) ); @@ -75,7 +75,7 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { before(async () => { await observability.users.setTestUserRole( observability.users.defineBasicObservabilityRole({ - observabilityCasesV2: ['read'], + observabilityCasesV3: ['read'], logs: ['all'], }) ); diff --git a/x-pack/test/observability_functional/apps/observability/pages/cases/case_details.ts b/x-pack/test/observability_functional/apps/observability/pages/cases/case_details.ts index 90fc09af9c6ad..392789c3604d2 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/cases/case_details.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/cases/case_details.ts @@ -33,7 +33,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { before(async () => { await observability.users.setTestUserRole( observability.users.defineBasicObservabilityRole({ - observabilityCasesV2: ['all'], + observabilityCasesV3: ['all'], logs: ['all'], }) ); From d6fd7e2cf2ba0c3ea66f0bd9abe4378186fff5c1 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Fri, 13 Dec 2024 07:00:33 +0000 Subject: [PATCH 07/32] Update snapshots --- .../__snapshots__/cases.test.ts.snap | 1 + .../feature_privilege_builder/cases.test.ts | 3 + .../cases/common/utils/capabilities.test.tsx | 3 + .../client/helpers/can_use_cases.test.ts | 62 +++++++------- .../client/helpers/capabilities.test.ts | 9 ++ .../__snapshots__/audit_logger.test.ts.snap | 84 +++++++++++++++++++ .../server/connectors/cases/index.test.ts | 4 + .../__snapshots__/oss_features.test.ts.snap | 6 ++ .../feature_privilege_iterator.test.ts | 9 ++ .../lib/product_features_service/mocks.ts | 5 ++ .../tests/features/deprecated_features.ts | 3 + 11 files changed, 158 insertions(+), 31 deletions(-) diff --git a/x-pack/packages/security/authorization_core/src/privileges/feature_privilege_builder/__snapshots__/cases.test.ts.snap b/x-pack/packages/security/authorization_core/src/privileges/feature_privilege_builder/__snapshots__/cases.test.ts.snap index 2997187697c40..39160d9eb5e58 100644 --- a/x-pack/packages/security/authorization_core/src/privileges/feature_privilege_builder/__snapshots__/cases.test.ts.snap +++ b/x-pack/packages/security/authorization_core/src/privileges/feature_privilege_builder/__snapshots__/cases.test.ts.snap @@ -18,6 +18,7 @@ Array [ "cases:observability/updateConfiguration", "cases:observability/createComment", "cases:observability/reopenCase", + "cases:observability/assignCase", ] `; diff --git a/x-pack/packages/security/authorization_core/src/privileges/feature_privilege_builder/cases.test.ts b/x-pack/packages/security/authorization_core/src/privileges/feature_privilege_builder/cases.test.ts index eae3bbc942e34..8aa6ec7cf25e7 100644 --- a/x-pack/packages/security/authorization_core/src/privileges/feature_privilege_builder/cases.test.ts +++ b/x-pack/packages/security/authorization_core/src/privileges/feature_privilege_builder/cases.test.ts @@ -130,6 +130,7 @@ describe(`cases`, () => { "cases:security/updateConfiguration", "cases:security/createComment", "cases:security/reopenCase", + "cases:security/assignCase", "cases:obs/getCase", "cases:obs/getComment", "cases:obs/getTags", @@ -187,6 +188,7 @@ describe(`cases`, () => { "cases:security/updateConfiguration", "cases:security/createComment", "cases:security/reopenCase", + "cases:security/assignCase", "cases:other-security/pushCase", "cases:other-security/createCase", "cases:other-security/getCase", @@ -203,6 +205,7 @@ describe(`cases`, () => { "cases:other-security/updateConfiguration", "cases:other-security/createComment", "cases:other-security/reopenCase", + "cases:other-security/assignCase", "cases:obs/getCase", "cases:obs/getComment", "cases:obs/getTags", diff --git a/x-pack/plugins/cases/common/utils/capabilities.test.tsx b/x-pack/plugins/cases/common/utils/capabilities.test.tsx index 6194cfd9aef02..52a334a30df92 100644 --- a/x-pack/plugins/cases/common/utils/capabilities.test.tsx +++ b/x-pack/plugins/cases/common/utils/capabilities.test.tsx @@ -18,6 +18,9 @@ describe('createUICapabilities', () => { "push_cases", "cases_connectors", ], + "assignCase": Array [ + "case_assign", + ], "createComment": Array [ "create_comment", ], diff --git a/x-pack/plugins/cases/public/client/helpers/can_use_cases.test.ts b/x-pack/plugins/cases/public/client/helpers/can_use_cases.test.ts index 69eca9d064602..4fe7a25fead3b 100644 --- a/x-pack/plugins/cases/public/client/helpers/can_use_cases.test.ts +++ b/x-pack/plugins/cases/public/client/helpers/can_use_cases.test.ts @@ -20,67 +20,67 @@ import { canUseCases } from './can_use_cases'; type CasesCapabilities = Pick< ApplicationStart['capabilities'], - 'securitySolutionCasesV2' | 'observabilityCasesV2' | 'generalCasesV2' + 'securitySolutionCasesV3' | 'observabilityCasesV3' | 'generalCasesV3' >; const hasAll: CasesCapabilities = { - securitySolutionCasesV2: allCasesCapabilities(), - observabilityCasesV2: allCasesCapabilities(), - generalCasesV2: allCasesCapabilities(), + securitySolutionCasesV3: allCasesCapabilities(), + observabilityCasesV3: allCasesCapabilities(), + generalCasesV3: allCasesCapabilities(), }; const hasNone: CasesCapabilities = { - securitySolutionCasesV2: noCasesCapabilities(), - observabilityCasesV2: noCasesCapabilities(), - generalCasesV2: noCasesCapabilities(), + securitySolutionCasesV3: noCasesCapabilities(), + observabilityCasesV3: noCasesCapabilities(), + generalCasesV3: noCasesCapabilities(), }; const hasSecurity: CasesCapabilities = { - securitySolutionCasesV2: allCasesCapabilities(), - observabilityCasesV2: noCasesCapabilities(), - generalCasesV2: noCasesCapabilities(), + securitySolutionCasesV3: allCasesCapabilities(), + observabilityCasesV3: noCasesCapabilities(), + generalCasesV3: noCasesCapabilities(), }; const hasObservability: CasesCapabilities = { - securitySolutionCasesV2: noCasesCapabilities(), - observabilityCasesV2: allCasesCapabilities(), - generalCasesV2: noCasesCapabilities(), + securitySolutionCasesV3: noCasesCapabilities(), + observabilityCasesV3: allCasesCapabilities(), + generalCasesV3: noCasesCapabilities(), }; const hasObservabilityWriteTrue: CasesCapabilities = { - securitySolutionCasesV2: noCasesCapabilities(), - observabilityCasesV2: writeCasesCapabilities(), - generalCasesV2: noCasesCapabilities(), + securitySolutionCasesV3: noCasesCapabilities(), + observabilityCasesV3: writeCasesCapabilities(), + generalCasesV3: noCasesCapabilities(), }; const hasSecurityWriteTrue: CasesCapabilities = { - securitySolutionCasesV2: writeCasesCapabilities(), - observabilityCasesV2: noCasesCapabilities(), - generalCasesV2: noCasesCapabilities(), + securitySolutionCasesV3: writeCasesCapabilities(), + observabilityCasesV3: noCasesCapabilities(), + generalCasesV3: noCasesCapabilities(), }; const hasObservabilityReadTrue: CasesCapabilities = { - securitySolutionCasesV2: noCasesCapabilities(), - observabilityCasesV2: readCasesCapabilities(), - generalCasesV2: noCasesCapabilities(), + securitySolutionCasesV3: noCasesCapabilities(), + observabilityCasesV3: readCasesCapabilities(), + generalCasesV3: noCasesCapabilities(), }; const hasSecurityReadTrue: CasesCapabilities = { - securitySolutionCasesV2: readCasesCapabilities(), - observabilityCasesV2: noCasesCapabilities(), - generalCasesV2: noCasesCapabilities(), + securitySolutionCasesV3: readCasesCapabilities(), + observabilityCasesV3: noCasesCapabilities(), + generalCasesV3: noCasesCapabilities(), }; const hasSecurityWriteAndObservabilityRead: CasesCapabilities = { - securitySolutionCasesV2: writeCasesCapabilities(), - observabilityCasesV2: readCasesCapabilities(), - generalCasesV2: noCasesCapabilities(), + securitySolutionCasesV3: writeCasesCapabilities(), + observabilityCasesV3: readCasesCapabilities(), + generalCasesV3: noCasesCapabilities(), }; const hasSecurityConnectors: CasesCapabilities = { - securitySolutionCasesV2: readCasesCapabilities(), - observabilityCasesV2: noCasesCapabilities(), - generalCasesV2: noCasesCapabilities(), + securitySolutionCasesV3: readCasesCapabilities(), + observabilityCasesV3: noCasesCapabilities(), + generalCasesV3: noCasesCapabilities(), }; describe('canUseCases', () => { diff --git a/x-pack/plugins/cases/public/client/helpers/capabilities.test.ts b/x-pack/plugins/cases/public/client/helpers/capabilities.test.ts index ec1b90eee0eb1..3ceb3a53dafbf 100644 --- a/x-pack/plugins/cases/public/client/helpers/capabilities.test.ts +++ b/x-pack/plugins/cases/public/client/helpers/capabilities.test.ts @@ -12,6 +12,7 @@ describe('getUICapabilities', () => { expect(getUICapabilities(undefined)).toMatchInlineSnapshot(` Object { "all": false, + "assignCase": false, "connectors": false, "create": false, "createComment": false, @@ -29,6 +30,7 @@ describe('getUICapabilities', () => { expect(getUICapabilities()).toMatchInlineSnapshot(` Object { "all": false, + "assignCase": false, "connectors": false, "create": false, "createComment": false, @@ -46,6 +48,7 @@ describe('getUICapabilities', () => { expect(getUICapabilities({ create_cases: true })).toMatchInlineSnapshot(` Object { "all": false, + "assignCase": false, "connectors": false, "create": true, "createComment": false, @@ -72,6 +75,7 @@ describe('getUICapabilities', () => { ).toMatchInlineSnapshot(` Object { "all": false, + "assignCase": false, "connectors": false, "create": false, "createComment": false, @@ -89,6 +93,7 @@ describe('getUICapabilities', () => { expect(getUICapabilities({})).toMatchInlineSnapshot(` Object { "all": false, + "assignCase": false, "connectors": false, "create": false, "createComment": false, @@ -115,6 +120,7 @@ describe('getUICapabilities', () => { ).toMatchInlineSnapshot(` Object { "all": false, + "assignCase": false, "connectors": true, "create": false, "createComment": false, @@ -142,6 +148,7 @@ describe('getUICapabilities', () => { ).toMatchInlineSnapshot(` Object { "all": false, + "assignCase": false, "connectors": false, "create": true, "createComment": false, @@ -169,6 +176,7 @@ describe('getUICapabilities', () => { ).toMatchInlineSnapshot(` Object { "all": false, + "assignCase": false, "connectors": true, "create": true, "createComment": false, @@ -186,6 +194,7 @@ describe('getUICapabilities', () => { expect(getUICapabilities({ cases_settings: true })).toMatchInlineSnapshot(` Object { "all": false, + "assignCase": false, "connectors": false, "create": false, "createComment": false, diff --git a/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap b/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap index b8129f9111b9c..12cb3fa8fc919 100644 --- a/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap +++ b/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap @@ -1,5 +1,89 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`audit_logger log function event structure creates the correct audit event for operation: "assignCase" with an error and entity 1`] = ` +Object { + "error": Object { + "code": "Error", + "message": "an error", + }, + "event": Object { + "action": "case_assign", + "category": Array [ + "database", + ], + "outcome": "failure", + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "1", + "type": "cases", + }, + }, + "message": "Failed attempt to update cases [id=1] as owner \\"awesome\\"", +} +`; + +exports[`audit_logger log function event structure creates the correct audit event for operation: "assignCase" with an error but no entity 1`] = ` +Object { + "error": Object { + "code": "Error", + "message": "an error", + }, + "event": Object { + "action": "case_assign", + "category": Array [ + "database", + ], + "outcome": "failure", + "type": Array [ + "change", + ], + }, + "message": "Failed attempt to update a case as any owners", +} +`; + +exports[`audit_logger log function event structure creates the correct audit event for operation: "assignCase" without an error but with an entity 1`] = ` +Object { + "event": Object { + "action": "case_assign", + "category": Array [ + "database", + ], + "outcome": "unknown", + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "5", + "type": "cases", + }, + }, + "message": "User is updating cases [id=5] as owner \\"super\\"", +} +`; + +exports[`audit_logger log function event structure creates the correct audit event for operation: "assignCase" without an error or entity 1`] = ` +Object { + "event": Object { + "action": "case_assign", + "category": Array [ + "database", + ], + "outcome": "unknown", + "type": Array [ + "change", + ], + }, + "message": "User is updating a case as any owners", +} +`; + exports[`audit_logger log function event structure creates the correct audit event for operation: "bulkCreateAttachments" with an error and entity 1`] = ` Object { "error": Object { diff --git a/x-pack/plugins/cases/server/connectors/cases/index.test.ts b/x-pack/plugins/cases/server/connectors/cases/index.test.ts index c0480d694184f..4c9320d969adb 100644 --- a/x-pack/plugins/cases/server/connectors/cases/index.test.ts +++ b/x-pack/plugins/cases/server/connectors/cases/index.test.ts @@ -37,6 +37,7 @@ describe('getCasesConnectorType', () => { 'cases:my-owner/deleteComment', 'cases:my-owner/findConfigurations', 'cases:my-owner/reopenCase', + 'cases:my-owner/assignCase', ]); }); @@ -358,6 +359,7 @@ describe('getCasesConnectorType', () => { 'cases:securitySolution/deleteComment', 'cases:securitySolution/findConfigurations', 'cases:securitySolution/reopenCase', + 'cases:securitySolution/assignCase', ]); }); @@ -379,6 +381,7 @@ describe('getCasesConnectorType', () => { 'cases:observability/deleteComment', 'cases:observability/findConfigurations', 'cases:observability/reopenCase', + 'cases:observability/assignCase', ]); }); @@ -400,6 +403,7 @@ describe('getCasesConnectorType', () => { 'cases:securitySolution/deleteComment', 'cases:securitySolution/findConfigurations', 'cases:securitySolution/reopenCase', + 'cases:securitySolution/assignCase', ]); }); }); diff --git a/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap b/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap index 140d20f8ebdb8..69ac577396ddb 100644 --- a/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap +++ b/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap @@ -558,6 +558,7 @@ Array [ ], "cases": Object { "all": Array [], + "assignCase": Array [], "create": Array [], "createComment": Array [], "delete": Array [], @@ -722,6 +723,7 @@ Array [ ], "cases": Object { "all": Array [], + "assignCase": Array [], "create": Array [], "createComment": Array [], "delete": Array [], @@ -1075,6 +1077,7 @@ Array [ ], "cases": Object { "all": Array [], + "assignCase": Array [], "create": Array [], "createComment": Array [], "delete": Array [], @@ -1222,6 +1225,7 @@ Array [ ], "cases": Object { "all": Array [], + "assignCase": Array [], "create": Array [], "createComment": Array [], "delete": Array [], @@ -1386,6 +1390,7 @@ Array [ ], "cases": Object { "all": Array [], + "assignCase": Array [], "create": Array [], "createComment": Array [], "delete": Array [], @@ -1739,6 +1744,7 @@ Array [ ], "cases": Object { "all": Array [], + "assignCase": Array [], "create": Array [], "createComment": Array [], "delete": Array [], diff --git a/x-pack/plugins/features/server/feature_privilege_iterator/feature_privilege_iterator.test.ts b/x-pack/plugins/features/server/feature_privilege_iterator/feature_privilege_iterator.test.ts index c4e542a7ebcde..ab6c002bb2d5d 100644 --- a/x-pack/plugins/features/server/feature_privilege_iterator/feature_privilege_iterator.test.ts +++ b/x-pack/plugins/features/server/feature_privilege_iterator/feature_privilege_iterator.test.ts @@ -822,6 +822,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type', 'cases-settings-sub-type'], createComment: ['cases-create-comment-type', 'cases-create-comment-sub-type'], reopenCase: ['cases-reopen-type', 'cases-reopen-sub-type'], + assignCase: [], }, ui: ['ui-action', 'ui-sub-type'], }, @@ -860,6 +861,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-sub-type'], createComment: ['cases-create-comment-sub-type'], reopenCase: ['cases-reopen-sub-type'], + assignCase: [], }, ui: ['ui-action', 'ui-sub-type'], }, @@ -1012,6 +1014,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], + assignCase: [], }, ui: ['ui-action'], }, @@ -1049,6 +1052,7 @@ describe('featurePrivilegeIterator', () => { settings: [], createComment: [], reopenCase: [], + assignCase: [], }, ui: ['ui-action'], }, @@ -1209,6 +1213,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type', 'cases-settings-sub-type'], createComment: ['cases-create-comment-type', 'cases-create-comment-sub-type'], reopenCase: ['cases-reopen-type', 'cases-reopen-sub-type'], + assignCase: [], }, ui: ['ui-action', 'ui-sub-type'], }, @@ -1456,6 +1461,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-sub-type'], createComment: ['cases-create-comment-sub-type'], reopenCase: ['cases-reopen-sub-type'], + assignCase: [], }, ui: ['ui-sub-type'], }, @@ -1494,6 +1500,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-sub-type'], createComment: ['cases-create-comment-sub-type'], reopenCase: ['cases-reopen-sub-type'], + assignCase: [], }, ui: ['ui-sub-type'], }, @@ -1630,6 +1637,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], + assignCase: [], }, ui: ['ui-action'], }, @@ -1667,6 +1675,7 @@ describe('featurePrivilegeIterator', () => { settings: [], createComment: [], reopenCase: [], + assignCase: [], }, ui: ['ui-action'], }, diff --git a/x-pack/plugins/security_solution/server/lib/product_features_service/mocks.ts b/x-pack/plugins/security_solution/server/lib/product_features_service/mocks.ts index 29df069020561..35a6671fea3c6 100644 --- a/x-pack/plugins/security_solution/server/lib/product_features_service/mocks.ts +++ b/x-pack/plugins/security_solution/server/lib/product_features_service/mocks.ts @@ -31,6 +31,11 @@ jest.mock('@kbn/security-solution-features/product_features', () => ({ baseKibanaSubFeatureIds: [], subFeaturesMap: new Map(), })), + getCasesV3Feature: jest.fn(() => ({ + baseKibanaFeature: {}, + baseKibanaSubFeatureIds: [], + subFeaturesMap: new Map(), + })), getAssistantFeature: jest.fn(() => ({ baseKibanaFeature: {}, baseKibanaSubFeatureIds: [], diff --git a/x-pack/test/security_api_integration/tests/features/deprecated_features.ts b/x-pack/test/security_api_integration/tests/features/deprecated_features.ts index 7887cd6a23dc0..7926fb048e1c4 100644 --- a/x-pack/test/security_api_integration/tests/features/deprecated_features.ts +++ b/x-pack/test/security_api_integration/tests/features/deprecated_features.ts @@ -182,8 +182,11 @@ export default function ({ getService }: FtrProviderContext) { "case_4_feature_a", "case_4_feature_b", "generalCases", + "generalCasesV2", "observabilityCases", + "observabilityCasesV2", "securitySolutionCases", + "securitySolutionCasesV2", ] `); }); From 225bfb9c259fa72010fbfbe27bca3c02da0300cf Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Fri, 13 Dec 2024 15:14:31 +0000 Subject: [PATCH 08/32] More test fixes --- .../plugins/cases/public/common/lib/kibana/hooks.test.tsx | 2 +- .../cases/public/common/lib/kibana/kibana_react.mock.tsx | 3 ++- .../cases/public/components/all_cases/all_cases_list.tsx | 1 + .../cases/public/components/app/use_available_owners.ts | 8 ++++---- .../common/roles_users/endpoint_operations_analyst.ts | 2 +- .../common/roles_users/without_response_actions_role.ts | 2 +- .../product_features_service.test.ts | 5 +++-- 7 files changed, 13 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/cases/public/common/lib/kibana/hooks.test.tsx b/x-pack/plugins/cases/public/common/lib/kibana/hooks.test.tsx index 73d1822c62499..81b1d8a938a2a 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/hooks.test.tsx +++ b/x-pack/plugins/cases/public/common/lib/kibana/hooks.test.tsx @@ -20,7 +20,7 @@ describe('hooks', () => { expect(result.current).toEqual({ actions: { crud: true, read: true }, - generalCasesV2: allCasesPermissions(), + generalCasesV3: allCasesPermissions(), visualize: { crud: true, read: true }, dashboard: { crud: true, read: true }, }); diff --git a/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.tsx b/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.tsx index e644c9604d495..84a02df8ab28c 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.tsx +++ b/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.tsx @@ -83,7 +83,7 @@ export const createStartServicesMock = ({ license }: StartServiceArgs = {}): Sta services.application.capabilities = { ...services.application.capabilities, actions: { save: true, show: true }, - generalCasesV2: { + generalCasesV3: { create_cases: true, read_cases: true, update_cases: true, @@ -93,6 +93,7 @@ export const createStartServicesMock = ({ license }: StartServiceArgs = {}): Sta cases_settings: true, case_reopen: true, create_comment: true, + case_assign: true, }, visualize: { save: true, show: true }, dashboard: { show: true, createNew: true }, diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx index 82e011366e884..343a006ae86b3 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx @@ -47,6 +47,7 @@ export interface AllCasesListProps { export const AllCasesList = React.memo( ({ hiddenStatuses = [], isSelectorView = false, onRowClick }) => { const { owner, permissions } = useCasesContext(); + const availableSolutions = useAvailableCasesOwners(getAllPermissionsExceptFrom('delete')); const isLoading = useIsLoadingCases(); const { euiTheme } = useEuiTheme(); diff --git a/x-pack/plugins/cases/public/components/app/use_available_owners.ts b/x-pack/plugins/cases/public/components/app/use_available_owners.ts index 4220ff8cdecd4..c9db38a5d1a80 100644 --- a/x-pack/plugins/cases/public/components/app/use_available_owners.ts +++ b/x-pack/plugins/cases/public/components/app/use_available_owners.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { APP_ID, FEATURE_ID_V2 } from '../../../common/constants'; +import { APP_ID, FEATURE_ID_V3 } from '../../../common/constants'; import { useKibana } from '../../common/lib/kibana'; import type { CasesPermissions } from '../../containers/types'; import { allCasePermissions } from '../../utils/permissions'; @@ -25,7 +25,7 @@ export const useAvailableCasesOwners = ( return Object.entries(kibanaCapabilities).reduce( (availableOwners: string[], [featureId, kibanaCapability]) => { - if (!featureId.endsWith('CasesV2')) { + if (!featureId.endsWith('CasesV3')) { return availableOwners; } for (const cap of capabilities) { @@ -42,9 +42,9 @@ export const useAvailableCasesOwners = ( }; const getOwnerFromFeatureID = (featureID: string) => { - if (featureID === FEATURE_ID_V2) { + if (featureID === FEATURE_ID_V3) { return APP_ID; } - return featureID.replace('CasesV2', ''); + return featureID.replace('CasesV3', ''); }; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/endpoint_operations_analyst.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/endpoint_operations_analyst.ts index 85cadf5aa65d4..8a147b0a647c6 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/endpoint_operations_analyst.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/endpoint_operations_analyst.ts @@ -55,7 +55,7 @@ export const getEndpointOperationsAnalyst: () => Omit = () => { fleet: ['all'], fleetv2: ['all'], osquery: ['all'], - securitySolutionCasesV2: ['all'], + securitySolutionCasesV3: ['all'], builtinAlerts: ['all'], siem: [ 'all', diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/without_response_actions_role.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/without_response_actions_role.ts index d57ca059de994..b19632a0b11c4 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/without_response_actions_role.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/without_response_actions_role.ts @@ -37,7 +37,7 @@ export const getNoResponseActionsRole: () => Omit = () => ({ advancedSettings: ['all'], dev_tools: ['all'], fleet: ['all'], - generalCasesV2: ['all'], + generalCasesV3: ['all'], indexPatterns: ['all'], osquery: ['all'], savedObjectsManagement: ['all'], diff --git a/x-pack/plugins/security_solution/server/lib/product_features_service/product_features_service.test.ts b/x-pack/plugins/security_solution/server/lib/product_features_service/product_features_service.test.ts index 768228f319b24..1c6091235fb56 100644 --- a/x-pack/plugins/security_solution/server/lib/product_features_service/product_features_service.test.ts +++ b/x-pack/plugins/security_solution/server/lib/product_features_service/product_features_service.test.ts @@ -45,6 +45,7 @@ jest.mock('@kbn/security-solution-features/product_features', () => ({ getAssistantFeature: () => mockGetFeature(), getCasesFeature: () => mockGetFeature(), getCasesV2Feature: () => mockGetFeature(), + getCasesV3Feature: () => mockGetFeature(), getSecurityFeature: () => mockGetFeature(), })); @@ -57,8 +58,8 @@ describe('ProductFeaturesService', () => { const experimentalFeatures = {} as ExperimentalFeatures; new ProductFeaturesService(loggerMock.create(), experimentalFeatures); - expect(mockGetFeature).toHaveBeenCalledTimes(5); - expect(MockedProductFeatures).toHaveBeenCalledTimes(5); + expect(mockGetFeature).toHaveBeenCalledTimes(6); + expect(MockedProductFeatures).toHaveBeenCalledTimes(6); }); it('should init all ProductFeatures when initialized', () => { From dd7006a1f45178ce8ae12ef8b201a6ad7bd529d4 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 17 Dec 2024 15:03:29 +0000 Subject: [PATCH 09/32] Test/type fixes --- .../components/app/use_available_owners.test.ts | 12 ++++++------ .../cases/server/connectors/cases/utils.test.ts | 1 + x-pack/plugins/cases/server/features/v3.ts | 1 + .../header/add_to_case_action.test.tsx | 1 + .../observability/server/features/cases_v2.ts | 1 + .../observability/server/features/cases_v3.ts | 1 + .../src/cases/v1_features/kibana_features.ts | 2 +- .../src/cases/v2_features/kibana_features.ts | 1 + 8 files changed, 13 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/cases/public/components/app/use_available_owners.test.ts b/x-pack/plugins/cases/public/components/app/use_available_owners.test.ts index 5a19e9a0f995b..a63268a1d6bdd 100644 --- a/x-pack/plugins/cases/public/components/app/use_available_owners.test.ts +++ b/x-pack/plugins/cases/public/components/app/use_available_owners.test.ts @@ -21,15 +21,15 @@ jest.mock('../../common/lib/kibana'); const useKibanaMock = useKibana as jest.MockedFunction; const hasAll = { - securitySolutionCasesV2: allCasesCapabilities(), - observabilityCasesV2: allCasesCapabilities(), - generalCasesV2: allCasesCapabilities(), + securitySolutionCasesV3: allCasesCapabilities(), + observabilityCasesV3: allCasesCapabilities(), + generalCasesV3: allCasesCapabilities(), }; const secAllObsReadGenNone = { - securitySolutionCasesV2: allCasesCapabilities(), - observabilityCasesV2: readCasesCapabilities(), - generalCasesV2: noCasesCapabilities(), + securitySolutionCasesV3: allCasesCapabilities(), + observabilityCasesV3: readCasesCapabilities(), + generalCasesV3: noCasesCapabilities(), }; const unrelatedFeatures = { diff --git a/x-pack/plugins/cases/server/connectors/cases/utils.test.ts b/x-pack/plugins/cases/server/connectors/cases/utils.test.ts index 55ffb5c7170bd..d528aad486bc4 100644 --- a/x-pack/plugins/cases/server/connectors/cases/utils.test.ts +++ b/x-pack/plugins/cases/server/connectors/cases/utils.test.ts @@ -508,6 +508,7 @@ describe('utils', () => { 'cases:my-owner/deleteComment', 'cases:my-owner/findConfigurations', 'cases:my-owner/reopenCase', + 'cases:my-owner/assignCase', ]); }); }); diff --git a/x-pack/plugins/cases/server/features/v3.ts b/x-pack/plugins/cases/server/features/v3.ts index 873a138eb9a55..9debe43bd6768 100644 --- a/x-pack/plugins/cases/server/features/v3.ts +++ b/x-pack/plugins/cases/server/features/v3.ts @@ -55,6 +55,7 @@ export const getV3 = (): KibanaFeatureConfig => { read: [APP_ID], update: [APP_ID], push: [APP_ID], + assignCase: [APP_ID], }, management: { insightsAndAlerting: [APP_ID], diff --git a/x-pack/solutions/observability/plugins/exploratory_view/public/components/shared/exploratory_view/header/add_to_case_action.test.tsx b/x-pack/solutions/observability/plugins/exploratory_view/public/components/shared/exploratory_view/header/add_to_case_action.test.tsx index 011fb93553ac4..8d3994e8e29b3 100644 --- a/x-pack/solutions/observability/plugins/exploratory_view/public/components/shared/exploratory_view/header/add_to_case_action.test.tsx +++ b/x-pack/solutions/observability/plugins/exploratory_view/public/components/shared/exploratory_view/header/add_to_case_action.test.tsx @@ -122,6 +122,7 @@ describe('AddToCaseAction', function () { settings: false, createComment: false, reopenCase: false, + assignCase: false, }, }) ); diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts index 9dab9076fa31b..7935f0dda0c09 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts @@ -50,6 +50,7 @@ export const getCasesFeatureV2 = ( read: [observabilityFeatureId], update: [observabilityFeatureId], push: [observabilityFeatureId], + assignCase: [observabilityFeatureId], }, savedObject: { all: [...filesSavedObjectTypes], diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts index eae54e51cbd16..ad62ad73b014b 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts @@ -36,6 +36,7 @@ export const getCasesFeatureV3 = ( read: [observabilityFeatureId], update: [observabilityFeatureId], push: [observabilityFeatureId], + assignCase: [observabilityFeatureId], }, savedObject: { all: [...filesSavedObjectTypes], diff --git a/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts index 5fdda1c8928c6..63c2bc17a72f1 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts @@ -72,7 +72,7 @@ export const getCasesBaseKibanaFeature = ({ minimal: [ { feature: CASES_FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen'], + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'case_assign'], }, ], }, diff --git a/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts index cdc02b91b68cf..a37abb1e1b7b1 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts @@ -60,6 +60,7 @@ export const getCasesBaseKibanaFeatureV2 = ({ read: [APP_ID], update: [APP_ID], push: [APP_ID], + assignCase: [APP_ID], }, savedObject: { all: [...savedObjects.files], From 2ddcf56baa49a3bd33f3dfdb11f388c0dc05643e Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Wed, 18 Dec 2024 03:00:01 +0000 Subject: [PATCH 10/32] Fix failing tests --- .../cases/public/containers/use_get_cases.test.tsx | 10 ++++++++-- .../api_integration/apis/features/features/features.ts | 6 +++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx b/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx index 0719c14e9fc4f..5d0281952ebe7 100644 --- a/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx @@ -69,7 +69,7 @@ describe('useGetCases', () => { appMockRender.coreStart.application.capabilities = { ...appMockRender.coreStart.application.capabilities, - observabilityCasesV2: { + observabilityCasesV3: { create_cases: true, read_cases: true, update_cases: true, @@ -77,8 +77,11 @@ describe('useGetCases', () => { cases_connectors: true, delete_cases: true, cases_settings: true, + create_comment: true, + reopen_case: true, + assign_case: true, }, - securitySolutionCasesV2: { + securitySolutionCasesV3: { create_cases: true, read_cases: true, update_cases: true, @@ -86,6 +89,9 @@ describe('useGetCases', () => { cases_connectors: true, delete_cases: true, cases_settings: true, + create_comment: true, + reopen_case: true, + assign_case: true, }, }; diff --git a/x-pack/test/api_integration/apis/features/features/features.ts b/x-pack/test/api_integration/apis/features/features/features.ts index e396d44a9819c..5e62d716ec324 100644 --- a/x-pack/test/api_integration/apis/features/features/features.ts +++ b/x-pack/test/api_integration/apis/features/features/features.ts @@ -167,7 +167,7 @@ export default function ({ getService }: FtrProviderContext) { 'guidedOnboardingFeature', 'monitoring', 'observabilityAIAssistant', - 'observabilityCasesV2', + 'observabilityCasesV3', 'savedObjectsManagement', 'savedQueryManagement', 'savedObjectsTagging', @@ -175,7 +175,7 @@ export default function ({ getService }: FtrProviderContext) { 'apm', 'stackAlerts', 'canvas', - 'generalCasesV2', + 'generalCasesV3', 'infrastructure', 'inventory', 'logs', @@ -190,7 +190,7 @@ export default function ({ getService }: FtrProviderContext) { 'slo', 'securitySolutionAssistant', 'securitySolutionAttackDiscovery', - 'securitySolutionCasesV2', + 'securitySolutionCasesV3', 'fleet', 'fleetv2', 'entityManager', From 218cef6e5407d5369e14da5f04e3c24a02da4200 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Mon, 23 Dec 2024 19:07:53 +0000 Subject: [PATCH 11/32] PR feedback --- x-pack/plugins/cases/common/constants/index.ts | 2 +- .../plugins/cases/common/utils/capabilities.test.tsx | 2 +- .../public/common/lib/kibana/kibana_react.mock.tsx | 2 +- x-pack/plugins/cases/public/common/mock/permissions.ts | 6 +++--- .../__snapshots__/audit_logger.test.ts.snap | 8 ++++---- x-pack/plugins/cases/server/authorization/index.ts | 2 +- .../plugins/cases/server/client/cases/bulk_update.ts | 10 ++++++++-- x-pack/plugins/cases/server/features/constants.ts | 2 +- x-pack/plugins/cases/server/features/v2.ts | 6 +++--- .../plugins/observability/server/features/cases_v2.ts | 6 +++--- .../plugins/observability/server/features/cases_v3.ts | 2 +- .../features/src/cases/v1_features/kibana_features.ts | 2 +- .../features/src/cases/v2_features/kibana_features.ts | 6 +++--- .../src/cases/v3_features/kibana_sub_features.ts | 4 +--- .../security_solution/public/cases_test_utils.ts | 6 +++--- x-pack/test/api_integration/apis/cases/common/roles.ts | 2 +- .../test/api_integration/apis/security/privileges.ts | 6 +++--- .../api_integration/apis/security/privileges_basic.ts | 6 +++--- .../common/plugins/security_solution/server/plugin.ts | 2 +- 19 files changed, 43 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/cases/common/constants/index.ts b/x-pack/plugins/cases/common/constants/index.ts index ef202a3abb32c..460ee4455b92b 100644 --- a/x-pack/plugins/cases/common/constants/index.ts +++ b/x-pack/plugins/cases/common/constants/index.ts @@ -184,7 +184,7 @@ export const CASES_SETTINGS_CAPABILITY = 'cases_settings' as const; export const CASES_CONNECTORS_CAPABILITY = 'cases_connectors' as const; export const CASES_REOPEN_CAPABILITY = 'case_reopen' as const; export const CREATE_COMMENT_CAPABILITY = 'create_comment' as const; -export const ASSIGN_CASE_CAPABILITY = 'case_assign' as const; +export const ASSIGN_CASE_CAPABILITY = 'cases_assign' as const; /** * Cases API Tags diff --git a/x-pack/plugins/cases/common/utils/capabilities.test.tsx b/x-pack/plugins/cases/common/utils/capabilities.test.tsx index 52a334a30df92..edfb5a14da532 100644 --- a/x-pack/plugins/cases/common/utils/capabilities.test.tsx +++ b/x-pack/plugins/cases/common/utils/capabilities.test.tsx @@ -19,7 +19,7 @@ describe('createUICapabilities', () => { "cases_connectors", ], "assignCase": Array [ - "case_assign", + "cases_assign", ], "createComment": Array [ "create_comment", diff --git a/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.tsx b/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.tsx index 84a02df8ab28c..8332ebd24b1c6 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.tsx +++ b/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.tsx @@ -93,7 +93,7 @@ export const createStartServicesMock = ({ license }: StartServiceArgs = {}): Sta cases_settings: true, case_reopen: true, create_comment: true, - case_assign: true, + cases_assign: true, }, visualize: { save: true, show: true }, dashboard: { show: true, createNew: true }, diff --git a/x-pack/plugins/cases/public/common/mock/permissions.ts b/x-pack/plugins/cases/public/common/mock/permissions.ts index ecde960bfe0fc..89f9c0cd488c7 100644 --- a/x-pack/plugins/cases/public/common/mock/permissions.ts +++ b/x-pack/plugins/cases/public/common/mock/permissions.ts @@ -132,7 +132,7 @@ export const noCasesCapabilities = () => cases_settings: false, create_comment: false, case_reopen: false, - case_assign: false, + cases_assign: false, }); export const readCasesCapabilities = () => buildCasesCapabilities({ @@ -143,7 +143,7 @@ export const readCasesCapabilities = () => cases_settings: false, create_comment: false, case_reopen: false, - case_assign: false, + cases_assign: false, }); export const writeCasesCapabilities = () => { return buildCasesCapabilities({ @@ -162,6 +162,6 @@ export const buildCasesCapabilities = (overrides?: Partial) = cases_settings: overrides?.cases_settings ?? true, create_comment: overrides?.create_comment ?? true, case_reopen: overrides?.case_reopen ?? true, - case_assign: overrides?.case_assign ?? true, + cases_assign: overrides?.cases_assign ?? true, }; }; diff --git a/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap b/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap index 12cb3fa8fc919..79e052ca9c87c 100644 --- a/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap +++ b/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap @@ -7,7 +7,7 @@ Object { "message": "an error", }, "event": Object { - "action": "case_assign", + "action": "cases_assign", "category": Array [ "database", ], @@ -33,7 +33,7 @@ Object { "message": "an error", }, "event": Object { - "action": "case_assign", + "action": "cases_assign", "category": Array [ "database", ], @@ -49,7 +49,7 @@ Object { exports[`audit_logger log function event structure creates the correct audit event for operation: "assignCase" without an error but with an entity 1`] = ` Object { "event": Object { - "action": "case_assign", + "action": "cases_assign", "category": Array [ "database", ], @@ -71,7 +71,7 @@ Object { exports[`audit_logger log function event structure creates the correct audit event for operation: "assignCase" without an error or entity 1`] = ` Object { "event": Object { - "action": "case_assign", + "action": "cases_assign", "category": Array [ "database", ], diff --git a/x-pack/plugins/cases/server/authorization/index.ts b/x-pack/plugins/cases/server/authorization/index.ts index c48d0a9a1ce71..cf5a454887eaa 100644 --- a/x-pack/plugins/cases/server/authorization/index.ts +++ b/x-pack/plugins/cases/server/authorization/index.ts @@ -193,7 +193,7 @@ const CaseOperations = { [WriteOperations.AssignCase]: { ecsType: EVENT_TYPES.change, name: WriteOperations.AssignCase as const, - action: 'case_assign', + action: 'cases_assign', verbs: updateVerbs, docType: 'case', savedObjectType: CASE_SAVED_OBJECT, diff --git a/x-pack/plugins/cases/server/client/cases/bulk_update.ts b/x-pack/plugins/cases/server/client/cases/bulk_update.ts index 549caabd3e573..a31a0c7cbf333 100644 --- a/x-pack/plugins/cases/server/client/cases/bulk_update.ts +++ b/x-pack/plugins/cases/server/client/cases/bulk_update.ts @@ -193,7 +193,7 @@ async function getAlertComments({ }); } -function haveSameElements(arr1?: Array<{ uid: string }>, arr2?: Array<{ uid: string }>): boolean { +function haveSameElements(arr1?: string[], arr2?: string[]): boolean { if (!arr1 || !arr2 || arr1.length !== arr2.length) return false; const set1 = new Set(arr1); return arr2.every((item) => set1.has(item)); @@ -308,7 +308,13 @@ function partitionPatchRequest( casesToAuthorize.set(foundCase.id, { id: foundCase.id, owner: foundCase.attributes.owner }); } if (reqCase.assignees) { - if (!haveSameElements(reqCase.assignees, foundCase?.attributes.assignees) && foundCase) { + if ( + !haveSameElements( + reqCase.assignees.map(({ uid }) => uid), + foundCase?.attributes.assignees.map(({ uid }) => uid) + ) && + foundCase + ) { changedAssignees.push(reqCase); } } diff --git a/x-pack/plugins/cases/server/features/constants.ts b/x-pack/plugins/cases/server/features/constants.ts index 8d73f1105a0c7..13c50e36b91be 100644 --- a/x-pack/plugins/cases/server/features/constants.ts +++ b/x-pack/plugins/cases/server/features/constants.ts @@ -16,4 +16,4 @@ export const CASES_DELETE_SUB_PRIVILEGE_ID = 'cases_delete'; export const CASES_SETTINGS_SUB_PRIVILEGE_ID = 'cases_settings'; export const CASES_CREATE_COMMENT_SUB_PRIVILEGE_ID = 'create_comment'; export const CASES_REOPEN_SUB_PRIVILEGE_ID = 'case_reopen'; -export const CASES_ASSIGN_SUB_PRIVILEGE_ID = 'case_assign'; +export const CASES_ASSIGN_SUB_PRIVILEGE_ID = 'cases_assign'; diff --git a/x-pack/plugins/cases/server/features/v2.ts b/x-pack/plugins/cases/server/features/v2.ts index 2e5a820cd7421..af114f65350a7 100644 --- a/x-pack/plugins/cases/server/features/v2.ts +++ b/x-pack/plugins/cases/server/features/v2.ts @@ -37,10 +37,10 @@ export const getV2 = (): KibanaFeatureConfig => { deprecated: { notice: i18n.translate('xpack.cases.features.casesFeature.deprecationMessage', { defaultMessage: - 'The {currentId} permissions are deprecated, please see {casesFeatureIdV2}.', + 'The {currentId} permissions are deprecated, please see {casesFeatureIdV3}.', values: { currentId: FEATURE_ID_V2, - casesFeatureIdV2: FEATURE_ID_V3, + casesFeatureIdV3: FEATURE_ID_V3, }, }), }, @@ -78,7 +78,7 @@ export const getV2 = (): KibanaFeatureConfig => { minimal: [ { feature: FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen', 'case_assign'], + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], }, ], }, diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts index 7935f0dda0c09..1705320ee2361 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts @@ -23,10 +23,10 @@ export const getCasesFeatureV2 = ( ): KibanaFeatureConfig => ({ deprecated: { notice: i18n.translate('xpack.observability.featureRegistry.casesFeature.deprecationMessage', { - defaultMessage: 'The {currentId} permissions are deprecated, please see {casesFeatureIdV2}.', + defaultMessage: 'The {currentId} permissions are deprecated, please see {casesFeatureIdV3}.', values: { currentId: casesFeatureIdV2, - casesFeatureIdV2: casesFeatureIdV3, + casesFeatureIdV3, }, }), }, @@ -62,7 +62,7 @@ export const getCasesFeatureV2 = ( minimal: [ { feature: casesFeatureIdV3, - privileges: ['minimal_all', 'create_comment', 'case_reopen', 'case_assign'], + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], }, ], }, diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts index ad62ad73b014b..23942ef63dde9 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts @@ -187,7 +187,7 @@ export const getCasesFeatureV3 = ( groupType: 'independent', privileges: [ { - id: 'case_assign', + id: 'cases_assign', name: i18n.translate('xpack.observability.features.assignUsersSubFeatureName', { defaultMessage: 'Assign users to cases', }), diff --git a/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts index 63c2bc17a72f1..5f8759bdea9f1 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts @@ -72,7 +72,7 @@ export const getCasesBaseKibanaFeature = ({ minimal: [ { feature: CASES_FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen', 'case_assign'], + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], }, ], }, diff --git a/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts index a37abb1e1b7b1..129f56ec9f759 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts @@ -29,10 +29,10 @@ export const getCasesBaseKibanaFeatureV2 = ({ 'securitySolutionPackages.features.featureRegistry.casesFeature.deprecationMessage', { defaultMessage: - 'The {currentId} permissions are deprecated, please see {casesFeatureIdV2}.', + 'The {currentId} permissions are deprecated, please see {casesFeatureIdV3}.', values: { currentId: CASES_FEATURE_ID_V2, - casesFeatureIdV2: CASES_FEATURE_ID_V3, + casesFeatureIdV3: CASES_FEATURE_ID_V3, }, } ), @@ -72,7 +72,7 @@ export const getCasesBaseKibanaFeatureV2 = ({ minimal: [ { feature: CASES_FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen', 'case_assign'], + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], }, ], }, diff --git a/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts b/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts index 64167fba65376..9e7954502c144 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts @@ -98,8 +98,6 @@ export const getCasesSubFeaturesMapV3 = ({ ], }; - /* The below sub features were newly added in v2 (8.17) */ - const casesAddCommentsCasesSubFeature: SubFeatureConfig = { name: i18n.translate( 'securitySolutionPackages.features.featureRegistry.addCommentsSubFeatureName', @@ -177,7 +175,7 @@ export const getCasesSubFeaturesMapV3 = ({ groupType: 'independent', privileges: [ { - id: 'case_assign', + id: 'cases_assign', name: i18n.translate('securitySolutionPackages.features.assignUsersSubFeatureName', { defaultMessage: 'Assign users to cases', }), diff --git a/x-pack/solutions/security/plugins/security_solution/public/cases_test_utils.ts b/x-pack/solutions/security/plugins/security_solution/public/cases_test_utils.ts index d88921bf06907..822ee32e74ec2 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/cases_test_utils.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/cases_test_utils.ts @@ -17,7 +17,7 @@ export const noCasesCapabilities = (): CasesCapabilities => ({ cases_settings: false, case_reopen: false, create_comment: false, - case_assign: false, + cases_assign: false, }); export const readCasesCapabilities = (): CasesCapabilities => ({ @@ -30,7 +30,7 @@ export const readCasesCapabilities = (): CasesCapabilities => ({ cases_settings: false, case_reopen: false, create_comment: false, - case_assign: false, + cases_assign: false, }); export const allCasesCapabilities = (): CasesCapabilities => ({ @@ -43,7 +43,7 @@ export const allCasesCapabilities = (): CasesCapabilities => ({ cases_settings: true, case_reopen: true, create_comment: true, - case_assign: true, + cases_assign: true, }); export const noCasesPermissions = (): CasesPermissions => ({ diff --git a/x-pack/test/api_integration/apis/cases/common/roles.ts b/x-pack/test/api_integration/apis/cases/common/roles.ts index 8f5cf95bc095c..035268ecb2d7e 100644 --- a/x-pack/test/api_integration/apis/cases/common/roles.ts +++ b/x-pack/test/api_integration/apis/cases/common/roles.ts @@ -575,7 +575,7 @@ export const casesV3ReadAndAssignee: Role = { spaces: ['*'], base: [], feature: { - generalCasesV3: ['read', 'case_assign'], + generalCasesV3: ['read', 'cases_assign'], actions: ['all'], actionsSimulators: ['all'], }, diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index 02a65eec6d45b..62986f5195966 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -49,7 +49,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'case_assign', + 'cases_assign', ], observabilityCases: [ 'all', @@ -78,7 +78,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'case_assign', + 'cases_assign', ], observabilityAIAssistant: ['all', 'read', 'minimal_all', 'minimal_read'], slo: ['all', 'read', 'minimal_all', 'minimal_read'], @@ -169,7 +169,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'case_assign', + 'cases_assign', ], infrastructure: ['all', 'read', 'minimal_all', 'minimal_read'], logs: ['all', 'read', 'minimal_all', 'minimal_read'], diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts index 8859d99083442..200370892cf1b 100644 --- a/x-pack/test/api_integration/apis/security/privileges_basic.ts +++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts @@ -141,7 +141,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'case_assign', + 'cases_assign', ], observabilityCases: [ 'all', @@ -170,7 +170,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'case_assign', + 'cases_assign', ], observabilityAIAssistant: ['all', 'read', 'minimal_all', 'minimal_read'], slo: ['all', 'read', 'minimal_all', 'minimal_read'], @@ -261,7 +261,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'case_assign', + 'cases_assign', ], infrastructure: ['all', 'read', 'minimal_all', 'minimal_read'], logs: ['all', 'read', 'minimal_all', 'minimal_read'], diff --git a/x-pack/test/cases_api_integration/common/plugins/security_solution/server/plugin.ts b/x-pack/test/cases_api_integration/common/plugins/security_solution/server/plugin.ts index c503af124b295..36c1e9d80147d 100644 --- a/x-pack/test/cases_api_integration/common/plugins/security_solution/server/plugin.ts +++ b/x-pack/test/cases_api_integration/common/plugins/security_solution/server/plugin.ts @@ -168,7 +168,7 @@ export class FixturePlugin implements Plugin Date: Mon, 23 Dec 2024 19:34:54 +0000 Subject: [PATCH 12/32] Unique i18n ids --- x-pack/plugins/cases/server/features/v2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/cases/server/features/v2.ts b/x-pack/plugins/cases/server/features/v2.ts index af114f65350a7..cee73bc76836e 100644 --- a/x-pack/plugins/cases/server/features/v2.ts +++ b/x-pack/plugins/cases/server/features/v2.ts @@ -35,7 +35,7 @@ export const getV2 = (): KibanaFeatureConfig => { return { deprecated: { - notice: i18n.translate('xpack.cases.features.casesFeature.deprecationMessage', { + notice: i18n.translate('xpack.cases.features.casesFeatureV2.deprecationMessage', { defaultMessage: 'The {currentId} permissions are deprecated, please see {casesFeatureIdV3}.', values: { From d6b39f5ecf8719bd3693e36dfc94b620fa29cfc7 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Wed, 15 Jan 2025 15:48:07 +0000 Subject: [PATCH 13/32] PR feedback --- .../cases/public/common/mock/permissions.ts | 1 + .../components/all_cases/utility_bar.test.tsx | 38 +++++++++++++++++++ .../components/assign_users.test.tsx | 6 +-- .../cases/public/utils/permissions.test.ts | 19 +++++++++- .../shared/cases/public/utils/permissions.ts | 11 +++++- .../shared/cases/server/features/v1.ts | 2 +- 6 files changed, 70 insertions(+), 7 deletions(-) diff --git a/x-pack/platform/plugins/shared/cases/public/common/mock/permissions.ts b/x-pack/platform/plugins/shared/cases/public/common/mock/permissions.ts index 89f9c0cd488c7..ee810edaf5891 100644 --- a/x-pack/platform/plugins/shared/cases/public/common/mock/permissions.ts +++ b/x-pack/platform/plugins/shared/cases/public/common/mock/permissions.ts @@ -40,6 +40,7 @@ export const noCreateCommentCasesPermissions = () => buildCasesPermissions({ createComment: false }); export const noUpdateCasesPermissions = () => buildCasesPermissions({ update: false, reopenCase: false }); +export const noAssignCasesPermissions = () => buildCasesPermissions({ assignCase: false }); export const noPushCasesPermissions = () => buildCasesPermissions({ push: false }); export const noDeleteCasesPermissions = () => buildCasesPermissions({ delete: false }); export const noReopenCasesPermissions = () => buildCasesPermissions({ reopenCase: false }); diff --git a/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.test.tsx b/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.test.tsx index 63d8cd2e5faab..a44545b9e95f4 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.test.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.test.tsx @@ -209,6 +209,44 @@ describe('Severity form field', () => { }); }); + it('does show the bulk actions with only assign permissions', async () => { + appMockRender = createAppMockRenderer({ + permissions: { + ...noCasesPermissions(), + assignCase: true, + }, + }); + appMockRender.render(); + + expect(await screen.findByTestId('case-table-bulk-actions-link-icon')).toBeInTheDocument(); + }); + + it('shows bulk actions when only assignCase and update permissions are present', async () => { + appMockRender = createAppMockRenderer({ + permissions: { + ...noCasesPermissions(), + assignCase: true, + update: true, + }, + }); + appMockRender.render(); + + expect(await screen.findByTestId('case-table-bulk-actions-link-icon')).toBeInTheDocument(); + }); + + it('shows bulk actions when only assignCase and delete permissions are present', async () => { + appMockRender = createAppMockRenderer({ + permissions: { + ...noCasesPermissions(), + assignCase: true, + delete: true, + }, + }); + appMockRender.render(); + + expect(await screen.findByTestId('case-table-bulk-actions-link-icon')).toBeInTheDocument(); + }); + describe('Maximum number of cases', () => { const newProps = { ...props, diff --git a/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.test.tsx b/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.test.tsx index 36384b9b4cd6f..b9435d94114f0 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.test.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.test.tsx @@ -11,7 +11,7 @@ import { userProfiles, userProfilesMap } from '../../../containers/user_profiles import { fireEvent, screen, waitFor } from '@testing-library/react'; import React from 'react'; import type { AppMockRenderer } from '../../../common/mock'; -import { createAppMockRenderer, noUpdateCasesPermissions } from '../../../common/mock'; +import { createAppMockRenderer, noAssignCasesPermissions } from '../../../common/mock'; import type { AssignUsersProps } from './assign_users'; import { AssignUsers } from './assign_users'; import { waitForEuiPopoverClose, waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; @@ -50,14 +50,14 @@ describe('AssignUsers', () => { }); it('does not show the suggest users edit button when the user does not have update permissions', () => { - appMockRender = createAppMockRenderer({ permissions: noUpdateCasesPermissions() }); + appMockRender = createAppMockRenderer({ permissions: noAssignCasesPermissions() }); appMockRender.render(); expect(screen.queryByText('case-view-assignees-edit')).not.toBeInTheDocument(); }); it('does not show the assign users link when the user does not have update permissions', () => { - appMockRender = createAppMockRenderer({ permissions: noUpdateCasesPermissions() }); + appMockRender = createAppMockRenderer({ permissions: noAssignCasesPermissions() }); appMockRender.render(); expect(screen.queryByTestId('assign yourself')).not.toBeInTheDocument(); diff --git a/x-pack/platform/plugins/shared/cases/public/utils/permissions.test.ts b/x-pack/platform/plugins/shared/cases/public/utils/permissions.test.ts index 66e63e6950dd4..93363061ed50e 100644 --- a/x-pack/platform/plugins/shared/cases/public/utils/permissions.test.ts +++ b/x-pack/platform/plugins/shared/cases/public/utils/permissions.test.ts @@ -10,7 +10,15 @@ import { getAllPermissionsExceptFrom, isReadOnlyPermissions } from './permission describe('permissions', () => { describe('isReadOnlyPermissions', () => { - const tests = [['update'], ['create'], ['delete'], ['push'], ['all']]; + const tests = [ + ['update'], + ['create'], + ['delete'], + ['push'], + ['all'], + ['assignCase'], + ['createComment'], + ]; it('returns true if the user has only read permissions', async () => { expect(isReadOnlyPermissions(readCasesPermissions())).toBe(true); @@ -31,7 +39,14 @@ describe('permissions', () => { describe('getAllPermissionsExceptFrom', () => { it('returns the correct permissions', async () => { - expect(getAllPermissionsExceptFrom('create')).toEqual(['read', 'update', 'delete', 'push']); + expect(getAllPermissionsExceptFrom('create')).toEqual([ + 'read', + 'update', + 'delete', + 'push', + 'assignCase', + 'createComment', + ]); }); }); }); diff --git a/x-pack/platform/plugins/shared/cases/public/utils/permissions.ts b/x-pack/platform/plugins/shared/cases/public/utils/permissions.ts index c96f6ef4f64d1..607adf5b064d0 100644 --- a/x-pack/platform/plugins/shared/cases/public/utils/permissions.ts +++ b/x-pack/platform/plugins/shared/cases/public/utils/permissions.ts @@ -15,13 +15,22 @@ export const isReadOnlyPermissions = (permissions: CasesPermissions) => { !permissions.delete && !permissions.push && !permissions.assignCase && + !permissions.createComment && permissions.read ); }; type CasePermission = Exclude; -export const allCasePermissions: CasePermission[] = ['create', 'read', 'update', 'delete', 'push']; +export const allCasePermissions: CasePermission[] = [ + 'create', + 'read', + 'update', + 'delete', + 'push', + 'assignCase', + 'createComment', +]; export const getAllPermissionsExceptFrom = (capToExclude: CasePermission): CasePermission[] => allCasePermissions.filter((permission) => permission !== capToExclude) as CasePermission[]; diff --git a/x-pack/platform/plugins/shared/cases/server/features/v1.ts b/x-pack/platform/plugins/shared/cases/server/features/v1.ts index 9e69db119cf99..261cfd429a8b8 100644 --- a/x-pack/platform/plugins/shared/cases/server/features/v1.ts +++ b/x-pack/platform/plugins/shared/cases/server/features/v1.ts @@ -76,7 +76,7 @@ export const getV1 = (): KibanaFeatureConfig => { minimal: [ { feature: FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen'], + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], }, ], }, From 4245847b351a3213e9d57bf074f7bf91bf8f6106 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Fri, 17 Jan 2025 08:34:21 +0000 Subject: [PATCH 14/32] WIP pr fixup --- .../feature_privilege_builder/cases.ts | 2 +- .../plugins/shared/cases/common/ui/types.ts | 2 +- .../shared/cases/common/utils/capabilities.ts | 5 +-- .../public/client/helpers/can_use_cases.ts | 6 ++-- .../public/client/helpers/capabilities.ts | 6 ++-- .../cases/public/common/lib/kibana/hooks.ts | 4 +-- .../common/lib/kibana/kibana_react.mock.tsx | 2 +- .../cases/public/common/mock/permissions.ts | 18 +++++----- .../public/common/use_cases_features.tsx | 6 ++-- .../components/actions/use_items_action.tsx | 2 +- .../components/all_cases/use_actions.test.tsx | 6 ++-- .../components/all_cases/use_actions.tsx | 4 +-- .../components/all_cases/use_bulk_actions.tsx | 2 +- .../components/all_cases/utility_bar.test.tsx | 6 ++-- .../components/all_cases/utility_bar.tsx | 5 +-- .../components/app/use_available_owners.ts | 1 - .../case_view/components/assign_users.tsx | 2 +- .../public/components/cases_context/index.tsx | 2 +- .../public/containers/use_get_cases.test.tsx | 35 ++++++------------- .../cases/public/utils/permissions.test.ts | 4 +-- .../shared/cases/public/utils/permissions.ts | 5 ++- .../cases/server/client/cases/bulk_update.ts | 2 +- .../shared/cases/server/features/v1.ts | 2 +- .../shared/cases/server/features/v2.ts | 5 ++- .../shared/cases/server/features/v3.ts | 8 +++-- .../common/feature_kibana_privileges.ts | 4 +-- .../feature_privilege_iterator.ts | 6 ++-- .../shared/features/server/feature_schema.ts | 2 +- .../pages/cases/components/cases.stories.tsx | 4 +-- .../observability/server/features/cases_v1.ts | 2 +- .../observability/server/features/cases_v2.ts | 4 +-- .../observability/server/features/cases_v3.ts | 8 ++--- .../public/utils/cases_permissions.ts | 4 +-- .../src/cases/v1_features/kibana_features.ts | 4 +-- .../src/cases/v2_features/kibana_features.ts | 4 +-- .../cases/v3_features/kibana_sub_features.ts | 6 ++-- .../public/cases_test_utils.ts | 14 ++++---- .../apis/cases/common/roles.ts | 2 +- .../api_integration/apis/cases/privileges.ts | 18 ++++++++-- .../apis/security/privileges.ts | 6 ++-- .../apis/security/privileges_basic.ts | 6 ++-- .../security_solution/server/plugin.ts | 2 +- 42 files changed, 119 insertions(+), 119 deletions(-) diff --git a/x-pack/platform/packages/private/security/authorization_core/src/privileges/feature_privilege_builder/cases.ts b/x-pack/platform/packages/private/security/authorization_core/src/privileges/feature_privilege_builder/cases.ts index d34ab5056794e..da612a522775a 100644 --- a/x-pack/platform/packages/private/security/authorization_core/src/privileges/feature_privilege_builder/cases.ts +++ b/x-pack/platform/packages/private/security/authorization_core/src/privileges/feature_privilege_builder/cases.ts @@ -73,7 +73,7 @@ export class FeaturePrivilegeCasesBuilder extends BaseFeaturePrivilegeBuilder { ...getCasesPrivilege(settingsOperations, privilegeDefinition.cases?.settings), ...getCasesPrivilege(createCommentOperations, privilegeDefinition.cases?.createComment), ...getCasesPrivilege(reopenOperations, privilegeDefinition.cases?.reopenCase), - ...getCasesPrivilege(assignOperations, privilegeDefinition.cases?.assignCase), + ...getCasesPrivilege(assignOperations, privilegeDefinition.cases?.assign), ]); } } diff --git a/x-pack/platform/plugins/shared/cases/common/ui/types.ts b/x-pack/platform/plugins/shared/cases/common/ui/types.ts index 1edbebe7c50dc..a9e190280fff7 100644 --- a/x-pack/platform/plugins/shared/cases/common/ui/types.ts +++ b/x-pack/platform/plugins/shared/cases/common/ui/types.ts @@ -321,7 +321,7 @@ export interface CasesPermissions { settings: boolean; reopenCase: boolean; createComment: boolean; - assignCase: boolean; + assign: boolean; } export interface CasesCapabilities { diff --git a/x-pack/platform/plugins/shared/cases/common/utils/capabilities.ts b/x-pack/platform/plugins/shared/cases/common/utils/capabilities.ts index 7ef3300d5fd7b..da89ab7fbc0a4 100644 --- a/x-pack/platform/plugins/shared/cases/common/utils/capabilities.ts +++ b/x-pack/platform/plugins/shared/cases/common/utils/capabilities.ts @@ -25,7 +25,7 @@ export interface CasesUiCapabilities { settings: readonly string[]; reopenCase: readonly string[]; createComment: readonly string[]; - assignCase: readonly string[]; + assign: readonly string[]; } /** * Return the UI capabilities for each type of operation. These strings must match the values defined in the UI @@ -38,11 +38,12 @@ export const createUICapabilities = (): CasesUiCapabilities => ({ UPDATE_CASES_CAPABILITY, PUSH_CASES_CAPABILITY, CASES_CONNECTORS_CAPABILITY, + ASSIGN_CASE_CAPABILITY, ] as const, read: [READ_CASES_CAPABILITY, CASES_CONNECTORS_CAPABILITY] as const, delete: [DELETE_CASES_CAPABILITY] as const, settings: [CASES_SETTINGS_CAPABILITY] as const, reopenCase: [CASES_REOPEN_CAPABILITY] as const, createComment: [CREATE_COMMENT_CAPABILITY] as const, - assignCase: [ASSIGN_CASE_CAPABILITY] as const, + assign: [ASSIGN_CASE_CAPABILITY] as const, }); diff --git a/x-pack/platform/plugins/shared/cases/public/client/helpers/can_use_cases.ts b/x-pack/platform/plugins/shared/cases/public/client/helpers/can_use_cases.ts index 8fb9403febdde..ee9044f0f4a45 100644 --- a/x-pack/platform/plugins/shared/cases/public/client/helpers/can_use_cases.ts +++ b/x-pack/platform/plugins/shared/cases/public/client/helpers/can_use_cases.ts @@ -45,7 +45,7 @@ export const canUseCases = acc.settings = acc.settings || userCapabilitiesForOwner.settings; acc.reopenCase = acc.reopenCase || userCapabilitiesForOwner.reopenCase; acc.createComment = acc.createComment || userCapabilitiesForOwner.createComment; - acc.assignCase = acc.assignCase || userCapabilitiesForOwner.assignCase; + acc.assign = acc.assign || userCapabilitiesForOwner.assign; const allFromAcc = acc.create && @@ -57,7 +57,7 @@ export const canUseCases = acc.settings && acc.reopenCase && acc.createComment && - acc.assignCase; + acc.assign; acc.all = acc.all || userCapabilitiesForOwner.all || allFromAcc; @@ -74,7 +74,7 @@ export const canUseCases = settings: false, reopenCase: false, createComment: false, - assignCase: false, + assign: false, } ); diff --git a/x-pack/platform/plugins/shared/cases/public/client/helpers/capabilities.ts b/x-pack/platform/plugins/shared/cases/public/client/helpers/capabilities.ts index 5aaed11394eff..5688b875c3233 100644 --- a/x-pack/platform/plugins/shared/cases/public/client/helpers/capabilities.ts +++ b/x-pack/platform/plugins/shared/cases/public/client/helpers/capabilities.ts @@ -31,7 +31,7 @@ export const getUICapabilities = ( const settings = !!featureCapabilities?.[CASES_SETTINGS_CAPABILITY]; const reopenCase = !!featureCapabilities?.[CASES_REOPEN_CAPABILITY]; const createComment = !!featureCapabilities?.[CREATE_COMMENT_CAPABILITY]; - const assignCase = !!featureCapabilities?.[ASSIGN_CASE_CAPABILITY]; + const assign = !!featureCapabilities?.[ASSIGN_CASE_CAPABILITY]; const all = create && @@ -43,7 +43,7 @@ export const getUICapabilities = ( settings && reopenCase && createComment && - assignCase; + assign; return { all, @@ -56,6 +56,6 @@ export const getUICapabilities = ( settings, reopenCase, createComment, - assignCase, + assign, }; }; diff --git a/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/hooks.ts b/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/hooks.ts index a3b6e94b1a76b..356108ac528d1 100644 --- a/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/hooks.ts +++ b/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/hooks.ts @@ -195,7 +195,7 @@ export const useApplicationCapabilities = (): UseApplicationCapabilities => { settings: permissions.settings, reopenCase: permissions.reopenCase, createComment: permissions.createComment, - assignCase: permissions.assignCase, + assign: permissions.assign, }, visualize: { crud: !!capabilities.visualize?.save, read: !!capabilities.visualize?.show }, dashboard: { @@ -220,7 +220,7 @@ export const useApplicationCapabilities = (): UseApplicationCapabilities => { permissions.settings, permissions.reopenCase, permissions.createComment, - permissions.assignCase, + permissions.assign, ] ); }; diff --git a/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/kibana_react.mock.tsx b/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/kibana_react.mock.tsx index 8332ebd24b1c6..9ba76d394dc1e 100644 --- a/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/kibana_react.mock.tsx +++ b/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/kibana_react.mock.tsx @@ -93,7 +93,7 @@ export const createStartServicesMock = ({ license }: StartServiceArgs = {}): Sta cases_settings: true, case_reopen: true, create_comment: true, - cases_assign: true, + assign: true, }, visualize: { save: true, show: true }, dashboard: { show: true, createNew: true }, diff --git a/x-pack/platform/plugins/shared/cases/public/common/mock/permissions.ts b/x-pack/platform/plugins/shared/cases/public/common/mock/permissions.ts index ee810edaf5891..19982e453b13d 100644 --- a/x-pack/platform/plugins/shared/cases/public/common/mock/permissions.ts +++ b/x-pack/platform/plugins/shared/cases/public/common/mock/permissions.ts @@ -19,7 +19,7 @@ export const noCasesPermissions = () => settings: false, createComment: false, reopenCase: false, - assignCase: false, + assign: false, }); export const readCasesPermissions = () => @@ -33,14 +33,14 @@ export const readCasesPermissions = () => settings: false, createComment: false, reopenCase: false, - assignCase: false, + assign: false, }); export const noCreateCasesPermissions = () => buildCasesPermissions({ create: false }); export const noCreateCommentCasesPermissions = () => buildCasesPermissions({ createComment: false }); export const noUpdateCasesPermissions = () => buildCasesPermissions({ update: false, reopenCase: false }); -export const noAssignCasesPermissions = () => buildCasesPermissions({ assignCase: false }); +export const noAssignCasesPermissions = () => buildCasesPermissions({ assign: false }); export const noPushCasesPermissions = () => buildCasesPermissions({ push: false }); export const noDeleteCasesPermissions = () => buildCasesPermissions({ delete: false }); export const noReopenCasesPermissions = () => buildCasesPermissions({ reopenCase: false }); @@ -54,7 +54,7 @@ export const onlyCreateCommentPermissions = () => push: false, createComment: true, reopenCase: false, - assignCase: false, + assign: false, }); export const onlyDeleteCasesPermission = () => buildCasesPermissions({ @@ -65,7 +65,7 @@ export const onlyDeleteCasesPermission = () => push: false, createComment: false, reopenCase: false, - assignCase: false, + assign: false, }); // In practice, a real life user should never have this configuration, but testing for thoroughness export const onlyReopenCasesPermission = () => @@ -77,7 +77,7 @@ export const onlyReopenCasesPermission = () => push: false, createComment: false, reopenCase: true, - assignCase: false, + assign: false, }); export const noConnectorsCasePermission = () => buildCasesPermissions({ connectors: false }); export const noCasesSettingsPermission = () => buildCasesPermissions({ settings: false }); @@ -93,7 +93,7 @@ export const buildCasesPermissions = (overrides: Partial { const { features, - permissions: { assignCase }, + permissions: { assign }, } = useCasesContext(); const { isAtLeastPlatinum } = useLicense(); const hasLicenseGreaterThanPlatinum = isAtLeastPlatinum(); @@ -40,7 +40,7 @@ export const useCasesFeatures = (): UseCasesFeatures => { */ isSyncAlertsEnabled: !features.alerts.enabled ? false : features.alerts.sync, metricsFeatures: features.metrics, - caseAssignmentAuthorized: hasLicenseGreaterThanPlatinum && assignCase, + caseAssignmentAuthorized: hasLicenseGreaterThanPlatinum && assign, pushToServiceAuthorized: hasLicenseGreaterThanPlatinum, observablesAuthorized: hasLicenseGreaterThanPlatinum, }), @@ -49,7 +49,7 @@ export const useCasesFeatures = (): UseCasesFeatures => { features.alerts.sync, features.metrics, hasLicenseGreaterThanPlatinum, - assignCase, + assign, ] ); diff --git a/x-pack/platform/plugins/shared/cases/public/components/actions/use_items_action.tsx b/x-pack/platform/plugins/shared/cases/public/components/actions/use_items_action.tsx index 3ccb596bc8466..a029cb12ba00c 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/actions/use_items_action.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/actions/use_items_action.tsx @@ -33,7 +33,7 @@ export const useItemsAction = ({ const { permissions } = useCasesContext(); const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); const [selectedCasesToEdit, setSelectedCasesToEdit] = useState([]); - const canUpdateStatus = permissions.update || permissions.assignCase; + const canUpdateStatus = permissions.update || permissions.assign; const isActionDisabled = isDisabled || !canUpdateStatus; const onFlyoutClosed = useCallback(() => setIsFlyoutOpen(false), []); diff --git a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.test.tsx b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.test.tsx index c9f1b53cf112f..05a380b2744aa 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.test.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.test.tsx @@ -396,7 +396,7 @@ describe('useActions', () => { connectors: true, settings: false, createComment: false, - assignCase: false, + assign: false, }, }); @@ -432,7 +432,7 @@ describe('useActions', () => { connectors: true, settings: false, createComment: false, - assignCase: false, + assign: false, }, }); @@ -468,7 +468,7 @@ describe('useActions', () => { connectors: true, settings: false, createComment: false, - assignCase: false, + assign: false, }, }); diff --git a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.tsx b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.tsx index f596cff11579f..d35e3ab3a6fdf 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.tsx @@ -84,7 +84,7 @@ const ActionColumnComponent: React.FC<{ theCase: CaseUI; disableActions: boolean const canDelete = deleteAction.canDelete; const canUpdate = statusAction.canUpdateStatus; - const canAssign = permissions.assignCase; + const canAssign = permissions.assign; const panels = useMemo((): EuiContextMenuPanelDescriptor[] => { const mainPanelItems: EuiContextMenuPanelItemDescriptor[] = []; @@ -249,7 +249,7 @@ interface UseBulkActionsProps { export const useActions = ({ disableActions }: UseBulkActionsProps): UseBulkActionsReturnValue => { const { permissions } = useCasesContext(); const shouldShowActions = - permissions.update || permissions.delete || permissions.reopenCase || permissions.assignCase; + permissions.update || permissions.delete || permissions.reopenCase || permissions.assign; return { actions: shouldShowActions diff --git a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_bulk_actions.tsx b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_bulk_actions.tsx index 98adead85250b..f20ce20a8b0e4 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_bulk_actions.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_bulk_actions.tsx @@ -75,7 +75,7 @@ export const useBulkActions = ({ const canDelete = deleteAction.canDelete; const canUpdate = statusAction.canUpdateStatus; - const canAssign = permissions.assignCase; + const canAssign = permissions.assign; const panels = useMemo((): EuiContextMenuPanelDescriptor[] => { const mainPanelItems: EuiContextMenuPanelItemDescriptor[] = []; diff --git a/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.test.tsx b/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.test.tsx index a44545b9e95f4..0f91de7bc2b65 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.test.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.test.tsx @@ -213,7 +213,7 @@ describe('Severity form field', () => { appMockRender = createAppMockRenderer({ permissions: { ...noCasesPermissions(), - assignCase: true, + assign: true, }, }); appMockRender.render(); @@ -225,7 +225,7 @@ describe('Severity form field', () => { appMockRender = createAppMockRenderer({ permissions: { ...noCasesPermissions(), - assignCase: true, + assign: true, update: true, }, }); @@ -238,7 +238,7 @@ describe('Severity form field', () => { appMockRender = createAppMockRenderer({ permissions: { ...noCasesPermissions(), - assignCase: true, + assign: true, delete: true, }, }); diff --git a/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.tsx b/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.tsx index 1e8a15017d806..3f7088acd03a0 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.tsx @@ -95,10 +95,7 @@ export const CasesTableUtilityBar: FunctionComponent = React.memo( * in the useBulkActions hook. */ const showBulkActions = - (permissions.update || - permissions.delete || - permissions.reopenCase || - permissions.assignCase) && + (permissions.update || permissions.delete || permissions.reopenCase || permissions.assign) && selectedCases.length > 0; const visibleCases = diff --git a/x-pack/platform/plugins/shared/cases/public/components/app/use_available_owners.ts b/x-pack/platform/plugins/shared/cases/public/components/app/use_available_owners.ts index c9db38a5d1a80..0f162797dca5d 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/app/use_available_owners.ts +++ b/x-pack/platform/plugins/shared/cases/public/components/app/use_available_owners.ts @@ -22,7 +22,6 @@ export const useAvailableCasesOwners = ( capabilities: Capability[] = allCasePermissions ): string[] => { const { capabilities: kibanaCapabilities } = useKibana().services.application; - return Object.entries(kibanaCapabilities).reduce( (availableOwners: string[], [featureId, kibanaCapability]) => { if (!featureId.endsWith('CasesV3')) { diff --git a/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.tsx b/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.tsx index 513666c803b22..fd4777f63a609 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.tsx @@ -180,7 +180,7 @@ const AssignUsersComponent: React.FC = ({ {isLoading && } - {!isLoading && permissions.assignCase && ( + {!isLoading && permissions.assign && ( { appMockRender.coreStart.application.capabilities = { ...appMockRender.coreStart.application.capabilities, - observabilityCasesV3: { - create_cases: true, - read_cases: true, - update_cases: true, - push_cases: true, - cases_connectors: true, - delete_cases: true, - cases_settings: true, - create_comment: true, - reopen_case: true, - assign_case: true, - }, - securitySolutionCasesV3: { - create_cases: true, - read_cases: true, - update_cases: true, - push_cases: true, - cases_connectors: true, - delete_cases: true, - cases_settings: true, - create_comment: true, - reopen_case: true, - assign_case: true, - }, + generalCasesV3: allCasesCapabilities(), + observabilityCasesV3: allCasesCapabilities(), + securitySolutionCasesV3: allCasesCapabilities(), }; const spyOnGetCases = jest.spyOn(api, 'getCases'); @@ -113,6 +92,12 @@ describe('useGetCases', () => { it('should set only the available owners when no owner is provided', async () => { appMockRender = createAppMockRenderer({ owner: [] }); + + appMockRender.coreStart.application.capabilities = { + ...appMockRender.coreStart.application.capabilities, + generalCasesV3: allCasesCapabilities(), + }; + const spyOnGetCases = jest.spyOn(api, 'getCases'); renderHook(() => useGetCases(), { diff --git a/x-pack/platform/plugins/shared/cases/public/utils/permissions.test.ts b/x-pack/platform/plugins/shared/cases/public/utils/permissions.test.ts index 93363061ed50e..3f0bf4a3271cf 100644 --- a/x-pack/platform/plugins/shared/cases/public/utils/permissions.test.ts +++ b/x-pack/platform/plugins/shared/cases/public/utils/permissions.test.ts @@ -16,7 +16,7 @@ describe('permissions', () => { ['delete'], ['push'], ['all'], - ['assignCase'], + ['assign'], ['createComment'], ]; @@ -44,7 +44,7 @@ describe('permissions', () => { 'update', 'delete', 'push', - 'assignCase', + 'assign', 'createComment', ]); }); diff --git a/x-pack/platform/plugins/shared/cases/public/utils/permissions.ts b/x-pack/platform/plugins/shared/cases/public/utils/permissions.ts index 607adf5b064d0..3f2d1c7b4cccf 100644 --- a/x-pack/platform/plugins/shared/cases/public/utils/permissions.ts +++ b/x-pack/platform/plugins/shared/cases/public/utils/permissions.ts @@ -14,7 +14,7 @@ export const isReadOnlyPermissions = (permissions: CasesPermissions) => { !permissions.update && !permissions.delete && !permissions.push && - !permissions.assignCase && + !permissions.assign && !permissions.createComment && permissions.read ); @@ -28,8 +28,7 @@ export const allCasePermissions: CasePermission[] = [ 'update', 'delete', 'push', - 'assignCase', - 'createComment', + 'assign', ]; export const getAllPermissionsExceptFrom = (capToExclude: CasePermission): CasePermission[] => diff --git a/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_update.ts b/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_update.ts index a31a0c7cbf333..9c6849a742234 100644 --- a/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_update.ts +++ b/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_update.ts @@ -355,7 +355,7 @@ export function getOperationsToAuthorize({ if (!onlyAssigneeOperations && !onlyReopenOperations) { operations.push(Operations.updateCase); } - + console.log(operations); return operations; } diff --git a/x-pack/platform/plugins/shared/cases/server/features/v1.ts b/x-pack/platform/plugins/shared/cases/server/features/v1.ts index 261cfd429a8b8..34130aaa0f024 100644 --- a/x-pack/platform/plugins/shared/cases/server/features/v1.ts +++ b/x-pack/platform/plugins/shared/cases/server/features/v1.ts @@ -61,7 +61,7 @@ export const getV1 = (): KibanaFeatureConfig => { push: [APP_ID], createComment: [APP_ID], reopenCase: [APP_ID], - assignCase: [APP_ID], + assign: [APP_ID], }, management: { insightsAndAlerting: [APP_ID], diff --git a/x-pack/platform/plugins/shared/cases/server/features/v2.ts b/x-pack/platform/plugins/shared/cases/server/features/v2.ts index cee73bc76836e..140d8af1ed864 100644 --- a/x-pack/platform/plugins/shared/cases/server/features/v2.ts +++ b/x-pack/platform/plugins/shared/cases/server/features/v2.ts @@ -64,6 +64,9 @@ export const getV2 = (): KibanaFeatureConfig => { read: [APP_ID], update: [APP_ID], push: [APP_ID], + createComment: [APP_ID], + reopenCase: [APP_ID], + assign: [APP_ID], }, management: { insightsAndAlerting: [APP_ID], @@ -78,7 +81,7 @@ export const getV2 = (): KibanaFeatureConfig => { minimal: [ { feature: FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], + privileges: ['minimal_all', 'create_comment', 'case_reopen'], }, ], }, diff --git a/x-pack/platform/plugins/shared/cases/server/features/v3.ts b/x-pack/platform/plugins/shared/cases/server/features/v3.ts index 9debe43bd6768..52e7c6a13db52 100644 --- a/x-pack/platform/plugins/shared/cases/server/features/v3.ts +++ b/x-pack/platform/plugins/shared/cases/server/features/v3.ts @@ -55,7 +55,9 @@ export const getV3 = (): KibanaFeatureConfig => { read: [APP_ID], update: [APP_ID], push: [APP_ID], - assignCase: [APP_ID], + createComment: [APP_ID], + reopenCase: [APP_ID], + // assign: [APP_ID], }, management: { insightsAndAlerting: [APP_ID], @@ -211,9 +213,9 @@ export const getV3 = (): KibanaFeatureConfig => { read: [], }, cases: { - assignCase: [APP_ID], + assign: [APP_ID], }, - ui: capabilities.assignCase, + ui: capabilities.assign, }, ], }, diff --git a/x-pack/platform/plugins/shared/features/common/feature_kibana_privileges.ts b/x-pack/platform/plugins/shared/features/common/feature_kibana_privileges.ts index 2ddc7b875bf7c..5a7f074b12ee7 100644 --- a/x-pack/platform/plugins/shared/features/common/feature_kibana_privileges.ts +++ b/x-pack/platform/plugins/shared/features/common/feature_kibana_privileges.ts @@ -243,11 +243,11 @@ export interface FeatureKibanaPrivileges { * @example * ```ts * { - * assignCase: ['securitySolution'] + * assign: ['securitySolution'] * } * ``` */ - assignCase?: readonly string[]; + assign?: readonly string[]; }; /** diff --git a/x-pack/platform/plugins/shared/features/server/feature_privilege_iterator/feature_privilege_iterator.ts b/x-pack/platform/plugins/shared/features/server/feature_privilege_iterator/feature_privilege_iterator.ts index 13a843f781a44..792b4a3d0ded9 100644 --- a/x-pack/platform/plugins/shared/features/server/feature_privilege_iterator/feature_privilege_iterator.ts +++ b/x-pack/platform/plugins/shared/features/server/feature_privilege_iterator/feature_privilege_iterator.ts @@ -180,9 +180,9 @@ function mergeWithSubFeatures( mergedConfig.cases?.reopenCase ?? [], subFeaturePrivilege.cases?.reopenCase ?? [] ), - assignCase: mergeArrays( - mergedConfig.cases?.assignCase ?? [], - subFeaturePrivilege.cases?.assignCase ?? [] + assign: mergeArrays( + mergedConfig.cases?.assign ?? [], + subFeaturePrivilege.cases?.assign ?? [] ), }; } diff --git a/x-pack/platform/plugins/shared/features/server/feature_schema.ts b/x-pack/platform/plugins/shared/features/server/feature_schema.ts index a373a97d639ce..00377d508151a 100644 --- a/x-pack/platform/plugins/shared/features/server/feature_schema.ts +++ b/x-pack/platform/plugins/shared/features/server/feature_schema.ts @@ -92,7 +92,7 @@ const casesSchemaObject = schema.maybe( settings: schema.maybe(casesSchema), createComment: schema.maybe(casesSchema), reopenCase: schema.maybe(casesSchema), - assignCase: schema.maybe(casesSchema), + assign: schema.maybe(casesSchema), }) ); diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/cases/components/cases.stories.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/cases/components/cases.stories.tsx index ae91737c5b526..862d26354f777 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/cases/components/cases.stories.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/cases/components/cases.stories.tsx @@ -30,7 +30,7 @@ const defaultProps: CasesProps = { settings: true, reopenCase: true, createComment: true, - assignCase: true, + assign: true, }, }; @@ -50,6 +50,6 @@ CasesPageWithNoPermissions.args = { settings: false, reopenCase: false, createComment: false, - assignCase: false, + assign: false, }, }; diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v1.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v1.ts index 8ce54ff36349a..d55685d01bf1f 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v1.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v1.ts @@ -52,7 +52,7 @@ export const getCasesFeature = ( push: [observabilityFeatureId], createComment: [observabilityFeatureId], reopenCase: [observabilityFeatureId], - assignCase: [observabilityFeatureId], + assign: [observabilityFeatureId], }, savedObject: { all: [...filesSavedObjectTypes], diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts index 1705320ee2361..85aaea39bb64c 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts @@ -50,7 +50,7 @@ export const getCasesFeatureV2 = ( read: [observabilityFeatureId], update: [observabilityFeatureId], push: [observabilityFeatureId], - assignCase: [observabilityFeatureId], + assign: [observabilityFeatureId], }, savedObject: { all: [...filesSavedObjectTypes], @@ -62,7 +62,7 @@ export const getCasesFeatureV2 = ( minimal: [ { feature: casesFeatureIdV3, - privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'assign'], }, ], }, diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts index 23942ef63dde9..b600ac840c48e 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts @@ -36,7 +36,7 @@ export const getCasesFeatureV3 = ( read: [observabilityFeatureId], update: [observabilityFeatureId], push: [observabilityFeatureId], - assignCase: [observabilityFeatureId], + assign: [observabilityFeatureId], }, savedObject: { all: [...filesSavedObjectTypes], @@ -187,7 +187,7 @@ export const getCasesFeatureV3 = ( groupType: 'independent', privileges: [ { - id: 'cases_assign', + id: 'assign', name: i18n.translate('xpack.observability.features.assignUsersSubFeatureName', { defaultMessage: 'Assign users to cases', }), @@ -197,9 +197,9 @@ export const getCasesFeatureV3 = ( read: [], }, cases: { - assignCase: [observabilityFeatureId], + assign: [observabilityFeatureId], }, - ui: casesCapabilities.assignCase, + ui: casesCapabilities.assign, }, ], }, diff --git a/x-pack/solutions/observability/plugins/observability_shared/public/utils/cases_permissions.ts b/x-pack/solutions/observability/plugins/observability_shared/public/utils/cases_permissions.ts index 97011f84300e0..a99d59a4e48b5 100644 --- a/x-pack/solutions/observability/plugins/observability_shared/public/utils/cases_permissions.ts +++ b/x-pack/solutions/observability/plugins/observability_shared/public/utils/cases_permissions.ts @@ -16,7 +16,7 @@ export const noCasesPermissions = () => ({ settings: false, createComment: false, reopenCase: false, - assignCase: false, + assign: false, }); export const allCasesPermissions = () => ({ @@ -30,5 +30,5 @@ export const allCasesPermissions = () => ({ settings: true, createComment: true, reopenCase: true, - assignCase: true, + assign: true, }); diff --git a/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts index 5f8759bdea9f1..d2dd3253ddc9f 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts @@ -60,7 +60,7 @@ export const getCasesBaseKibanaFeature = ({ push: [APP_ID], createComment: [APP_ID], reopenCase: [APP_ID], - assignCase: [APP_ID], + assign: [APP_ID], }, savedObject: { all: [...savedObjects.files], @@ -72,7 +72,7 @@ export const getCasesBaseKibanaFeature = ({ minimal: [ { feature: CASES_FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'assign'], }, ], }, diff --git a/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts index 129f56ec9f759..f9de0fe677f2b 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts @@ -60,7 +60,7 @@ export const getCasesBaseKibanaFeatureV2 = ({ read: [APP_ID], update: [APP_ID], push: [APP_ID], - assignCase: [APP_ID], + assign: [APP_ID], }, savedObject: { all: [...savedObjects.files], @@ -72,7 +72,7 @@ export const getCasesBaseKibanaFeatureV2 = ({ minimal: [ { feature: CASES_FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'assign'], }, ], }, diff --git a/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts b/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts index 9e7954502c144..1667c364dd0d5 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts @@ -175,7 +175,7 @@ export const getCasesSubFeaturesMapV3 = ({ groupType: 'independent', privileges: [ { - id: 'cases_assign', + id: 'assign', name: i18n.translate('securitySolutionPackages.features.assignUsersSubFeatureName', { defaultMessage: 'Assign users to cases', }), @@ -185,9 +185,9 @@ export const getCasesSubFeaturesMapV3 = ({ read: [], }, cases: { - assignCase: [APP_ID], + assign: [APP_ID], }, - ui: uiCapabilities.assignCase, + ui: uiCapabilities.assign, }, ], }, diff --git a/x-pack/solutions/security/plugins/security_solution/public/cases_test_utils.ts b/x-pack/solutions/security/plugins/security_solution/public/cases_test_utils.ts index 822ee32e74ec2..75cc14d51a36c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/cases_test_utils.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/cases_test_utils.ts @@ -17,7 +17,7 @@ export const noCasesCapabilities = (): CasesCapabilities => ({ cases_settings: false, case_reopen: false, create_comment: false, - cases_assign: false, + assign: false, }); export const readCasesCapabilities = (): CasesCapabilities => ({ @@ -30,7 +30,7 @@ export const readCasesCapabilities = (): CasesCapabilities => ({ cases_settings: false, case_reopen: false, create_comment: false, - cases_assign: false, + assign: false, }); export const allCasesCapabilities = (): CasesCapabilities => ({ @@ -43,7 +43,7 @@ export const allCasesCapabilities = (): CasesCapabilities => ({ cases_settings: true, case_reopen: true, create_comment: true, - cases_assign: true, + assign: true, }); export const noCasesPermissions = (): CasesPermissions => ({ @@ -57,7 +57,7 @@ export const noCasesPermissions = (): CasesPermissions => ({ settings: false, reopenCase: false, createComment: false, - assignCase: false, + assign: false, }); export const readCasesPermissions = (): CasesPermissions => ({ @@ -71,7 +71,7 @@ export const readCasesPermissions = (): CasesPermissions => ({ settings: false, reopenCase: false, createComment: false, - assignCase: false, + assign: false, }); export const writeCasesPermissions = (): CasesPermissions => ({ @@ -85,7 +85,7 @@ export const writeCasesPermissions = (): CasesPermissions => ({ settings: true, reopenCase: true, createComment: true, - assignCase: true, + assign: true, }); export const allCasesPermissions = (): CasesPermissions => ({ @@ -99,5 +99,5 @@ export const allCasesPermissions = (): CasesPermissions => ({ settings: true, reopenCase: true, createComment: true, - assignCase: true, + assign: true, }); diff --git a/x-pack/test/api_integration/apis/cases/common/roles.ts b/x-pack/test/api_integration/apis/cases/common/roles.ts index 035268ecb2d7e..5622aa82110fc 100644 --- a/x-pack/test/api_integration/apis/cases/common/roles.ts +++ b/x-pack/test/api_integration/apis/cases/common/roles.ts @@ -575,7 +575,7 @@ export const casesV3ReadAndAssignee: Role = { spaces: ['*'], base: [], feature: { - generalCasesV3: ['read', 'cases_assign'], + generalCasesV3: ['read', 'assign'], actions: ['all'], actionsSimulators: ['all'], }, diff --git a/x-pack/test/api_integration/apis/cases/privileges.ts b/x-pack/test/api_integration/apis/cases/privileges.ts index ffe477f74997f..e04ad34827a62 100644 --- a/x-pack/test/api_integration/apis/cases/privileges.ts +++ b/x-pack/test/api_integration/apis/cases/privileges.ts @@ -293,7 +293,12 @@ export default ({ getService }: FtrProviderContext): void => { }); } - for (const { user, owner } of [{ user: casesV3NoAssigneeUser, owner: CASES_APP_ID }]) { + for (const { user, owner } of [ + { user: casesV3NoAssigneeUser, owner: CASES_APP_ID }, + { user: casesV2NoCreateCommentWithReopenUser, owner: CASES_APP_ID }, + { user: obsCasesV2NoCreateCommentWithReopenUser, owner: OBSERVABILITY_APP_ID }, + { user: secCasesV2NoCreateCommentWithReopenUser, owner: SECURITY_SOLUTION_APP_ID }, + ]) { it(`User ${ user.username } with role(s) ${user.roles.join()} CANNOT change assignee`, async () => { @@ -308,7 +313,16 @@ export default ({ getService }: FtrProviderContext): void => { }); } - for (const { user, owner } of [{ user: casesV3ReadAndAssignUser, owner: CASES_APP_ID }]) { + for (const { user, owner } of [ + { user: casesV3ReadAndAssignUser, owner: CASES_APP_ID }, + { user: casesV2AllUser, owner: CASES_APP_ID }, + { user: secCasesV2AllUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: obsCasesV2AllUser, owner: OBSERVABILITY_APP_ID }, + { user: obsCasesAllUser, owner: OBSERVABILITY_APP_ID }, + { user: secCasesV2AllUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: obsCasesAllUser, owner: OBSERVABILITY_APP_ID }, + { user: obsCasesV2AllUser, owner: OBSERVABILITY_APP_ID }, + ]) { it(`User ${ user.username } with role(s) ${user.roles.join()} CAN change assignee`, async () => { diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index 8dff0c1c74ad4..7263871dea5a5 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -49,7 +49,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'cases_assign', + 'assign', ], observabilityCases: [ 'all', @@ -78,7 +78,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'cases_assign', + 'assign', ], observabilityAIAssistant: ['all', 'read', 'minimal_all', 'minimal_read'], slo: ['all', 'read', 'minimal_all', 'minimal_read'], @@ -169,7 +169,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'cases_assign', + 'assign', ], infrastructure: ['all', 'read', 'minimal_all', 'minimal_read'], logs: ['all', 'read', 'minimal_all', 'minimal_read'], diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts index 0d955083e7bb5..e2eb057ac86c0 100644 --- a/x-pack/test/api_integration/apis/security/privileges_basic.ts +++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts @@ -141,7 +141,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'cases_assign', + 'assign', ], observabilityCases: [ 'all', @@ -170,7 +170,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'cases_assign', + 'assign', ], observabilityAIAssistant: ['all', 'read', 'minimal_all', 'minimal_read'], slo: ['all', 'read', 'minimal_all', 'minimal_read'], @@ -261,7 +261,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'cases_assign', + 'assign', ], infrastructure: ['all', 'read', 'minimal_all', 'minimal_read'], logs: ['all', 'read', 'minimal_all', 'minimal_read'], diff --git a/x-pack/test/cases_api_integration/common/plugins/security_solution/server/plugin.ts b/x-pack/test/cases_api_integration/common/plugins/security_solution/server/plugin.ts index 36c1e9d80147d..f1ede956a59ec 100644 --- a/x-pack/test/cases_api_integration/common/plugins/security_solution/server/plugin.ts +++ b/x-pack/test/cases_api_integration/common/plugins/security_solution/server/plugin.ts @@ -168,7 +168,7 @@ export class FixturePlugin implements Plugin Date: Tue, 21 Jan 2025 14:52:25 -0500 Subject: [PATCH 15/32] Change magic string --- .../plugins/shared/cases/common/ui/types.ts | 2 +- .../shared/cases/common/utils/capabilities.ts | 1 - .../public/client/helpers/can_use_cases.ts | 8 ++++---- .../public/client/helpers/capabilities.test.ts | 18 +++++++++--------- .../public/client/helpers/capabilities.ts | 6 +++--- .../cases/public/common/lib/kibana/hooks.ts | 4 ++-- .../cases/public/common/mock/permissions.ts | 18 +++++++++--------- .../cases/public/common/use_cases_features.tsx | 6 +++--- .../components/actions/use_items_action.tsx | 2 +- .../components/all_cases/use_actions.test.tsx | 6 +++--- .../components/all_cases/use_actions.tsx | 4 ++-- .../components/all_cases/use_bulk_actions.tsx | 2 +- .../components/all_cases/utility_bar.test.tsx | 6 +++--- .../components/all_cases/utility_bar.tsx | 5 ++++- .../case_view/components/assign_users.tsx | 2 +- .../public/components/cases_context/index.tsx | 3 ++- .../cases/public/utils/permissions.test.ts | 5 ++--- .../shared/cases/public/utils/permissions.ts | 4 ++-- .../cases/server/client/cases/bulk_update.ts | 2 +- .../plugins/shared/cases/server/features/v1.ts | 2 +- .../observability/server/features/cases_v2.ts | 2 +- .../observability/server/features/cases_v3.ts | 2 +- .../src/cases/v1_features/kibana_features.ts | 2 +- .../src/cases/v2_features/kibana_features.ts | 2 +- .../cases/v3_features/kibana_sub_features.ts | 2 +- .../api_integration/apis/cases/common/roles.ts | 2 +- .../apis/security/privileges.ts | 6 +++--- .../apis/security/privileges_basic.ts | 6 +++--- .../plugins/security_solution/server/plugin.ts | 4 ++-- 29 files changed, 68 insertions(+), 66 deletions(-) diff --git a/x-pack/platform/plugins/shared/cases/common/ui/types.ts b/x-pack/platform/plugins/shared/cases/common/ui/types.ts index a9e190280fff7..31c47517ffc0d 100644 --- a/x-pack/platform/plugins/shared/cases/common/ui/types.ts +++ b/x-pack/platform/plugins/shared/cases/common/ui/types.ts @@ -321,7 +321,7 @@ export interface CasesPermissions { settings: boolean; reopenCase: boolean; createComment: boolean; - assign: boolean; + assignCases: boolean; } export interface CasesCapabilities { diff --git a/x-pack/platform/plugins/shared/cases/common/utils/capabilities.ts b/x-pack/platform/plugins/shared/cases/common/utils/capabilities.ts index da89ab7fbc0a4..99cbc1ae71f98 100644 --- a/x-pack/platform/plugins/shared/cases/common/utils/capabilities.ts +++ b/x-pack/platform/plugins/shared/cases/common/utils/capabilities.ts @@ -38,7 +38,6 @@ export const createUICapabilities = (): CasesUiCapabilities => ({ UPDATE_CASES_CAPABILITY, PUSH_CASES_CAPABILITY, CASES_CONNECTORS_CAPABILITY, - ASSIGN_CASE_CAPABILITY, ] as const, read: [READ_CASES_CAPABILITY, CASES_CONNECTORS_CAPABILITY] as const, delete: [DELETE_CASES_CAPABILITY] as const, diff --git a/x-pack/platform/plugins/shared/cases/public/client/helpers/can_use_cases.ts b/x-pack/platform/plugins/shared/cases/public/client/helpers/can_use_cases.ts index ee9044f0f4a45..e9594fbd7ff0f 100644 --- a/x-pack/platform/plugins/shared/cases/public/client/helpers/can_use_cases.ts +++ b/x-pack/platform/plugins/shared/cases/public/client/helpers/can_use_cases.ts @@ -34,8 +34,8 @@ export const canUseCases = const aggregatedPermissions = owners.reduce( // eslint-disable-next-line complexity (acc, owner) => { + console.log(capabilities); const userCapabilitiesForOwner = getUICapabilities(capabilities[getFeatureID(owner)]); - acc.create = acc.create || userCapabilitiesForOwner.create; acc.read = acc.read || userCapabilitiesForOwner.read; acc.update = acc.update || userCapabilitiesForOwner.update; @@ -45,7 +45,7 @@ export const canUseCases = acc.settings = acc.settings || userCapabilitiesForOwner.settings; acc.reopenCase = acc.reopenCase || userCapabilitiesForOwner.reopenCase; acc.createComment = acc.createComment || userCapabilitiesForOwner.createComment; - acc.assign = acc.assign || userCapabilitiesForOwner.assign; + acc.assignCases = acc.assignCases || userCapabilitiesForOwner.assignCases; const allFromAcc = acc.create && @@ -57,7 +57,7 @@ export const canUseCases = acc.settings && acc.reopenCase && acc.createComment && - acc.assign; + acc.assignCases; acc.all = acc.all || userCapabilitiesForOwner.all || allFromAcc; @@ -74,7 +74,7 @@ export const canUseCases = settings: false, reopenCase: false, createComment: false, - assign: false, + assignCases: false, } ); diff --git a/x-pack/platform/plugins/shared/cases/public/client/helpers/capabilities.test.ts b/x-pack/platform/plugins/shared/cases/public/client/helpers/capabilities.test.ts index 3ceb3a53dafbf..c04bfed11da27 100644 --- a/x-pack/platform/plugins/shared/cases/public/client/helpers/capabilities.test.ts +++ b/x-pack/platform/plugins/shared/cases/public/client/helpers/capabilities.test.ts @@ -12,7 +12,7 @@ describe('getUICapabilities', () => { expect(getUICapabilities(undefined)).toMatchInlineSnapshot(` Object { "all": false, - "assignCase": false, + "assign": false, "connectors": false, "create": false, "createComment": false, @@ -30,7 +30,7 @@ describe('getUICapabilities', () => { expect(getUICapabilities()).toMatchInlineSnapshot(` Object { "all": false, - "assignCase": false, + "assign": false, "connectors": false, "create": false, "createComment": false, @@ -48,7 +48,7 @@ describe('getUICapabilities', () => { expect(getUICapabilities({ create_cases: true })).toMatchInlineSnapshot(` Object { "all": false, - "assignCase": false, + "assign": false, "connectors": false, "create": true, "createComment": false, @@ -75,7 +75,7 @@ describe('getUICapabilities', () => { ).toMatchInlineSnapshot(` Object { "all": false, - "assignCase": false, + "assign": false, "connectors": false, "create": false, "createComment": false, @@ -93,7 +93,7 @@ describe('getUICapabilities', () => { expect(getUICapabilities({})).toMatchInlineSnapshot(` Object { "all": false, - "assignCase": false, + "assign": false, "connectors": false, "create": false, "createComment": false, @@ -120,7 +120,7 @@ describe('getUICapabilities', () => { ).toMatchInlineSnapshot(` Object { "all": false, - "assignCase": false, + "assign": false, "connectors": true, "create": false, "createComment": false, @@ -148,7 +148,7 @@ describe('getUICapabilities', () => { ).toMatchInlineSnapshot(` Object { "all": false, - "assignCase": false, + "assign": false, "connectors": false, "create": true, "createComment": false, @@ -176,7 +176,7 @@ describe('getUICapabilities', () => { ).toMatchInlineSnapshot(` Object { "all": false, - "assignCase": false, + "assign": false, "connectors": true, "create": true, "createComment": false, @@ -194,7 +194,7 @@ describe('getUICapabilities', () => { expect(getUICapabilities({ cases_settings: true })).toMatchInlineSnapshot(` Object { "all": false, - "assignCase": false, + "assign": false, "connectors": false, "create": false, "createComment": false, diff --git a/x-pack/platform/plugins/shared/cases/public/client/helpers/capabilities.ts b/x-pack/platform/plugins/shared/cases/public/client/helpers/capabilities.ts index 5688b875c3233..ba9d60b19001a 100644 --- a/x-pack/platform/plugins/shared/cases/public/client/helpers/capabilities.ts +++ b/x-pack/platform/plugins/shared/cases/public/client/helpers/capabilities.ts @@ -31,7 +31,7 @@ export const getUICapabilities = ( const settings = !!featureCapabilities?.[CASES_SETTINGS_CAPABILITY]; const reopenCase = !!featureCapabilities?.[CASES_REOPEN_CAPABILITY]; const createComment = !!featureCapabilities?.[CREATE_COMMENT_CAPABILITY]; - const assign = !!featureCapabilities?.[ASSIGN_CASE_CAPABILITY]; + const assignCases = !!featureCapabilities?.[ASSIGN_CASE_CAPABILITY]; const all = create && @@ -43,7 +43,7 @@ export const getUICapabilities = ( settings && reopenCase && createComment && - assign; + assignCases; return { all, @@ -56,6 +56,6 @@ export const getUICapabilities = ( settings, reopenCase, createComment, - assign, + assignCases, }; }; diff --git a/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/hooks.ts b/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/hooks.ts index 356108ac528d1..0dcf7ab2c683a 100644 --- a/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/hooks.ts +++ b/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/hooks.ts @@ -195,7 +195,7 @@ export const useApplicationCapabilities = (): UseApplicationCapabilities => { settings: permissions.settings, reopenCase: permissions.reopenCase, createComment: permissions.createComment, - assign: permissions.assign, + assignCases: permissions.assignCases, }, visualize: { crud: !!capabilities.visualize?.save, read: !!capabilities.visualize?.show }, dashboard: { @@ -220,7 +220,7 @@ export const useApplicationCapabilities = (): UseApplicationCapabilities => { permissions.settings, permissions.reopenCase, permissions.createComment, - permissions.assign, + permissions.assignCases, ] ); }; diff --git a/x-pack/platform/plugins/shared/cases/public/common/mock/permissions.ts b/x-pack/platform/plugins/shared/cases/public/common/mock/permissions.ts index 19982e453b13d..91fdef063645b 100644 --- a/x-pack/platform/plugins/shared/cases/public/common/mock/permissions.ts +++ b/x-pack/platform/plugins/shared/cases/public/common/mock/permissions.ts @@ -19,7 +19,7 @@ export const noCasesPermissions = () => settings: false, createComment: false, reopenCase: false, - assign: false, + assignCases: false, }); export const readCasesPermissions = () => @@ -33,14 +33,14 @@ export const readCasesPermissions = () => settings: false, createComment: false, reopenCase: false, - assign: false, + assignCases: false, }); export const noCreateCasesPermissions = () => buildCasesPermissions({ create: false }); export const noCreateCommentCasesPermissions = () => buildCasesPermissions({ createComment: false }); export const noUpdateCasesPermissions = () => buildCasesPermissions({ update: false, reopenCase: false }); -export const noAssignCasesPermissions = () => buildCasesPermissions({ assign: false }); +export const noAssignCasesPermissions = () => buildCasesPermissions({ assignCases: false }); export const noPushCasesPermissions = () => buildCasesPermissions({ push: false }); export const noDeleteCasesPermissions = () => buildCasesPermissions({ delete: false }); export const noReopenCasesPermissions = () => buildCasesPermissions({ reopenCase: false }); @@ -54,7 +54,7 @@ export const onlyCreateCommentPermissions = () => push: false, createComment: true, reopenCase: false, - assign: false, + assignCases: false, }); export const onlyDeleteCasesPermission = () => buildCasesPermissions({ @@ -65,7 +65,7 @@ export const onlyDeleteCasesPermission = () => push: false, createComment: false, reopenCase: false, - assign: false, + assignCases: false, }); // In practice, a real life user should never have this configuration, but testing for thoroughness export const onlyReopenCasesPermission = () => @@ -77,7 +77,7 @@ export const onlyReopenCasesPermission = () => push: false, createComment: false, reopenCase: true, - assign: false, + assignCases: false, }); export const noConnectorsCasePermission = () => buildCasesPermissions({ connectors: false }); export const noCasesSettingsPermission = () => buildCasesPermissions({ settings: false }); @@ -93,7 +93,7 @@ export const buildCasesPermissions = (overrides: Partial { const { features, - permissions: { assign }, + permissions: { assignCases }, } = useCasesContext(); const { isAtLeastPlatinum } = useLicense(); const hasLicenseGreaterThanPlatinum = isAtLeastPlatinum(); @@ -40,7 +40,7 @@ export const useCasesFeatures = (): UseCasesFeatures => { */ isSyncAlertsEnabled: !features.alerts.enabled ? false : features.alerts.sync, metricsFeatures: features.metrics, - caseAssignmentAuthorized: hasLicenseGreaterThanPlatinum && assign, + caseAssignmentAuthorized: hasLicenseGreaterThanPlatinum && assignCases, pushToServiceAuthorized: hasLicenseGreaterThanPlatinum, observablesAuthorized: hasLicenseGreaterThanPlatinum, }), @@ -49,7 +49,7 @@ export const useCasesFeatures = (): UseCasesFeatures => { features.alerts.sync, features.metrics, hasLicenseGreaterThanPlatinum, - assign, + assignCases, ] ); diff --git a/x-pack/platform/plugins/shared/cases/public/components/actions/use_items_action.tsx b/x-pack/platform/plugins/shared/cases/public/components/actions/use_items_action.tsx index a029cb12ba00c..835854e53771d 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/actions/use_items_action.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/actions/use_items_action.tsx @@ -33,7 +33,7 @@ export const useItemsAction = ({ const { permissions } = useCasesContext(); const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); const [selectedCasesToEdit, setSelectedCasesToEdit] = useState([]); - const canUpdateStatus = permissions.update || permissions.assign; + const canUpdateStatus = permissions.update || permissions.assignCases; const isActionDisabled = isDisabled || !canUpdateStatus; const onFlyoutClosed = useCallback(() => setIsFlyoutOpen(false), []); diff --git a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.test.tsx b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.test.tsx index 05a380b2744aa..64e579a61a937 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.test.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.test.tsx @@ -396,7 +396,7 @@ describe('useActions', () => { connectors: true, settings: false, createComment: false, - assign: false, + assignCases: false, }, }); @@ -432,7 +432,7 @@ describe('useActions', () => { connectors: true, settings: false, createComment: false, - assign: false, + assignCases: false, }, }); @@ -468,7 +468,7 @@ describe('useActions', () => { connectors: true, settings: false, createComment: false, - assign: false, + assignCases: false, }, }); diff --git a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.tsx b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.tsx index d35e3ab3a6fdf..b6df7ac269d36 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.tsx @@ -84,7 +84,7 @@ const ActionColumnComponent: React.FC<{ theCase: CaseUI; disableActions: boolean const canDelete = deleteAction.canDelete; const canUpdate = statusAction.canUpdateStatus; - const canAssign = permissions.assign; + const canAssign = permissions.assignCases; const panels = useMemo((): EuiContextMenuPanelDescriptor[] => { const mainPanelItems: EuiContextMenuPanelItemDescriptor[] = []; @@ -249,7 +249,7 @@ interface UseBulkActionsProps { export const useActions = ({ disableActions }: UseBulkActionsProps): UseBulkActionsReturnValue => { const { permissions } = useCasesContext(); const shouldShowActions = - permissions.update || permissions.delete || permissions.reopenCase || permissions.assign; + permissions.update || permissions.delete || permissions.reopenCase || permissions.assignCases; return { actions: shouldShowActions diff --git a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_bulk_actions.tsx b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_bulk_actions.tsx index f20ce20a8b0e4..7e3940cebdad7 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_bulk_actions.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_bulk_actions.tsx @@ -75,7 +75,7 @@ export const useBulkActions = ({ const canDelete = deleteAction.canDelete; const canUpdate = statusAction.canUpdateStatus; - const canAssign = permissions.assign; + const canAssign = permissions.assignCases; const panels = useMemo((): EuiContextMenuPanelDescriptor[] => { const mainPanelItems: EuiContextMenuPanelItemDescriptor[] = []; diff --git a/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.test.tsx b/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.test.tsx index 0f91de7bc2b65..6b18a4964bd1f 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.test.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.test.tsx @@ -213,7 +213,7 @@ describe('Severity form field', () => { appMockRender = createAppMockRenderer({ permissions: { ...noCasesPermissions(), - assign: true, + assignCases: true, }, }); appMockRender.render(); @@ -225,7 +225,7 @@ describe('Severity form field', () => { appMockRender = createAppMockRenderer({ permissions: { ...noCasesPermissions(), - assign: true, + assignCases: true, update: true, }, }); @@ -238,7 +238,7 @@ describe('Severity form field', () => { appMockRender = createAppMockRenderer({ permissions: { ...noCasesPermissions(), - assign: true, + assignCases: true, delete: true, }, }); diff --git a/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.tsx b/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.tsx index 3f7088acd03a0..70534b2986932 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.tsx @@ -95,7 +95,10 @@ export const CasesTableUtilityBar: FunctionComponent = React.memo( * in the useBulkActions hook. */ const showBulkActions = - (permissions.update || permissions.delete || permissions.reopenCase || permissions.assign) && + (permissions.update || + permissions.delete || + permissions.reopenCase || + permissions.assignCases) && selectedCases.length > 0; const visibleCases = diff --git a/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.tsx b/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.tsx index fd4777f63a609..10ffec7b4d671 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.tsx @@ -180,7 +180,7 @@ const AssignUsersComponent: React.FC = ({ {isLoading && } - {!isLoading && permissions.assign && ( + {!isLoading && permissions.assignCases && ( { ['delete'], ['push'], ['all'], - ['assign'], + ['assignCases'], ['createComment'], ]; @@ -44,8 +44,7 @@ describe('permissions', () => { 'update', 'delete', 'push', - 'assign', - 'createComment', + 'assignCases', ]); }); }); diff --git a/x-pack/platform/plugins/shared/cases/public/utils/permissions.ts b/x-pack/platform/plugins/shared/cases/public/utils/permissions.ts index 3f2d1c7b4cccf..d17add7d14eb5 100644 --- a/x-pack/platform/plugins/shared/cases/public/utils/permissions.ts +++ b/x-pack/platform/plugins/shared/cases/public/utils/permissions.ts @@ -14,7 +14,7 @@ export const isReadOnlyPermissions = (permissions: CasesPermissions) => { !permissions.update && !permissions.delete && !permissions.push && - !permissions.assign && + !permissions.assignCases && !permissions.createComment && permissions.read ); @@ -28,7 +28,7 @@ export const allCasePermissions: CasePermission[] = [ 'update', 'delete', 'push', - 'assign', + 'assignCases', ]; export const getAllPermissionsExceptFrom = (capToExclude: CasePermission): CasePermission[] => diff --git a/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_update.ts b/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_update.ts index 9c6849a742234..a31a0c7cbf333 100644 --- a/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_update.ts +++ b/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_update.ts @@ -355,7 +355,7 @@ export function getOperationsToAuthorize({ if (!onlyAssigneeOperations && !onlyReopenOperations) { operations.push(Operations.updateCase); } - console.log(operations); + return operations; } diff --git a/x-pack/platform/plugins/shared/cases/server/features/v1.ts b/x-pack/platform/plugins/shared/cases/server/features/v1.ts index 34130aaa0f024..e0ec993fff7ad 100644 --- a/x-pack/platform/plugins/shared/cases/server/features/v1.ts +++ b/x-pack/platform/plugins/shared/cases/server/features/v1.ts @@ -76,7 +76,7 @@ export const getV1 = (): KibanaFeatureConfig => { minimal: [ { feature: FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], + privileges: ['minimal_all', 'create_comment', 'case_reopen'], }, ], }, diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts index 85aaea39bb64c..1e94f6f3fb771 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts @@ -62,7 +62,7 @@ export const getCasesFeatureV2 = ( minimal: [ { feature: casesFeatureIdV3, - privileges: ['minimal_all', 'create_comment', 'case_reopen', 'assign'], + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], }, ], }, diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts index b600ac840c48e..4e58d011054d9 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts @@ -187,7 +187,7 @@ export const getCasesFeatureV3 = ( groupType: 'independent', privileges: [ { - id: 'assign', + id: 'cases_assign', name: i18n.translate('xpack.observability.features.assignUsersSubFeatureName', { defaultMessage: 'Assign users to cases', }), diff --git a/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts index d2dd3253ddc9f..ee9092c5786cd 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts @@ -72,7 +72,7 @@ export const getCasesBaseKibanaFeature = ({ minimal: [ { feature: CASES_FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen', 'assign'], + privileges: ['minimal_all', 'create_comment', 'case_reopen'], }, ], }, diff --git a/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts index f9de0fe677f2b..0c62a2da60605 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts @@ -72,7 +72,7 @@ export const getCasesBaseKibanaFeatureV2 = ({ minimal: [ { feature: CASES_FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen', 'assign'], + privileges: ['minimal_all', 'create_comment', 'case_reopen'], }, ], }, diff --git a/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts b/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts index 1667c364dd0d5..70060450cb64e 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts @@ -175,7 +175,7 @@ export const getCasesSubFeaturesMapV3 = ({ groupType: 'independent', privileges: [ { - id: 'assign', + id: 'cases_assign', name: i18n.translate('securitySolutionPackages.features.assignUsersSubFeatureName', { defaultMessage: 'Assign users to cases', }), diff --git a/x-pack/test/api_integration/apis/cases/common/roles.ts b/x-pack/test/api_integration/apis/cases/common/roles.ts index 5622aa82110fc..035268ecb2d7e 100644 --- a/x-pack/test/api_integration/apis/cases/common/roles.ts +++ b/x-pack/test/api_integration/apis/cases/common/roles.ts @@ -575,7 +575,7 @@ export const casesV3ReadAndAssignee: Role = { spaces: ['*'], base: [], feature: { - generalCasesV3: ['read', 'assign'], + generalCasesV3: ['read', 'cases_assign'], actions: ['all'], actionsSimulators: ['all'], }, diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index 7263871dea5a5..8dff0c1c74ad4 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -49,7 +49,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'assign', + 'cases_assign', ], observabilityCases: [ 'all', @@ -78,7 +78,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'assign', + 'cases_assign', ], observabilityAIAssistant: ['all', 'read', 'minimal_all', 'minimal_read'], slo: ['all', 'read', 'minimal_all', 'minimal_read'], @@ -169,7 +169,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'assign', + 'cases_assign', ], infrastructure: ['all', 'read', 'minimal_all', 'minimal_read'], logs: ['all', 'read', 'minimal_all', 'minimal_read'], diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts index e2eb057ac86c0..0d955083e7bb5 100644 --- a/x-pack/test/api_integration/apis/security/privileges_basic.ts +++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts @@ -141,7 +141,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'assign', + 'cases_assign', ], observabilityCases: [ 'all', @@ -170,7 +170,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'assign', + 'cases_assign', ], observabilityAIAssistant: ['all', 'read', 'minimal_all', 'minimal_read'], slo: ['all', 'read', 'minimal_all', 'minimal_read'], @@ -261,7 +261,7 @@ export default function ({ getService }: FtrProviderContext) { 'cases_settings', 'create_comment', 'case_reopen', - 'assign', + 'cases_assign', ], infrastructure: ['all', 'read', 'minimal_all', 'minimal_read'], logs: ['all', 'read', 'minimal_all', 'minimal_read'], diff --git a/x-pack/test/cases_api_integration/common/plugins/security_solution/server/plugin.ts b/x-pack/test/cases_api_integration/common/plugins/security_solution/server/plugin.ts index f1ede956a59ec..63fb13be2c611 100644 --- a/x-pack/test/cases_api_integration/common/plugins/security_solution/server/plugin.ts +++ b/x-pack/test/cases_api_integration/common/plugins/security_solution/server/plugin.ts @@ -168,7 +168,7 @@ export class FixturePlugin implements Plugin Date: Wed, 22 Jan 2025 06:05:19 +0000 Subject: [PATCH 16/32] Use correct form of permission/capability --- .../plugins/shared/cases/common/ui/types.ts | 2 +- .../public/client/helpers/can_use_cases.ts | 7 +++---- .../public/client/helpers/capabilities.ts | 2 +- .../cases/public/common/lib/kibana/hooks.ts | 4 ++-- .../common/lib/kibana/kibana_react.mock.tsx | 2 +- .../cases/public/common/mock/permissions.ts | 18 +++++++++--------- .../cases/public/common/use_cases_features.tsx | 6 +++--- .../components/actions/use_items_action.tsx | 2 +- .../components/all_cases/use_actions.test.tsx | 6 +++--- .../components/all_cases/use_actions.tsx | 4 ++-- .../components/all_cases/use_bulk_actions.tsx | 2 +- .../components/all_cases/utility_bar.test.tsx | 6 +++--- .../components/all_cases/utility_bar.tsx | 5 +---- .../components/app/use_available_owners.ts | 3 ++- .../case_view/components/assign_users.tsx | 2 +- .../public/components/cases_context/index.tsx | 4 ++-- .../cases/public/utils/permissions.test.ts | 4 ++-- .../shared/cases/public/utils/permissions.ts | 4 ++-- 18 files changed, 40 insertions(+), 43 deletions(-) diff --git a/x-pack/platform/plugins/shared/cases/common/ui/types.ts b/x-pack/platform/plugins/shared/cases/common/ui/types.ts index 4fa322f59104a..fc74ebd5077af 100644 --- a/x-pack/platform/plugins/shared/cases/common/ui/types.ts +++ b/x-pack/platform/plugins/shared/cases/common/ui/types.ts @@ -326,7 +326,7 @@ export interface CasesPermissions { settings: boolean; reopenCase: boolean; createComment: boolean; - assignCases: boolean; + assign: boolean; } export interface CasesCapabilities { diff --git a/x-pack/platform/plugins/shared/cases/public/client/helpers/can_use_cases.ts b/x-pack/platform/plugins/shared/cases/public/client/helpers/can_use_cases.ts index e9594fbd7ff0f..4f68e0c61f119 100644 --- a/x-pack/platform/plugins/shared/cases/public/client/helpers/can_use_cases.ts +++ b/x-pack/platform/plugins/shared/cases/public/client/helpers/can_use_cases.ts @@ -34,7 +34,6 @@ export const canUseCases = const aggregatedPermissions = owners.reduce( // eslint-disable-next-line complexity (acc, owner) => { - console.log(capabilities); const userCapabilitiesForOwner = getUICapabilities(capabilities[getFeatureID(owner)]); acc.create = acc.create || userCapabilitiesForOwner.create; acc.read = acc.read || userCapabilitiesForOwner.read; @@ -45,7 +44,7 @@ export const canUseCases = acc.settings = acc.settings || userCapabilitiesForOwner.settings; acc.reopenCase = acc.reopenCase || userCapabilitiesForOwner.reopenCase; acc.createComment = acc.createComment || userCapabilitiesForOwner.createComment; - acc.assignCases = acc.assignCases || userCapabilitiesForOwner.assignCases; + acc.assign = acc.assign || userCapabilitiesForOwner.assign; const allFromAcc = acc.create && @@ -57,7 +56,7 @@ export const canUseCases = acc.settings && acc.reopenCase && acc.createComment && - acc.assignCases; + acc.assign; acc.all = acc.all || userCapabilitiesForOwner.all || allFromAcc; @@ -74,7 +73,7 @@ export const canUseCases = settings: false, reopenCase: false, createComment: false, - assignCases: false, + assign: false, } ); diff --git a/x-pack/platform/plugins/shared/cases/public/client/helpers/capabilities.ts b/x-pack/platform/plugins/shared/cases/public/client/helpers/capabilities.ts index ba9d60b19001a..cbf467986b6ac 100644 --- a/x-pack/platform/plugins/shared/cases/public/client/helpers/capabilities.ts +++ b/x-pack/platform/plugins/shared/cases/public/client/helpers/capabilities.ts @@ -56,6 +56,6 @@ export const getUICapabilities = ( settings, reopenCase, createComment, - assignCases, + assign: assignCases, }; }; diff --git a/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/hooks.ts b/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/hooks.ts index 0dcf7ab2c683a..356108ac528d1 100644 --- a/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/hooks.ts +++ b/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/hooks.ts @@ -195,7 +195,7 @@ export const useApplicationCapabilities = (): UseApplicationCapabilities => { settings: permissions.settings, reopenCase: permissions.reopenCase, createComment: permissions.createComment, - assignCases: permissions.assignCases, + assign: permissions.assign, }, visualize: { crud: !!capabilities.visualize?.save, read: !!capabilities.visualize?.show }, dashboard: { @@ -220,7 +220,7 @@ export const useApplicationCapabilities = (): UseApplicationCapabilities => { permissions.settings, permissions.reopenCase, permissions.createComment, - permissions.assignCases, + permissions.assign, ] ); }; diff --git a/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/kibana_react.mock.tsx b/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/kibana_react.mock.tsx index 9ba76d394dc1e..8332ebd24b1c6 100644 --- a/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/kibana_react.mock.tsx +++ b/x-pack/platform/plugins/shared/cases/public/common/lib/kibana/kibana_react.mock.tsx @@ -93,7 +93,7 @@ export const createStartServicesMock = ({ license }: StartServiceArgs = {}): Sta cases_settings: true, case_reopen: true, create_comment: true, - assign: true, + cases_assign: true, }, visualize: { save: true, show: true }, dashboard: { show: true, createNew: true }, diff --git a/x-pack/platform/plugins/shared/cases/public/common/mock/permissions.ts b/x-pack/platform/plugins/shared/cases/public/common/mock/permissions.ts index 91fdef063645b..19982e453b13d 100644 --- a/x-pack/platform/plugins/shared/cases/public/common/mock/permissions.ts +++ b/x-pack/platform/plugins/shared/cases/public/common/mock/permissions.ts @@ -19,7 +19,7 @@ export const noCasesPermissions = () => settings: false, createComment: false, reopenCase: false, - assignCases: false, + assign: false, }); export const readCasesPermissions = () => @@ -33,14 +33,14 @@ export const readCasesPermissions = () => settings: false, createComment: false, reopenCase: false, - assignCases: false, + assign: false, }); export const noCreateCasesPermissions = () => buildCasesPermissions({ create: false }); export const noCreateCommentCasesPermissions = () => buildCasesPermissions({ createComment: false }); export const noUpdateCasesPermissions = () => buildCasesPermissions({ update: false, reopenCase: false }); -export const noAssignCasesPermissions = () => buildCasesPermissions({ assignCases: false }); +export const noAssignCasesPermissions = () => buildCasesPermissions({ assign: false }); export const noPushCasesPermissions = () => buildCasesPermissions({ push: false }); export const noDeleteCasesPermissions = () => buildCasesPermissions({ delete: false }); export const noReopenCasesPermissions = () => buildCasesPermissions({ reopenCase: false }); @@ -54,7 +54,7 @@ export const onlyCreateCommentPermissions = () => push: false, createComment: true, reopenCase: false, - assignCases: false, + assign: false, }); export const onlyDeleteCasesPermission = () => buildCasesPermissions({ @@ -65,7 +65,7 @@ export const onlyDeleteCasesPermission = () => push: false, createComment: false, reopenCase: false, - assignCases: false, + assign: false, }); // In practice, a real life user should never have this configuration, but testing for thoroughness export const onlyReopenCasesPermission = () => @@ -77,7 +77,7 @@ export const onlyReopenCasesPermission = () => push: false, createComment: false, reopenCase: true, - assignCases: false, + assign: false, }); export const noConnectorsCasePermission = () => buildCasesPermissions({ connectors: false }); export const noCasesSettingsPermission = () => buildCasesPermissions({ settings: false }); @@ -93,7 +93,7 @@ export const buildCasesPermissions = (overrides: Partial { const { features, - permissions: { assignCases }, + permissions: { assign }, } = useCasesContext(); const { isAtLeastPlatinum } = useLicense(); const hasLicenseGreaterThanPlatinum = isAtLeastPlatinum(); @@ -40,7 +40,7 @@ export const useCasesFeatures = (): UseCasesFeatures => { */ isSyncAlertsEnabled: !features.alerts.enabled ? false : features.alerts.sync, metricsFeatures: features.metrics, - caseAssignmentAuthorized: hasLicenseGreaterThanPlatinum && assignCases, + caseAssignmentAuthorized: hasLicenseGreaterThanPlatinum && assign, pushToServiceAuthorized: hasLicenseGreaterThanPlatinum, observablesAuthorized: hasLicenseGreaterThanPlatinum, }), @@ -49,7 +49,7 @@ export const useCasesFeatures = (): UseCasesFeatures => { features.alerts.sync, features.metrics, hasLicenseGreaterThanPlatinum, - assignCases, + assign, ] ); diff --git a/x-pack/platform/plugins/shared/cases/public/components/actions/use_items_action.tsx b/x-pack/platform/plugins/shared/cases/public/components/actions/use_items_action.tsx index 835854e53771d..a029cb12ba00c 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/actions/use_items_action.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/actions/use_items_action.tsx @@ -33,7 +33,7 @@ export const useItemsAction = ({ const { permissions } = useCasesContext(); const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); const [selectedCasesToEdit, setSelectedCasesToEdit] = useState([]); - const canUpdateStatus = permissions.update || permissions.assignCases; + const canUpdateStatus = permissions.update || permissions.assign; const isActionDisabled = isDisabled || !canUpdateStatus; const onFlyoutClosed = useCallback(() => setIsFlyoutOpen(false), []); diff --git a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.test.tsx b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.test.tsx index 64e579a61a937..05a380b2744aa 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.test.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.test.tsx @@ -396,7 +396,7 @@ describe('useActions', () => { connectors: true, settings: false, createComment: false, - assignCases: false, + assign: false, }, }); @@ -432,7 +432,7 @@ describe('useActions', () => { connectors: true, settings: false, createComment: false, - assignCases: false, + assign: false, }, }); @@ -468,7 +468,7 @@ describe('useActions', () => { connectors: true, settings: false, createComment: false, - assignCases: false, + assign: false, }, }); diff --git a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.tsx b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.tsx index b6df7ac269d36..d35e3ab3a6fdf 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_actions.tsx @@ -84,7 +84,7 @@ const ActionColumnComponent: React.FC<{ theCase: CaseUI; disableActions: boolean const canDelete = deleteAction.canDelete; const canUpdate = statusAction.canUpdateStatus; - const canAssign = permissions.assignCases; + const canAssign = permissions.assign; const panels = useMemo((): EuiContextMenuPanelDescriptor[] => { const mainPanelItems: EuiContextMenuPanelItemDescriptor[] = []; @@ -249,7 +249,7 @@ interface UseBulkActionsProps { export const useActions = ({ disableActions }: UseBulkActionsProps): UseBulkActionsReturnValue => { const { permissions } = useCasesContext(); const shouldShowActions = - permissions.update || permissions.delete || permissions.reopenCase || permissions.assignCases; + permissions.update || permissions.delete || permissions.reopenCase || permissions.assign; return { actions: shouldShowActions diff --git a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_bulk_actions.tsx b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_bulk_actions.tsx index 7e3940cebdad7..f20ce20a8b0e4 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_bulk_actions.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/all_cases/use_bulk_actions.tsx @@ -75,7 +75,7 @@ export const useBulkActions = ({ const canDelete = deleteAction.canDelete; const canUpdate = statusAction.canUpdateStatus; - const canAssign = permissions.assignCases; + const canAssign = permissions.assign; const panels = useMemo((): EuiContextMenuPanelDescriptor[] => { const mainPanelItems: EuiContextMenuPanelItemDescriptor[] = []; diff --git a/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.test.tsx b/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.test.tsx index 6b18a4964bd1f..0f91de7bc2b65 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.test.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.test.tsx @@ -213,7 +213,7 @@ describe('Severity form field', () => { appMockRender = createAppMockRenderer({ permissions: { ...noCasesPermissions(), - assignCases: true, + assign: true, }, }); appMockRender.render(); @@ -225,7 +225,7 @@ describe('Severity form field', () => { appMockRender = createAppMockRenderer({ permissions: { ...noCasesPermissions(), - assignCases: true, + assign: true, update: true, }, }); @@ -238,7 +238,7 @@ describe('Severity form field', () => { appMockRender = createAppMockRenderer({ permissions: { ...noCasesPermissions(), - assignCases: true, + assign: true, delete: true, }, }); diff --git a/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.tsx b/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.tsx index 70534b2986932..3f7088acd03a0 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/all_cases/utility_bar.tsx @@ -95,10 +95,7 @@ export const CasesTableUtilityBar: FunctionComponent = React.memo( * in the useBulkActions hook. */ const showBulkActions = - (permissions.update || - permissions.delete || - permissions.reopenCase || - permissions.assignCases) && + (permissions.update || permissions.delete || permissions.reopenCase || permissions.assign) && selectedCases.length > 0; const visibleCases = diff --git a/x-pack/platform/plugins/shared/cases/public/components/app/use_available_owners.ts b/x-pack/platform/plugins/shared/cases/public/components/app/use_available_owners.ts index 0f162797dca5d..ecaaafe965dd7 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/app/use_available_owners.ts +++ b/x-pack/platform/plugins/shared/cases/public/components/app/use_available_owners.ts @@ -28,7 +28,8 @@ export const useAvailableCasesOwners = ( return availableOwners; } for (const cap of capabilities) { - const hasCapability = !!kibanaCapability[`${cap}_cases`]; + const hasCapability = + !!kibanaCapability[`${cap}_cases`] || !!kibanaCapability[`cases_${cap}`]; if (!hasCapability) { return availableOwners; } diff --git a/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.tsx b/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.tsx index 10ffec7b4d671..fd4777f63a609 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.tsx @@ -180,7 +180,7 @@ const AssignUsersComponent: React.FC = ({ {isLoading && } - {!isLoading && permissions.assignCases && ( + {!isLoading && permissions.assign && ( { ['delete'], ['push'], ['all'], - ['assignCases'], + ['assign'], ['createComment'], ]; @@ -44,7 +44,7 @@ describe('permissions', () => { 'update', 'delete', 'push', - 'assignCases', + 'assign', ]); }); }); diff --git a/x-pack/platform/plugins/shared/cases/public/utils/permissions.ts b/x-pack/platform/plugins/shared/cases/public/utils/permissions.ts index d17add7d14eb5..3f2d1c7b4cccf 100644 --- a/x-pack/platform/plugins/shared/cases/public/utils/permissions.ts +++ b/x-pack/platform/plugins/shared/cases/public/utils/permissions.ts @@ -14,7 +14,7 @@ export const isReadOnlyPermissions = (permissions: CasesPermissions) => { !permissions.update && !permissions.delete && !permissions.push && - !permissions.assignCases && + !permissions.assign && !permissions.createComment && permissions.read ); @@ -28,7 +28,7 @@ export const allCasePermissions: CasePermission[] = [ 'update', 'delete', 'push', - 'assignCases', + 'assign', ]; export const getAllPermissionsExceptFrom = (capToExclude: CasePermission): CasePermission[] => From e7a43755d7583afabade4be0cf9b21ecb15dc036 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Wed, 22 Jan 2025 06:33:09 +0000 Subject: [PATCH 17/32] Fix types --- .../plugins/security_solution/public/cases_test_utils.ts | 6 +++--- .../plugins/cases/public/application.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/cases_test_utils.ts b/x-pack/solutions/security/plugins/security_solution/public/cases_test_utils.ts index 75cc14d51a36c..8352959b888fb 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/cases_test_utils.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/cases_test_utils.ts @@ -17,7 +17,7 @@ export const noCasesCapabilities = (): CasesCapabilities => ({ cases_settings: false, case_reopen: false, create_comment: false, - assign: false, + cases_assign: false, }); export const readCasesCapabilities = (): CasesCapabilities => ({ @@ -30,7 +30,7 @@ export const readCasesCapabilities = (): CasesCapabilities => ({ cases_settings: false, case_reopen: false, create_comment: false, - assign: false, + cases_assign: false, }); export const allCasesCapabilities = (): CasesCapabilities => ({ @@ -43,7 +43,7 @@ export const allCasesCapabilities = (): CasesCapabilities => ({ cases_settings: true, case_reopen: true, create_comment: true, - assign: true, + cases_assign: true, }); export const noCasesPermissions = (): CasesPermissions => ({ diff --git a/x-pack/test/functional_with_es_ssl/plugins/cases/public/application.tsx b/x-pack/test/functional_with_es_ssl/plugins/cases/public/application.tsx index fece838d20ab3..993dae5add26c 100644 --- a/x-pack/test/functional_with_es_ssl/plugins/cases/public/application.tsx +++ b/x-pack/test/functional_with_es_ssl/plugins/cases/public/application.tsx @@ -44,7 +44,7 @@ const permissions = { settings: true, createComment: true, reopenCase: true, - assignCase: true, + assign: true, }; const attachments = [{ type: AttachmentType.user as const, comment: 'test' }]; From e726edabe6c0e8638f250f59bc79f18642f9e21e Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Wed, 22 Jan 2025 22:28:22 +0000 Subject: [PATCH 18/32] Fix tests, clean up solution feature definitions --- .../cases/common/utils/capabilities.test.tsx | 2 +- .../plugins/shared/cases/server/features/v1.ts | 2 +- .../plugins/shared/cases/server/features/v2.ts | 2 +- .../__snapshots__/oss_features.test.ts.snap | 12 ++++++------ .../feature_privilege_iterator.test.ts | 18 +++++++++--------- .../observability/server/features/cases_v1.ts | 2 +- .../src/cases/v1_features/kibana_features.ts | 2 +- .../src/cases/v2_features/kibana_features.ts | 2 +- .../api_integration/apis/cases/common/roles.ts | 8 +++++++- .../api_integration/apis/cases/privileges.ts | 5 +++++ 10 files changed, 33 insertions(+), 22 deletions(-) diff --git a/x-pack/platform/plugins/shared/cases/common/utils/capabilities.test.tsx b/x-pack/platform/plugins/shared/cases/common/utils/capabilities.test.tsx index edfb5a14da532..daf3ee66b65fe 100644 --- a/x-pack/platform/plugins/shared/cases/common/utils/capabilities.test.tsx +++ b/x-pack/platform/plugins/shared/cases/common/utils/capabilities.test.tsx @@ -18,7 +18,7 @@ describe('createUICapabilities', () => { "push_cases", "cases_connectors", ], - "assignCase": Array [ + "assign": Array [ "cases_assign", ], "createComment": Array [ diff --git a/x-pack/platform/plugins/shared/cases/server/features/v1.ts b/x-pack/platform/plugins/shared/cases/server/features/v1.ts index e0ec993fff7ad..34130aaa0f024 100644 --- a/x-pack/platform/plugins/shared/cases/server/features/v1.ts +++ b/x-pack/platform/plugins/shared/cases/server/features/v1.ts @@ -76,7 +76,7 @@ export const getV1 = (): KibanaFeatureConfig => { minimal: [ { feature: FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen'], + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], }, ], }, diff --git a/x-pack/platform/plugins/shared/cases/server/features/v2.ts b/x-pack/platform/plugins/shared/cases/server/features/v2.ts index 140d8af1ed864..cb5010082d053 100644 --- a/x-pack/platform/plugins/shared/cases/server/features/v2.ts +++ b/x-pack/platform/plugins/shared/cases/server/features/v2.ts @@ -81,7 +81,7 @@ export const getV2 = (): KibanaFeatureConfig => { minimal: [ { feature: FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen'], + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], }, ], }, diff --git a/x-pack/platform/plugins/shared/features/server/__snapshots__/oss_features.test.ts.snap b/x-pack/platform/plugins/shared/features/server/__snapshots__/oss_features.test.ts.snap index 672ac614e261f..fabfb34c67c4b 100644 --- a/x-pack/platform/plugins/shared/features/server/__snapshots__/oss_features.test.ts.snap +++ b/x-pack/platform/plugins/shared/features/server/__snapshots__/oss_features.test.ts.snap @@ -558,7 +558,7 @@ Array [ ], "cases": Object { "all": Array [], - "assignCase": Array [], + "assign": Array [], "create": Array [], "createComment": Array [], "delete": Array [], @@ -723,7 +723,7 @@ Array [ ], "cases": Object { "all": Array [], - "assignCase": Array [], + "assign": Array [], "create": Array [], "createComment": Array [], "delete": Array [], @@ -1077,7 +1077,7 @@ Array [ ], "cases": Object { "all": Array [], - "assignCase": Array [], + "assign": Array [], "create": Array [], "createComment": Array [], "delete": Array [], @@ -1225,7 +1225,7 @@ Array [ ], "cases": Object { "all": Array [], - "assignCase": Array [], + "assign": Array [], "create": Array [], "createComment": Array [], "delete": Array [], @@ -1390,7 +1390,7 @@ Array [ ], "cases": Object { "all": Array [], - "assignCase": Array [], + "assign": Array [], "create": Array [], "createComment": Array [], "delete": Array [], @@ -1744,7 +1744,7 @@ Array [ ], "cases": Object { "all": Array [], - "assignCase": Array [], + "assign": Array [], "create": Array [], "createComment": Array [], "delete": Array [], diff --git a/x-pack/platform/plugins/shared/features/server/feature_privilege_iterator/feature_privilege_iterator.test.ts b/x-pack/platform/plugins/shared/features/server/feature_privilege_iterator/feature_privilege_iterator.test.ts index ab6c002bb2d5d..977c6a1eda5fc 100644 --- a/x-pack/platform/plugins/shared/features/server/feature_privilege_iterator/feature_privilege_iterator.test.ts +++ b/x-pack/platform/plugins/shared/features/server/feature_privilege_iterator/feature_privilege_iterator.test.ts @@ -822,7 +822,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type', 'cases-settings-sub-type'], createComment: ['cases-create-comment-type', 'cases-create-comment-sub-type'], reopenCase: ['cases-reopen-type', 'cases-reopen-sub-type'], - assignCase: [], + assign: [], }, ui: ['ui-action', 'ui-sub-type'], }, @@ -861,7 +861,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-sub-type'], createComment: ['cases-create-comment-sub-type'], reopenCase: ['cases-reopen-sub-type'], - assignCase: [], + assign: [], }, ui: ['ui-action', 'ui-sub-type'], }, @@ -1014,7 +1014,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], - assignCase: [], + assign: [], }, ui: ['ui-action'], }, @@ -1052,7 +1052,7 @@ describe('featurePrivilegeIterator', () => { settings: [], createComment: [], reopenCase: [], - assignCase: [], + assign: [], }, ui: ['ui-action'], }, @@ -1213,7 +1213,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type', 'cases-settings-sub-type'], createComment: ['cases-create-comment-type', 'cases-create-comment-sub-type'], reopenCase: ['cases-reopen-type', 'cases-reopen-sub-type'], - assignCase: [], + assign: [], }, ui: ['ui-action', 'ui-sub-type'], }, @@ -1461,7 +1461,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-sub-type'], createComment: ['cases-create-comment-sub-type'], reopenCase: ['cases-reopen-sub-type'], - assignCase: [], + assign: [], }, ui: ['ui-sub-type'], }, @@ -1500,7 +1500,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-sub-type'], createComment: ['cases-create-comment-sub-type'], reopenCase: ['cases-reopen-sub-type'], - assignCase: [], + assign: [], }, ui: ['ui-sub-type'], }, @@ -1637,7 +1637,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], - assignCase: [], + assign: [], }, ui: ['ui-action'], }, @@ -1675,7 +1675,7 @@ describe('featurePrivilegeIterator', () => { settings: [], createComment: [], reopenCase: [], - assignCase: [], + assign: [], }, ui: ['ui-action'], }, diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v1.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v1.ts index d55685d01bf1f..4c970251cce8a 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v1.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v1.ts @@ -64,7 +64,7 @@ export const getCasesFeature = ( minimal: [ { feature: casesFeatureIdV3, - privileges: ['minimal_all', 'create_comment', 'case_reopen'], + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], }, ], }, diff --git a/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts index ee9092c5786cd..98cdd2eed1e67 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts @@ -72,7 +72,7 @@ export const getCasesBaseKibanaFeature = ({ minimal: [ { feature: CASES_FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen'], + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], }, ], }, diff --git a/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts index 0c62a2da60605..0fcf330bf8b6d 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts @@ -72,7 +72,7 @@ export const getCasesBaseKibanaFeatureV2 = ({ minimal: [ { feature: CASES_FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen'], + privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], }, ], }, diff --git a/x-pack/test/api_integration/apis/cases/common/roles.ts b/x-pack/test/api_integration/apis/cases/common/roles.ts index 035268ecb2d7e..8066318cccf04 100644 --- a/x-pack/test/api_integration/apis/cases/common/roles.ts +++ b/x-pack/test/api_integration/apis/cases/common/roles.ts @@ -550,7 +550,13 @@ export const casesV3NoAssignee: Role = { spaces: ['*'], base: [], feature: { - generalCasesV3: ['read', 'update', 'create', 'delete', 'create_comment', 'case_reopen'], + generalCasesV3: [ + 'minimal_all', + 'create_comment', + 'case_reopen', + 'cases_delete', + 'cases_settings', + ], actions: ['all'], actionsSimulators: ['all'], }, diff --git a/x-pack/test/api_integration/apis/cases/privileges.ts b/x-pack/test/api_integration/apis/cases/privileges.ts index e04ad34827a62..56b94c90cdeee 100644 --- a/x-pack/test/api_integration/apis/cases/privileges.ts +++ b/x-pack/test/api_integration/apis/cases/privileges.ts @@ -298,6 +298,9 @@ export default ({ getService }: FtrProviderContext): void => { { user: casesV2NoCreateCommentWithReopenUser, owner: CASES_APP_ID }, { user: obsCasesV2NoCreateCommentWithReopenUser, owner: OBSERVABILITY_APP_ID }, { user: secCasesV2NoCreateCommentWithReopenUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: secReadUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: casesOnlyDeleteUser, owner: CASES_APP_ID }, + { user: obsCasesOnlyDeleteUser, owner: OBSERVABILITY_APP_ID }, ]) { it(`User ${ user.username @@ -315,6 +318,8 @@ export default ({ getService }: FtrProviderContext): void => { for (const { user, owner } of [ { user: casesV3ReadAndAssignUser, owner: CASES_APP_ID }, + { user: secAllUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: casesAllUser, owner: CASES_APP_ID }, { user: casesV2AllUser, owner: CASES_APP_ID }, { user: secCasesV2AllUser, owner: SECURITY_SOLUTION_APP_ID }, { user: obsCasesV2AllUser, owner: OBSERVABILITY_APP_ID }, From 9964c69fa5513ba6d6c5837ba1ce16544bbf08a0 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Wed, 22 Jan 2025 23:17:42 +0000 Subject: [PATCH 19/32] Align solution feature definitions --- x-pack/platform/plugins/shared/cases/server/features/v3.ts | 2 +- .../plugins/observability/server/features/cases_v2.ts | 2 ++ .../plugins/observability/server/features/cases_v3.ts | 2 ++ .../packages/features/src/cases/v2_features/kibana_features.ts | 2 ++ .../packages/features/src/cases/v3_features/kibana_features.ts | 3 +++ 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/x-pack/platform/plugins/shared/cases/server/features/v3.ts b/x-pack/platform/plugins/shared/cases/server/features/v3.ts index 52e7c6a13db52..09e93b6cc7be7 100644 --- a/x-pack/platform/plugins/shared/cases/server/features/v3.ts +++ b/x-pack/platform/plugins/shared/cases/server/features/v3.ts @@ -57,7 +57,7 @@ export const getV3 = (): KibanaFeatureConfig => { push: [APP_ID], createComment: [APP_ID], reopenCase: [APP_ID], - // assign: [APP_ID], + assign: [APP_ID], }, management: { insightsAndAlerting: [APP_ID], diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts index 1e94f6f3fb771..4cf2930e7027a 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts @@ -50,6 +50,8 @@ export const getCasesFeatureV2 = ( read: [observabilityFeatureId], update: [observabilityFeatureId], push: [observabilityFeatureId], + createComment: [observabilityFeatureId], + reopenCase: [observabilityFeatureId], assign: [observabilityFeatureId], }, savedObject: { diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts index 4e58d011054d9..ceb17151b2d39 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts @@ -36,6 +36,8 @@ export const getCasesFeatureV3 = ( read: [observabilityFeatureId], update: [observabilityFeatureId], push: [observabilityFeatureId], + createComment: [observabilityFeatureId], + reopenCase: [observabilityFeatureId], assign: [observabilityFeatureId], }, savedObject: { diff --git a/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts index 0fcf330bf8b6d..f5126e277e665 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts @@ -60,6 +60,8 @@ export const getCasesBaseKibanaFeatureV2 = ({ read: [APP_ID], update: [APP_ID], push: [APP_ID], + createComment: [APP_ID], + reopenCase: [APP_ID], assign: [APP_ID], }, savedObject: { diff --git a/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_features.ts index c9a08ebb8614d..fbe25ef75da11 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_features.ts @@ -42,6 +42,9 @@ export const getCasesBaseKibanaFeatureV3 = ({ read: [APP_ID], update: [APP_ID], push: [APP_ID], + createComment: [APP_ID], + reopenCase: [APP_ID], + assign: [APP_ID], }, savedObject: { all: [...savedObjects.files], From c83a7bf08c502fa284b298d08af08051a1e281c3 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Thu, 23 Jan 2025 00:12:54 +0000 Subject: [PATCH 20/32] More integration test scenarios --- x-pack/test/api_integration/apis/cases/privileges.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/x-pack/test/api_integration/apis/cases/privileges.ts b/x-pack/test/api_integration/apis/cases/privileges.ts index 56b94c90cdeee..ecc222902f3de 100644 --- a/x-pack/test/api_integration/apis/cases/privileges.ts +++ b/x-pack/test/api_integration/apis/cases/privileges.ts @@ -78,6 +78,7 @@ export default ({ getService }: FtrProviderContext): void => { { user: secCasesV3AllUser, owner: SECURITY_SOLUTION_APP_ID }, { user: casesV3AllUser, owner: CASES_APP_ID }, { user: obsCasesV3AllUser, owner: OBSERVABILITY_APP_ID }, + { user: casesV3NoAssigneeUser, owner: CASES_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can create a case`, async () => { await createCase(supertestWithoutAuth, getPostCaseRequest({ owner }), 200, { @@ -101,6 +102,7 @@ export default ({ getService }: FtrProviderContext): void => { { user: secCasesV3AllUser, owner: SECURITY_SOLUTION_APP_ID }, { user: casesV3AllUser, owner: CASES_APP_ID }, { user: obsCasesV3AllUser, owner: OBSERVABILITY_APP_ID }, + { user: casesV3NoAssigneeUser, owner: CASES_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can get a case`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); @@ -166,6 +168,7 @@ export default ({ getService }: FtrProviderContext): void => { { user: secCasesV3AllUser, owner: SECURITY_SOLUTION_APP_ID }, { user: casesV3AllUser, owner: CASES_APP_ID }, { user: obsCasesV3AllUser, owner: OBSERVABILITY_APP_ID }, + { user: casesV3NoAssigneeUser, owner: CASES_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can delete a case`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); @@ -210,6 +213,7 @@ export default ({ getService }: FtrProviderContext): void => { { user: secCasesV3AllUser, owner: SECURITY_SOLUTION_APP_ID }, { user: casesV3AllUser, owner: CASES_APP_ID }, { user: obsCasesV3AllUser, owner: OBSERVABILITY_APP_ID }, + { user: casesV3NoAssigneeUser, owner: CASES_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can reopen a case`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); @@ -275,6 +279,7 @@ export default ({ getService }: FtrProviderContext): void => { { user: secCasesV3AllUser, owner: SECURITY_SOLUTION_APP_ID }, { user: casesV3AllUser, owner: CASES_APP_ID }, { user: obsCasesV3AllUser, owner: OBSERVABILITY_APP_ID }, + { user: casesV3NoAssigneeUser, owner: CASES_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can add comments`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); @@ -301,6 +306,7 @@ export default ({ getService }: FtrProviderContext): void => { { user: secReadUser, owner: SECURITY_SOLUTION_APP_ID }, { user: casesOnlyDeleteUser, owner: CASES_APP_ID }, { user: obsCasesOnlyDeleteUser, owner: OBSERVABILITY_APP_ID }, + { user: casesV3NoAssigneeUser, owner: CASES_APP_ID }, ]) { it(`User ${ user.username From 52b59b8d8b522d54446b086776a4d7a840748051 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Thu, 23 Jan 2025 01:57:46 +0000 Subject: [PATCH 21/32] Update test --- .../shared/exploratory_view/header/add_to_case_action.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/observability/plugins/exploratory_view/public/components/shared/exploratory_view/header/add_to_case_action.test.tsx b/x-pack/solutions/observability/plugins/exploratory_view/public/components/shared/exploratory_view/header/add_to_case_action.test.tsx index 8d3994e8e29b3..ad7abb87d8d08 100644 --- a/x-pack/solutions/observability/plugins/exploratory_view/public/components/shared/exploratory_view/header/add_to_case_action.test.tsx +++ b/x-pack/solutions/observability/plugins/exploratory_view/public/components/shared/exploratory_view/header/add_to_case_action.test.tsx @@ -122,7 +122,7 @@ describe('AddToCaseAction', function () { settings: false, createComment: false, reopenCase: false, - assignCase: false, + assign: false, }, }) ); From faee86abc74870048cd49fa6bdb45b358153ed9a Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Fri, 24 Jan 2025 03:16:17 +0000 Subject: [PATCH 22/32] Fix tests that were too permissive --- .../apis/cases/common/roles.ts | 20 +-- .../api_integration/apis/cases/privileges.ts | 118 +++++++++++++++--- .../common/lib/api/case.ts | 30 +++-- 3 files changed, 130 insertions(+), 38 deletions(-) diff --git a/x-pack/test/api_integration/apis/cases/common/roles.ts b/x-pack/test/api_integration/apis/cases/common/roles.ts index 8066318cccf04..48e80049cf66b 100644 --- a/x-pack/test/api_integration/apis/cases/common/roles.ts +++ b/x-pack/test/api_integration/apis/cases/common/roles.ts @@ -176,7 +176,7 @@ export const secCasesV2NoReopenWithCreateComment: Role = { { feature: { siem: ['all'], - securitySolutionCasesV2: ['read', 'update', 'create', 'delete', 'create_comment'], + securitySolutionCasesV2: ['read', 'update', 'create', 'cases_delete', 'create_comment'], actions: ['all'], actionsSimulators: ['all'], }, @@ -550,13 +550,7 @@ export const casesV3NoAssignee: Role = { spaces: ['*'], base: [], feature: { - generalCasesV3: [ - 'minimal_all', - 'create_comment', - 'case_reopen', - 'cases_delete', - 'cases_settings', - ], + generalCasesV3: ['minimal_read', 'delete', 'case_reopen'], actions: ['all'], actionsSimulators: ['all'], }, @@ -830,7 +824,13 @@ export const obsCasesV2NoReopenWithCreateComment: Role = { spaces: ['*'], base: [], feature: { - observabilityCasesV2: ['read', 'update', 'create', 'delete', 'create_comment'], + observabilityCasesV2: [ + 'read', + 'cases_update', + 'create', + 'cases_delete', + 'create_comment', + ], actions: ['all'], actionsSimulators: ['all'], }, @@ -855,7 +855,7 @@ export const obsCasesV2NoCreateCommentWithReopen: Role = { spaces: ['*'], base: [], feature: { - observabilityCasesV2: ['read', 'update', 'create', 'delete', 'case_reopen'], + observabilityCasesV2: ['read', 'update', 'create', 'cases_delete', 'case_reopen'], actions: ['all'], actionsSimulators: ['all'], }, diff --git a/x-pack/test/api_integration/apis/cases/privileges.ts b/x-pack/test/api_integration/apis/cases/privileges.ts index ecc222902f3de..2d4ec1e6d086e 100644 --- a/x-pack/test/api_integration/apis/cases/privileges.ts +++ b/x-pack/test/api_integration/apis/cases/privileges.ts @@ -54,6 +54,7 @@ import { secCasesV2NoCreateCommentWithReopenUser, } from './common/users'; import { getPostCaseRequest } from '../../../cases_api_integration/common/lib/mock'; +import { suggestUserProfiles } from '../../../cases_api_integration/common/lib/api/user_profiles'; export default ({ getService }: FtrProviderContext): void => { describe('feature privilege', () => { @@ -81,7 +82,7 @@ export default ({ getService }: FtrProviderContext): void => { { user: casesV3NoAssigneeUser, owner: CASES_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can create a case`, async () => { - await createCase(supertestWithoutAuth, getPostCaseRequest({ owner }), 200, { + await createCase(supertest, getPostCaseRequest({ owner }), 200, { user, space: null, }); @@ -168,7 +169,6 @@ export default ({ getService }: FtrProviderContext): void => { { user: secCasesV3AllUser, owner: SECURITY_SOLUTION_APP_ID }, { user: casesV3AllUser, owner: CASES_APP_ID }, { user: obsCasesV3AllUser, owner: OBSERVABILITY_APP_ID }, - { user: casesV3NoAssigneeUser, owner: CASES_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can delete a case`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); @@ -207,13 +207,9 @@ export default ({ getService }: FtrProviderContext): void => { { user: obsCasesV2AllUser, owner: OBSERVABILITY_APP_ID }, { user: casesAllUser, owner: CASES_APP_ID }, { user: casesV2AllUser, owner: CASES_APP_ID }, - { user: casesV2NoCreateCommentWithReopenUser, owner: CASES_APP_ID }, - { user: obsCasesV2NoCreateCommentWithReopenUser, owner: OBSERVABILITY_APP_ID }, - { user: secCasesV2NoCreateCommentWithReopenUser, owner: SECURITY_SOLUTION_APP_ID }, { user: secCasesV3AllUser, owner: SECURITY_SOLUTION_APP_ID }, { user: casesV3AllUser, owner: CASES_APP_ID }, { user: obsCasesV3AllUser, owner: OBSERVABILITY_APP_ID }, - { user: casesV3NoAssigneeUser, owner: CASES_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can reopen a case`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); @@ -221,7 +217,14 @@ export default ({ getService }: FtrProviderContext): void => { supertest: supertestWithoutAuth, caseId: caseInfo.id, status: 'closed' as CaseStatuses, - version: '2', + version: caseInfo.version, + expectedHttpCode: 200, + auth: { user, space: null }, + }); + + const updatedCase = await getCase({ + supertest: supertestWithoutAuth, + caseId: caseInfo.id, expectedHttpCode: 200, auth: { user, space: null }, }); @@ -230,17 +233,78 @@ export default ({ getService }: FtrProviderContext): void => { supertest: supertestWithoutAuth, caseId: caseInfo.id, status: 'open' as CaseStatuses, - version: '3', + version: updatedCase.version, expectedHttpCode: 200, auth: { user, space: null }, }); }); } - for (const { user, owner } of [ - { user: casesV2NoReopenWithCreateCommentUser, owner: CASES_APP_ID }, - { user: obsCasesV2NoReopenWithCreateCommentUser, owner: OBSERVABILITY_APP_ID }, - { user: secCasesV2NoReopenWithCreateCommentUser, owner: SECURITY_SOLUTION_APP_ID }, + for (const { user, owner, userWithFullPerms } of [ + { + user: casesV2NoCreateCommentWithReopenUser, + owner: CASES_APP_ID, + userWithFullPerms: casesV3AllUser, + }, + { + user: obsCasesV2NoCreateCommentWithReopenUser, + owner: OBSERVABILITY_APP_ID, + userWithFullPerms: obsCasesV3AllUser, + }, + { + user: secCasesV2NoCreateCommentWithReopenUser, + owner: SECURITY_SOLUTION_APP_ID, + userWithFullPerms: secCasesV3AllUser, + }, + { user: casesV3NoAssigneeUser, owner: CASES_APP_ID, userWithFullPerms: casesV3AllUser }, + ]) { + it(`User ${ + user.username + } with role(s) ${user.roles.join()} can reopen a case, if it's closed, but otherwise can't change status`, async () => { + const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); + await updateCaseStatus({ + supertest: supertestWithoutAuth, + caseId: caseInfo.id, + status: 'closed' as CaseStatuses, + version: caseInfo.version, + expectedHttpCode: 200, + auth: { user: userWithFullPerms, space: null }, + }); + + const updatedCase = await getCase({ + supertest: supertestWithoutAuth, + caseId: caseInfo.id, + expectedHttpCode: 200, + auth: { user, space: null }, + }); + + await updateCaseStatus({ + supertest: supertestWithoutAuth, + caseId: caseInfo.id, + status: 'open' as CaseStatuses, + version: updatedCase.version, + expectedHttpCode: 200, + auth: { user, space: null }, + }); + }); + } + + for (const { user, owner, userWithFullPerms } of [ + { + user: casesV2NoReopenWithCreateCommentUser, + owner: CASES_APP_ID, + userWithFullPerms: casesV3AllUser, + }, + { + user: obsCasesV2NoReopenWithCreateCommentUser, + owner: OBSERVABILITY_APP_ID, + userWithFullPerms: obsCasesV3AllUser, + }, + { + user: secCasesV2NoReopenWithCreateCommentUser, + owner: SECURITY_SOLUTION_APP_ID, + userWithFullPerms: secCasesV3AllUser, + }, ]) { it(`User ${ user.username @@ -250,7 +314,14 @@ export default ({ getService }: FtrProviderContext): void => { supertest: supertestWithoutAuth, caseId: caseInfo.id, status: 'closed' as CaseStatuses, - version: '2', + version: caseInfo.version, + expectedHttpCode: 200, + auth: { user: userWithFullPerms, space: null }, + }); + + const updatedCase = await getCase({ + supertest: supertestWithoutAuth, + caseId: caseInfo.id, expectedHttpCode: 200, auth: { user, space: null }, }); @@ -259,7 +330,7 @@ export default ({ getService }: FtrProviderContext): void => { supertest: supertestWithoutAuth, caseId: caseInfo.id, status: 'open' as CaseStatuses, - version: '3', + version: updatedCase.version, expectedHttpCode: 403, auth: { user, space: null }, }); @@ -279,7 +350,6 @@ export default ({ getService }: FtrProviderContext): void => { { user: secCasesV3AllUser, owner: SECURITY_SOLUTION_APP_ID }, { user: casesV3AllUser, owner: CASES_APP_ID }, { user: obsCasesV3AllUser, owner: OBSERVABILITY_APP_ID }, - { user: casesV3NoAssigneeUser, owner: CASES_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can add comments`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); @@ -306,7 +376,6 @@ export default ({ getService }: FtrProviderContext): void => { { user: secReadUser, owner: SECURITY_SOLUTION_APP_ID }, { user: casesOnlyDeleteUser, owner: CASES_APP_ID }, { user: obsCasesOnlyDeleteUser, owner: OBSERVABILITY_APP_ID }, - { user: casesV3NoAssigneeUser, owner: CASES_APP_ID }, ]) { it(`User ${ user.username @@ -318,6 +387,7 @@ export default ({ getService }: FtrProviderContext): void => { assigneeId: caseInfo.created_by.profile_uid ?? '', expectedHttpCode: 403, auth: { user, space: null }, + version: caseInfo.version, }); }); } @@ -338,10 +408,22 @@ export default ({ getService }: FtrProviderContext): void => { user.username } with role(s) ${user.roles.join()} CAN change assignee`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); - await updateCaseAssignee({ + const [{ uid: assigneeId }] = await suggestUserProfiles({ + supertest: supertestWithoutAuth, + req: { name: user.username, owners: [owner], size: 1 }, + auth: { user, space: null }, + }); + const wtf = await updateCaseAssignee({ + supertest: supertestWithoutAuth, + caseId: caseInfo.id, + assigneeId, + expectedHttpCode: 200, + version: caseInfo.version, + auth: { user, space: null }, + }); + const userId2 = await getCase({ supertest: supertestWithoutAuth, caseId: caseInfo.id, - assigneeId: caseInfo.created_by.profile_uid ?? '', expectedHttpCode: 200, auth: { user, space: null }, }); diff --git a/x-pack/test/cases_api_integration/common/lib/api/case.ts b/x-pack/test/cases_api_integration/common/lib/api/case.ts index 12a5190300fc0..baf37459776f0 100644 --- a/x-pack/test/cases_api_integration/common/lib/api/case.ts +++ b/x-pack/test/cases_api_integration/common/lib/api/case.ts @@ -112,16 +112,21 @@ export const updateCaseStatus = async ({ auth?: { user: User; space: string | null }; }) => { const updateRequest: CasePatchRequest = { - status, - version, - id: caseId, + cases: [ + { + status, + version, + id: caseId, + }, + ], }; const { body: updatedCase } = await supertest - .patch(`/api/cases/${caseId}`) + .patch(`${getSpaceUrlPrefix(auth?.space)}${CASES_URL}`) .auth(auth.user.username, auth.user.password) .set('kbn-xsrf', 'xxx') - .send(updateRequest); + .send(updateRequest) + .expect(expectedHttpCode); return updatedCase; }; @@ -141,15 +146,20 @@ export const updateCaseAssignee = async ({ auth?: { user: User; space: string | null }; }) => { const updateRequest: CasePatchRequest = { - version, - assignees: [{ uid: assigneeId }], - id: caseId, + cases: [ + { + version, + assignees: [{ uid: assigneeId }], + id: caseId, + }, + ], }; const { body: updatedCase } = await supertest - .patch(`/api/cases/${caseId}`) + .patch(`${getSpaceUrlPrefix(auth?.space)}${CASES_URL}`) .auth(auth.user.username, auth.user.password) .set('kbn-xsrf', 'xxx') - .send(updateRequest); + .send(updateRequest) + .expect(expectedHttpCode); return updatedCase; }; From d87152818fb98b0a32125a8dff20c68d5804306a Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Fri, 24 Jan 2025 15:31:26 +0000 Subject: [PATCH 23/32] Fix ui capability not being linked to cases api operation --- .../shared/cases/common/utils/capabilities.ts | 4 +- .../shared/cases/server/features/v3.ts | 3 +- .../observability/server/features/cases_v3.ts | 3 +- .../src/cases/v3_features/kibana_features.ts | 1 - .../cases/v3_features/kibana_sub_features.ts | 2 +- .../apis/cases/common/roles.ts | 2 +- .../api_integration/apis/cases/privileges.ts | 47 ++++++++++++------- 7 files changed, 37 insertions(+), 25 deletions(-) diff --git a/x-pack/platform/plugins/shared/cases/common/utils/capabilities.ts b/x-pack/platform/plugins/shared/cases/common/utils/capabilities.ts index 99cbc1ae71f98..7ef3300d5fd7b 100644 --- a/x-pack/platform/plugins/shared/cases/common/utils/capabilities.ts +++ b/x-pack/platform/plugins/shared/cases/common/utils/capabilities.ts @@ -25,7 +25,7 @@ export interface CasesUiCapabilities { settings: readonly string[]; reopenCase: readonly string[]; createComment: readonly string[]; - assign: readonly string[]; + assignCase: readonly string[]; } /** * Return the UI capabilities for each type of operation. These strings must match the values defined in the UI @@ -44,5 +44,5 @@ export const createUICapabilities = (): CasesUiCapabilities => ({ settings: [CASES_SETTINGS_CAPABILITY] as const, reopenCase: [CASES_REOPEN_CAPABILITY] as const, createComment: [CREATE_COMMENT_CAPABILITY] as const, - assign: [ASSIGN_CASE_CAPABILITY] as const, + assignCase: [ASSIGN_CASE_CAPABILITY] as const, }); diff --git a/x-pack/platform/plugins/shared/cases/server/features/v3.ts b/x-pack/platform/plugins/shared/cases/server/features/v3.ts index 09e93b6cc7be7..b90a4712b7539 100644 --- a/x-pack/platform/plugins/shared/cases/server/features/v3.ts +++ b/x-pack/platform/plugins/shared/cases/server/features/v3.ts @@ -57,7 +57,6 @@ export const getV3 = (): KibanaFeatureConfig => { push: [APP_ID], createComment: [APP_ID], reopenCase: [APP_ID], - assign: [APP_ID], }, management: { insightsAndAlerting: [APP_ID], @@ -215,7 +214,7 @@ export const getV3 = (): KibanaFeatureConfig => { cases: { assign: [APP_ID], }, - ui: capabilities.assign, + ui: capabilities.assignCase, }, ], }, diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts index ceb17151b2d39..a6af796653ed8 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts @@ -38,7 +38,6 @@ export const getCasesFeatureV3 = ( push: [observabilityFeatureId], createComment: [observabilityFeatureId], reopenCase: [observabilityFeatureId], - assign: [observabilityFeatureId], }, savedObject: { all: [...filesSavedObjectTypes], @@ -201,7 +200,7 @@ export const getCasesFeatureV3 = ( cases: { assign: [observabilityFeatureId], }, - ui: casesCapabilities.assign, + ui: casesCapabilities.assignCase, }, ], }, diff --git a/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_features.ts index fbe25ef75da11..c53b236a61b78 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_features.ts @@ -44,7 +44,6 @@ export const getCasesBaseKibanaFeatureV3 = ({ push: [APP_ID], createComment: [APP_ID], reopenCase: [APP_ID], - assign: [APP_ID], }, savedObject: { all: [...savedObjects.files], diff --git a/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts b/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts index 70060450cb64e..b1672d25d0c3b 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_sub_features.ts @@ -187,7 +187,7 @@ export const getCasesSubFeaturesMapV3 = ({ cases: { assign: [APP_ID], }, - ui: uiCapabilities.assign, + ui: uiCapabilities.assignCase, }, ], }, diff --git a/x-pack/test/api_integration/apis/cases/common/roles.ts b/x-pack/test/api_integration/apis/cases/common/roles.ts index 48e80049cf66b..461a2e296fcd3 100644 --- a/x-pack/test/api_integration/apis/cases/common/roles.ts +++ b/x-pack/test/api_integration/apis/cases/common/roles.ts @@ -575,7 +575,7 @@ export const casesV3ReadAndAssignee: Role = { spaces: ['*'], base: [], feature: { - generalCasesV3: ['read', 'cases_assign'], + generalCasesV3: ['minimal_all', 'cases_assign'], actions: ['all'], actionsSimulators: ['all'], }, diff --git a/x-pack/test/api_integration/apis/cases/privileges.ts b/x-pack/test/api_integration/apis/cases/privileges.ts index 2d4ec1e6d086e..f1bb7c2781933 100644 --- a/x-pack/test/api_integration/apis/cases/privileges.ts +++ b/x-pack/test/api_integration/apis/cases/privileges.ts @@ -368,23 +368,44 @@ export default ({ getService }: FtrProviderContext): void => { }); } - for (const { user, owner } of [ - { user: casesV3NoAssigneeUser, owner: CASES_APP_ID }, - { user: casesV2NoCreateCommentWithReopenUser, owner: CASES_APP_ID }, - { user: obsCasesV2NoCreateCommentWithReopenUser, owner: OBSERVABILITY_APP_ID }, - { user: secCasesV2NoCreateCommentWithReopenUser, owner: SECURITY_SOLUTION_APP_ID }, - { user: secReadUser, owner: SECURITY_SOLUTION_APP_ID }, - { user: casesOnlyDeleteUser, owner: CASES_APP_ID }, - { user: obsCasesOnlyDeleteUser, owner: OBSERVABILITY_APP_ID }, + for (const { user, owner, userWithFullPerms } of [ + { user: casesV3NoAssigneeUser, owner: CASES_APP_ID, userWithFullPerms: casesV3AllUser }, + { + user: casesV2NoCreateCommentWithReopenUser, + owner: CASES_APP_ID, + userWithFullPerms: casesV3AllUser, + }, + { + user: obsCasesV2NoCreateCommentWithReopenUser, + owner: OBSERVABILITY_APP_ID, + userWithFullPerms: obsCasesV3AllUser, + }, + { + user: secCasesV2NoCreateCommentWithReopenUser, + owner: SECURITY_SOLUTION_APP_ID, + userWithFullPerms: secCasesV3AllUser, + }, + { user: secReadUser, owner: SECURITY_SOLUTION_APP_ID, userWithFullPerms: secAllUser }, + { user: casesOnlyDeleteUser, owner: CASES_APP_ID, userWithFullPerms: casesAllUser }, + { + user: obsCasesOnlyDeleteUser, + owner: OBSERVABILITY_APP_ID, + userWithFullPerms: obsCasesAllUser, + }, ]) { it(`User ${ user.username } with role(s) ${user.roles.join()} CANNOT change assignee`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); + const [{ uid: assigneeId }] = await suggestUserProfiles({ + supertest: supertestWithoutAuth, + req: { name: userWithFullPerms.username, owners: [owner], size: 1 }, + auth: { user: userWithFullPerms, space: null }, + }); await updateCaseAssignee({ supertest: supertestWithoutAuth, caseId: caseInfo.id, - assigneeId: caseInfo.created_by.profile_uid ?? '', + assigneeId, expectedHttpCode: 403, auth: { user, space: null }, version: caseInfo.version, @@ -413,7 +434,7 @@ export default ({ getService }: FtrProviderContext): void => { req: { name: user.username, owners: [owner], size: 1 }, auth: { user, space: null }, }); - const wtf = await updateCaseAssignee({ + await updateCaseAssignee({ supertest: supertestWithoutAuth, caseId: caseInfo.id, assigneeId, @@ -421,12 +442,6 @@ export default ({ getService }: FtrProviderContext): void => { version: caseInfo.version, auth: { user, space: null }, }); - const userId2 = await getCase({ - supertest: supertestWithoutAuth, - caseId: caseInfo.id, - expectedHttpCode: 200, - auth: { user, space: null }, - }); }); } From 4de0bcc89c08ee8ca543555c9bf50b438592435a Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Fri, 24 Jan 2025 18:40:07 +0000 Subject: [PATCH 24/32] Add assignee checks to create apis + unit tests --- .../server/client/cases/bulk_create.test.ts | 59 +++++++++++++++++++ .../cases/server/client/cases/bulk_create.ts | 18 ++++-- .../cases/server/client/cases/create.test.ts | 45 ++++++++++++++ .../cases/server/client/cases/create.ts | 16 +++-- 4 files changed, 129 insertions(+), 9 deletions(-) diff --git a/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_create.test.ts b/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_create.test.ts index ace3c39d31e0a..fb5e2c7ca54e5 100644 --- a/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_create.test.ts +++ b/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_create.test.ts @@ -416,6 +416,65 @@ describe('bulkCreate', () => { { id: 'mock-saved-object-id', owner: 'securitySolution' }, { id: 'mock-saved-object-id', owner: 'cases' }, ], + operation: [ + { + action: 'cases_assign', + docType: 'case', + ecsType: 'change', + name: 'assignCase', + savedObjectType: 'cases', + verbs: { past: 'updated', present: 'update', progressive: 'updating' }, + }, + { + action: 'case_create', + docType: 'case', + ecsType: 'creation', + name: 'createCase', + savedObjectType: 'cases', + verbs: { past: 'created', present: 'create', progressive: 'creating' }, + }, + ], + }); + }); + + it('validates with assign+create operations when cases have assignees', async () => { + await bulkCreate( + { cases: [getCases()[0], getCases({ owner: 'cases' })[0]] }, + clientArgs, + casesClientMock + ); + + expect(clientArgs.authorization.ensureAuthorized).toHaveBeenCalledWith({ + entities: [ + { id: 'mock-saved-object-id', owner: 'securitySolution' }, + { id: 'mock-saved-object-id', owner: 'cases' }, + ], + operation: [ + { + action: 'cases_assign', + docType: 'case', + ecsType: 'change', + name: 'assignCase', + savedObjectType: 'cases', + verbs: { past: 'updated', present: 'update', progressive: 'updating' }, + }, + { + action: 'case_create', + docType: 'case', + ecsType: 'creation', + name: 'createCase', + savedObjectType: 'cases', + verbs: { past: 'created', present: 'create', progressive: 'creating' }, + }, + ], + }); + }); + + it('validates with only create operation when cases have no assignees', async () => { + await bulkCreate({ cases: [getCases({ assignees: [] })[0]] }, clientArgs, casesClientMock); + + expect(clientArgs.authorization.ensureAuthorized).toHaveBeenCalledWith({ + entities: [{ id: 'mock-saved-object-id', owner: 'securitySolution' }], operation: { action: 'case_create', docType: 'case', diff --git a/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_create.ts b/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_create.ts index b94e16481a35a..12f351347d4d5 100644 --- a/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_create.ts +++ b/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_create.ts @@ -54,10 +54,20 @@ export const bulkCreate = async ( const casesWithIds = getCaseWithIds(decodedData); - await auth.ensureAuthorized({ - operation: Operations.createCase, - entities: casesWithIds.map((theCase) => ({ owner: theCase.owner, id: theCase.id })), - }); + if ( + casesWithIds.filter((theCase) => theCase.assignees && theCase.assignees.length !== 0).length > + 0 + ) { + await auth.ensureAuthorized({ + operation: [Operations.assignCase, Operations.createCase], + entities: casesWithIds.map((theCase) => ({ owner: theCase.owner, id: theCase.id })), + }); + } else { + await auth.ensureAuthorized({ + operation: Operations.createCase, + entities: casesWithIds.map((theCase) => ({ owner: theCase.owner, id: theCase.id })), + }); + } const hasPlatinumLicenseOrGreater = await licensingService.isAtLeastPlatinum(); diff --git a/x-pack/platform/plugins/shared/cases/server/client/cases/create.test.ts b/x-pack/platform/plugins/shared/cases/server/client/cases/create.test.ts index 5a3eb7bd4f54f..9bc5455a0633d 100644 --- a/x-pack/platform/plugins/shared/cases/server/client/cases/create.test.ts +++ b/x-pack/platform/plugins/shared/cases/server/client/cases/create.test.ts @@ -116,6 +116,51 @@ describe('create', () => { `Failed to create case: Error: In order to assign users to cases, you must be subscribed to an Elastic Platinum license` ); }); + + it('validates with assign+create operations when cases have assignees', async () => { + clientArgs.services.licensingService.isAtLeastPlatinum.mockResolvedValue(true); + await create(theCase, clientArgs, casesClientMock); + + expect(clientArgs.authorization.ensureAuthorized).toHaveBeenCalledWith( + expect.objectContaining({ + operation: [ + { + action: 'cases_assign', + docType: 'case', + ecsType: 'change', + name: 'assignCase', + savedObjectType: 'cases', + verbs: { past: 'updated', present: 'update', progressive: 'updating' }, + }, + { + action: 'case_create', + docType: 'case', + ecsType: 'creation', + name: 'createCase', + savedObjectType: 'cases', + verbs: { past: 'created', present: 'create', progressive: 'creating' }, + }, + ], + }) + ); + }); + + it('validates with only create operation when cases have no assignees', async () => { + await create({ ...theCase, assignees: [] }, clientArgs, casesClientMock); + + expect(clientArgs.authorization.ensureAuthorized).toHaveBeenCalledWith( + expect.objectContaining({ + operation: { + action: 'case_create', + docType: 'case', + ecsType: 'creation', + name: 'createCase', + savedObjectType: 'cases', + verbs: { past: 'created', present: 'create', progressive: 'creating' }, + }, + }) + ); + }); }); describe('Attributes', () => { diff --git a/x-pack/platform/plugins/shared/cases/server/client/cases/create.ts b/x-pack/platform/plugins/shared/cases/server/client/cases/create.ts index b1d444ec2a3c5..88dafdb4e9dc3 100644 --- a/x-pack/platform/plugins/shared/cases/server/client/cases/create.ts +++ b/x-pack/platform/plugins/shared/cases/server/client/cases/create.ts @@ -52,11 +52,17 @@ export const create = async ( validateCustomFields(customFieldsValidationParams); const savedObjectID = SavedObjectsUtils.generateId(); - - await auth.ensureAuthorized({ - operation: Operations.createCase, - entities: [{ owner: query.owner, id: savedObjectID }], - }); + if (query.assignees && query.assignees.length > 0) { + await auth.ensureAuthorized({ + operation: [Operations.assignCase, Operations.createCase], + entities: [{ owner: query.owner, id: savedObjectID }], + }); + } else { + await auth.ensureAuthorized({ + operation: Operations.createCase, + entities: [{ owner: query.owner, id: savedObjectID }], + }); + } /** * Assign users to a case is only available to Platinum+ From 095bd0f96f5953d6f228acebca9c2e35d905d90a Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Fri, 24 Jan 2025 21:27:27 +0000 Subject: [PATCH 25/32] Use correct type for update cases api --- x-pack/test/cases_api_integration/common/lib/api/case.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/test/cases_api_integration/common/lib/api/case.ts b/x-pack/test/cases_api_integration/common/lib/api/case.ts index baf37459776f0..c05fa07420f93 100644 --- a/x-pack/test/cases_api_integration/common/lib/api/case.ts +++ b/x-pack/test/cases_api_integration/common/lib/api/case.ts @@ -10,7 +10,7 @@ import { Case, CaseStatuses } from '@kbn/cases-plugin/common/types/domain'; import { CasePostRequest, CasesFindResponse, - CasePatchRequest, + CasesPatchRequest, } from '@kbn/cases-plugin/common/types/api'; import type SuperTest from 'supertest'; import { ToolingLog } from '@kbn/tooling-log'; @@ -111,7 +111,7 @@ export const updateCaseStatus = async ({ expectedHttpCode?: number; auth?: { user: User; space: string | null }; }) => { - const updateRequest: CasePatchRequest = { + const updateRequest: CasesPatchRequest = { cases: [ { status, @@ -145,7 +145,7 @@ export const updateCaseAssignee = async ({ expectedHttpCode?: number; auth?: { user: User; space: string | null }; }) => { - const updateRequest: CasePatchRequest = { + const updateRequest: CasesPatchRequest = { cases: [ { version, From 28f03c20998a525d6a309980fb542354333e9c54 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Fri, 24 Jan 2025 20:03:33 -0500 Subject: [PATCH 26/32] Update snapshot --- .../plugins/shared/cases/common/utils/capabilities.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/platform/plugins/shared/cases/common/utils/capabilities.test.tsx b/x-pack/platform/plugins/shared/cases/common/utils/capabilities.test.tsx index daf3ee66b65fe..edfb5a14da532 100644 --- a/x-pack/platform/plugins/shared/cases/common/utils/capabilities.test.tsx +++ b/x-pack/platform/plugins/shared/cases/common/utils/capabilities.test.tsx @@ -18,7 +18,7 @@ describe('createUICapabilities', () => { "push_cases", "cases_connectors", ], - "assign": Array [ + "assignCase": Array [ "cases_assign", ], "createComment": Array [ From 0f6a236f7c7628f5c61f031963516f3461a5ad18 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Fri, 24 Jan 2025 20:16:30 -0500 Subject: [PATCH 27/32] Update new test --- x-pack/test/spaces_api_integration/common/suites/get.agnostic.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/spaces_api_integration/common/suites/get.agnostic.ts b/x-pack/test/spaces_api_integration/common/suites/get.agnostic.ts index 0993452aebd94..a77486d4b27cf 100644 --- a/x-pack/test/spaces_api_integration/common/suites/get.agnostic.ts +++ b/x-pack/test/spaces_api_integration/common/suites/get.agnostic.ts @@ -94,6 +94,7 @@ export function getTestSuiteFactory(context: DeploymentAgnosticFtrProviderContex 'logs', 'observabilityCases', 'observabilityCasesV2', + 'observabilityCasesV3', 'securitySolutionAssistant', 'securitySolutionAttackDiscovery', 'securitySolutionCases', From 49b76670c10d0cfaaa46cffa1327445e7c2f9175 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 28 Jan 2025 23:24:19 +0000 Subject: [PATCH 28/32] PR feedback --- .../cases/public/components/actions/use_items_action.tsx | 5 +++-- .../case_view/components/assign_users.test.tsx | 4 ++-- .../shared/cases/server/client/cases/bulk_update.ts | 9 ++------- x-pack/test/api_integration/apis/cases/common/roles.ts | 8 ++++---- x-pack/test/api_integration/apis/cases/privileges.ts | 8 +++++++- x-pack/test/cases_api_integration/common/lib/api/case.ts | 2 +- 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/x-pack/platform/plugins/shared/cases/public/components/actions/use_items_action.tsx b/x-pack/platform/plugins/shared/cases/public/components/actions/use_items_action.tsx index a029cb12ba00c..c5264b699fbb2 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/actions/use_items_action.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/actions/use_items_action.tsx @@ -33,8 +33,9 @@ export const useItemsAction = ({ const { permissions } = useCasesContext(); const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); const [selectedCasesToEdit, setSelectedCasesToEdit] = useState([]); - const canUpdateStatus = permissions.update || permissions.assign; - const isActionDisabled = isDisabled || !canUpdateStatus; + const canUpdateStatus = permissions.update; + const canUpdateAssignee = permissions.assign; + const isActionDisabled = isDisabled || (!canUpdateStatus && !canUpdateAssignee); const onFlyoutClosed = useCallback(() => setIsFlyoutOpen(false), []); const openFlyout = useCallback( diff --git a/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.test.tsx b/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.test.tsx index b9435d94114f0..22910c5451490 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.test.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/case_view/components/assign_users.test.tsx @@ -49,14 +49,14 @@ describe('AssignUsers', () => { expect(screen.getByText('No users are assigned')).toBeInTheDocument(); }); - it('does not show the suggest users edit button when the user does not have update permissions', () => { + it('does not show the suggest users edit button when the user does not have assign permissions', () => { appMockRender = createAppMockRenderer({ permissions: noAssignCasesPermissions() }); appMockRender.render(); expect(screen.queryByText('case-view-assignees-edit')).not.toBeInTheDocument(); }); - it('does not show the assign users link when the user does not have update permissions', () => { + it('does not show the assign users link when the user does not have assign permissions', () => { appMockRender = createAppMockRenderer({ permissions: noAssignCasesPermissions() }); appMockRender.render(); diff --git a/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_update.ts b/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_update.ts index a31a0c7cbf333..d714d7f22e83c 100644 --- a/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_update.ts +++ b/x-pack/platform/plugins/shared/cases/server/client/cases/bulk_update.ts @@ -14,6 +14,7 @@ import type { SavedObjectsFindResult, SavedObjectsUpdateResponse, } from '@kbn/core/server'; +import { isEqual } from 'lodash'; import { nodeBuilder } from '@kbn/es-query'; @@ -193,12 +194,6 @@ async function getAlertComments({ }); } -function haveSameElements(arr1?: string[], arr2?: string[]): boolean { - if (!arr1 || !arr2 || arr1.length !== arr2.length) return false; - const set1 = new Set(arr1); - return arr2.every((item) => set1.has(item)); -} - /** * Returns what status the alert comment should have based on whether it is associated to a case. */ @@ -309,7 +304,7 @@ function partitionPatchRequest( } if (reqCase.assignees) { if ( - !haveSameElements( + !isEqual( reqCase.assignees.map(({ uid }) => uid), foundCase?.attributes.assignees.map(({ uid }) => uid) ) && diff --git a/x-pack/test/api_integration/apis/cases/common/roles.ts b/x-pack/test/api_integration/apis/cases/common/roles.ts index 461a2e296fcd3..f27ce68f1ddf2 100644 --- a/x-pack/test/api_integration/apis/cases/common/roles.ts +++ b/x-pack/test/api_integration/apis/cases/common/roles.ts @@ -550,7 +550,7 @@ export const casesV3NoAssignee: Role = { spaces: ['*'], base: [], feature: { - generalCasesV3: ['minimal_read', 'delete', 'case_reopen'], + generalCasesV3: ['minimal_read', 'cases_delete', 'case_reopen', 'create_comment'], actions: ['all'], actionsSimulators: ['all'], }, @@ -575,7 +575,7 @@ export const casesV3ReadAndAssignee: Role = { spaces: ['*'], base: [], feature: { - generalCasesV3: ['minimal_all', 'cases_assign'], + generalCasesV3: ['minimal_read', 'cases_assign'], actions: ['all'], actionsSimulators: ['all'], }, @@ -600,7 +600,7 @@ export const casesV2NoReopenWithCreateComment: Role = { spaces: ['*'], base: [], feature: { - generalCasesV2: ['read', 'update', 'create', 'delete', 'create_comment'], + generalCasesV2: ['read', 'update', 'create', 'cases_delete', 'create_comment'], actions: ['all'], actionsSimulators: ['all'], }, @@ -625,7 +625,7 @@ export const casesV2NoCreateCommentWithReopen: Role = { spaces: ['*'], base: [], feature: { - generalCasesV2: ['read', 'update', 'create', 'delete', 'case_reopen'], + generalCasesV2: ['read', 'update', 'create', 'cases_delete', 'case_reopen'], actions: ['all'], actionsSimulators: ['all'], }, diff --git a/x-pack/test/api_integration/apis/cases/privileges.ts b/x-pack/test/api_integration/apis/cases/privileges.ts index f1bb7c2781933..66939f7603bec 100644 --- a/x-pack/test/api_integration/apis/cases/privileges.ts +++ b/x-pack/test/api_integration/apis/cases/privileges.ts @@ -126,6 +126,7 @@ export default ({ getService }: FtrProviderContext): void => { { user: secReadCasesNoneUser, owner: SECURITY_SOLUTION_APP_ID }, { user: casesOnlyDeleteUser, owner: CASES_APP_ID }, { user: obsCasesOnlyDeleteUser, owner: OBSERVABILITY_APP_ID }, + { user: casesV3NoAssigneeUser, owner: CASES_APP_ID }, ]) { it(`User ${ user.username @@ -169,6 +170,7 @@ export default ({ getService }: FtrProviderContext): void => { { user: secCasesV3AllUser, owner: SECURITY_SOLUTION_APP_ID }, { user: casesV3AllUser, owner: CASES_APP_ID }, { user: obsCasesV3AllUser, owner: OBSERVABILITY_APP_ID }, + { user: casesV3NoAssigneeUser, owner: CASES_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can delete a case`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); @@ -260,7 +262,7 @@ export default ({ getService }: FtrProviderContext): void => { ]) { it(`User ${ user.username - } with role(s) ${user.roles.join()} can reopen a case, if it's closed, but otherwise can't change status`, async () => { + } with role(s) ${user.roles.join()} can reopen a case, if it's closed`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); await updateCaseStatus({ supertest: supertestWithoutAuth, @@ -350,6 +352,7 @@ export default ({ getService }: FtrProviderContext): void => { { user: secCasesV3AllUser, owner: SECURITY_SOLUTION_APP_ID }, { user: casesV3AllUser, owner: CASES_APP_ID }, { user: obsCasesV3AllUser, owner: OBSERVABILITY_APP_ID }, + { user: casesV3NoAssigneeUser, owner: CASES_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can add comments`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); @@ -424,6 +427,9 @@ export default ({ getService }: FtrProviderContext): void => { { user: secCasesV2AllUser, owner: SECURITY_SOLUTION_APP_ID }, { user: obsCasesAllUser, owner: OBSERVABILITY_APP_ID }, { user: obsCasesV2AllUser, owner: OBSERVABILITY_APP_ID }, + { user: secCasesV3AllUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: casesV3AllUser, owner: CASES_APP_ID }, + { user: obsCasesV3AllUser, owner: OBSERVABILITY_APP_ID }, ]) { it(`User ${ user.username diff --git a/x-pack/test/cases_api_integration/common/lib/api/case.ts b/x-pack/test/cases_api_integration/common/lib/api/case.ts index c05fa07420f93..be9576fe55f87 100644 --- a/x-pack/test/cases_api_integration/common/lib/api/case.ts +++ b/x-pack/test/cases_api_integration/common/lib/api/case.ts @@ -106,7 +106,7 @@ export const updateCaseStatus = async ({ }: { supertest: SuperTest.Agent; caseId: string; - version?: string; + version: string; status?: CaseStatuses; expectedHttpCode?: number; auth?: { user: User; space: string | null }; From 4b16c26ec6e2d2a8251d391c5f1bda676dd19c74 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Wed, 29 Jan 2025 16:12:38 -0500 Subject: [PATCH 29/32] Feature definition pr feedback --- .../platform/plugins/shared/cases/server/features/v1.ts | 7 ++++++- .../platform/plugins/shared/cases/server/features/v2.ts | 9 +++++++-- .../platform/plugins/shared/cases/server/features/v3.ts | 2 -- .../plugins/observability/server/features/cases_v1.ts | 7 ++++++- .../plugins/observability/server/features/cases_v2.ts | 9 +++++++-- .../plugins/observability/server/features/cases_v3.ts | 2 -- .../features/src/cases/v1_features/kibana_features.ts | 7 ++++++- .../features/src/cases/v2_features/kibana_features.ts | 9 +++++++-- .../features/src/cases/v3_features/kibana_features.ts | 2 -- 9 files changed, 39 insertions(+), 15 deletions(-) diff --git a/x-pack/platform/plugins/shared/cases/server/features/v1.ts b/x-pack/platform/plugins/shared/cases/server/features/v1.ts index 34130aaa0f024..30ffe7d1f015e 100644 --- a/x-pack/platform/plugins/shared/cases/server/features/v1.ts +++ b/x-pack/platform/plugins/shared/cases/server/features/v1.ts @@ -70,7 +70,12 @@ export const getV1 = (): KibanaFeatureConfig => { all: [...filesSavedObjectTypes], read: [...filesSavedObjectTypes], }, - ui: capabilities.all, + ui: [ + ...capabilities.all, + ...capabilities.createComment, + ...capabilities.reopenCase, + ...capabilities.assignCase, + ], replacedBy: { default: [{ feature: FEATURE_ID_V3, privileges: ['all'] }], minimal: [ diff --git a/x-pack/platform/plugins/shared/cases/server/features/v2.ts b/x-pack/platform/plugins/shared/cases/server/features/v2.ts index cb5010082d053..ba562d79cda73 100644 --- a/x-pack/platform/plugins/shared/cases/server/features/v2.ts +++ b/x-pack/platform/plugins/shared/cases/server/features/v2.ts @@ -75,13 +75,18 @@ export const getV2 = (): KibanaFeatureConfig => { all: [...filesSavedObjectTypes], read: [...filesSavedObjectTypes], }, - ui: capabilities.all, + ui: [ + ...capabilities.all, + ...capabilities.createComment, + ...capabilities.reopenCase, + ...capabilities.assignCase, + ], replacedBy: { default: [{ feature: FEATURE_ID_V3, privileges: ['all'] }], minimal: [ { feature: FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], + privileges: ['minimal_all', 'cases_assign'], }, ], }, diff --git a/x-pack/platform/plugins/shared/cases/server/features/v3.ts b/x-pack/platform/plugins/shared/cases/server/features/v3.ts index b90a4712b7539..732fd837468c0 100644 --- a/x-pack/platform/plugins/shared/cases/server/features/v3.ts +++ b/x-pack/platform/plugins/shared/cases/server/features/v3.ts @@ -55,8 +55,6 @@ export const getV3 = (): KibanaFeatureConfig => { read: [APP_ID], update: [APP_ID], push: [APP_ID], - createComment: [APP_ID], - reopenCase: [APP_ID], }, management: { insightsAndAlerting: [APP_ID], diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v1.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v1.ts index 4c970251cce8a..8a111035173af 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v1.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v1.ts @@ -58,7 +58,12 @@ export const getCasesFeature = ( all: [...filesSavedObjectTypes], read: [...filesSavedObjectTypes], }, - ui: casesCapabilities.all, + ui: [ + ...casesCapabilities.all, + ...casesCapabilities.createComment, + ...casesCapabilities.reopenCase, + ...casesCapabilities.assignCase, + ], replacedBy: { default: [{ feature: casesFeatureIdV3, privileges: ['all'] }], minimal: [ diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts index 4cf2930e7027a..acf0a3855719d 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts @@ -58,13 +58,18 @@ export const getCasesFeatureV2 = ( all: [...filesSavedObjectTypes], read: [...filesSavedObjectTypes], }, - ui: casesCapabilities.all, + ui: [ + ...casesCapabilities.all, + ...casesCapabilities.createComment, + ...casesCapabilities.reopenCase, + ...casesCapabilities.assignCase, + ], replacedBy: { default: [{ feature: casesFeatureIdV3, privileges: ['all'] }], minimal: [ { feature: casesFeatureIdV3, - privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], + privileges: ['minimal_all', 'cases_assign'], }, ], }, diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts index a6af796653ed8..fad40422f0baa 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v3.ts @@ -36,8 +36,6 @@ export const getCasesFeatureV3 = ( read: [observabilityFeatureId], update: [observabilityFeatureId], push: [observabilityFeatureId], - createComment: [observabilityFeatureId], - reopenCase: [observabilityFeatureId], }, savedObject: { all: [...filesSavedObjectTypes], diff --git a/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts index 98cdd2eed1e67..8721aebdd858a 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v1_features/kibana_features.ts @@ -66,7 +66,12 @@ export const getCasesBaseKibanaFeature = ({ all: [...savedObjects.files], read: [...savedObjects.files], }, - ui: uiCapabilities.all, + ui: [ + ...uiCapabilities.all, + ...uiCapabilities.createComment, + ...uiCapabilities.reopenCase, + ...uiCapabilities.assignCase, + ], replacedBy: { default: [{ feature: CASES_FEATURE_ID_V3, privileges: ['all'] }], minimal: [ diff --git a/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts index f5126e277e665..e954422695ad3 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts @@ -68,13 +68,18 @@ export const getCasesBaseKibanaFeatureV2 = ({ all: [...savedObjects.files], read: [...savedObjects.files], }, - ui: uiCapabilities.all, + ui: [ + ...uiCapabilities.all, + ...uiCapabilities.createComment, + ...uiCapabilities.reopenCase, + ...uiCapabilities.assignCase, + ], replacedBy: { default: [{ feature: CASES_FEATURE_ID_V3, privileges: ['all'] }], minimal: [ { feature: CASES_FEATURE_ID_V3, - privileges: ['minimal_all', 'create_comment', 'case_reopen', 'cases_assign'], + privileges: ['minimal_all', 'cases_assign'], }, ], }, diff --git a/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_features.ts index c53b236a61b78..c9a08ebb8614d 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v3_features/kibana_features.ts @@ -42,8 +42,6 @@ export const getCasesBaseKibanaFeatureV3 = ({ read: [APP_ID], update: [APP_ID], push: [APP_ID], - createComment: [APP_ID], - reopenCase: [APP_ID], }, savedObject: { all: [...savedObjects.files], From d124d4a43fbf6f5eb8c0792794551b80a48f67a6 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Wed, 29 Jan 2025 17:58:40 -0500 Subject: [PATCH 30/32] More feature definition refinement --- .../platform/plugins/shared/cases/server/features/v2.ts | 9 +-------- .../plugins/observability/server/features/cases_v2.ts | 9 +-------- .../features/src/cases/v2_features/kibana_features.ts | 9 +-------- 3 files changed, 3 insertions(+), 24 deletions(-) diff --git a/x-pack/platform/plugins/shared/cases/server/features/v2.ts b/x-pack/platform/plugins/shared/cases/server/features/v2.ts index ba562d79cda73..7eb48a9c55508 100644 --- a/x-pack/platform/plugins/shared/cases/server/features/v2.ts +++ b/x-pack/platform/plugins/shared/cases/server/features/v2.ts @@ -64,8 +64,6 @@ export const getV2 = (): KibanaFeatureConfig => { read: [APP_ID], update: [APP_ID], push: [APP_ID], - createComment: [APP_ID], - reopenCase: [APP_ID], assign: [APP_ID], }, management: { @@ -75,12 +73,7 @@ export const getV2 = (): KibanaFeatureConfig => { all: [...filesSavedObjectTypes], read: [...filesSavedObjectTypes], }, - ui: [ - ...capabilities.all, - ...capabilities.createComment, - ...capabilities.reopenCase, - ...capabilities.assignCase, - ], + ui: [...capabilities.all, ...capabilities.assignCase], replacedBy: { default: [{ feature: FEATURE_ID_V3, privileges: ['all'] }], minimal: [ diff --git a/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts b/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts index acf0a3855719d..9f3e899f5ff6e 100644 --- a/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts +++ b/x-pack/solutions/observability/plugins/observability/server/features/cases_v2.ts @@ -50,20 +50,13 @@ export const getCasesFeatureV2 = ( read: [observabilityFeatureId], update: [observabilityFeatureId], push: [observabilityFeatureId], - createComment: [observabilityFeatureId], - reopenCase: [observabilityFeatureId], assign: [observabilityFeatureId], }, savedObject: { all: [...filesSavedObjectTypes], read: [...filesSavedObjectTypes], }, - ui: [ - ...casesCapabilities.all, - ...casesCapabilities.createComment, - ...casesCapabilities.reopenCase, - ...casesCapabilities.assignCase, - ], + ui: [...casesCapabilities.all, ...casesCapabilities.assignCase], replacedBy: { default: [{ feature: casesFeatureIdV3, privileges: ['all'] }], minimal: [ diff --git a/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts b/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts index e954422695ad3..8588712e1de38 100644 --- a/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts +++ b/x-pack/solutions/security/packages/features/src/cases/v2_features/kibana_features.ts @@ -60,20 +60,13 @@ export const getCasesBaseKibanaFeatureV2 = ({ read: [APP_ID], update: [APP_ID], push: [APP_ID], - createComment: [APP_ID], - reopenCase: [APP_ID], assign: [APP_ID], }, savedObject: { all: [...savedObjects.files], read: [...savedObjects.files], }, - ui: [ - ...uiCapabilities.all, - ...uiCapabilities.createComment, - ...uiCapabilities.reopenCase, - ...uiCapabilities.assignCase, - ], + ui: [...uiCapabilities.all, ...uiCapabilities.assignCase], replacedBy: { default: [{ feature: CASES_FEATURE_ID_V3, privileges: ['all'] }], minimal: [ From b9a8de8ccf04ed54176e0882f6b16a9112769211 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Wed, 29 Jan 2025 19:32:02 -0500 Subject: [PATCH 31/32] Update new snapshots --- .../features/server/__snapshots__/oss_features.test.ts.snap | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/x-pack/platform/plugins/shared/features/server/__snapshots__/oss_features.test.ts.snap b/x-pack/platform/plugins/shared/features/server/__snapshots__/oss_features.test.ts.snap index 5001fb6636251..7dc313ec8c079 100644 --- a/x-pack/platform/plugins/shared/features/server/__snapshots__/oss_features.test.ts.snap +++ b/x-pack/platform/plugins/shared/features/server/__snapshots__/oss_features.test.ts.snap @@ -1254,6 +1254,7 @@ Array [ ], "cases": Object { "all": Array [], + "assign": Array [], "create": Array [], "createComment": Array [], "delete": Array [], @@ -1566,6 +1567,7 @@ Array [ ], "cases": Object { "all": Array [], + "assign": Array [], "create": Array [], "createComment": Array [], "delete": Array [], @@ -2069,6 +2071,7 @@ Array [ ], "cases": Object { "all": Array [], + "assign": Array [], "create": Array [], "createComment": Array [], "delete": Array [], @@ -2381,6 +2384,7 @@ Array [ ], "cases": Object { "all": Array [], + "assign": Array [], "create": Array [], "createComment": Array [], "delete": Array [], @@ -2693,6 +2697,7 @@ Array [ ], "cases": Object { "all": Array [], + "assign": Array [], "create": Array [], "createComment": Array [], "delete": Array [], @@ -3196,6 +3201,7 @@ Array [ ], "cases": Object { "all": Array [], + "assign": Array [], "create": Array [], "createComment": Array [], "delete": Array [], From e9d4f337a3ff106fe84a0f3157a73425b93eab2c Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Wed, 29 Jan 2025 20:48:37 -0500 Subject: [PATCH 32/32] Add new subfeatures to feature privileges tests --- .../feature_privilege_iterator.test.ts | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/x-pack/platform/plugins/shared/features/server/feature_privilege_iterator/feature_privilege_iterator.test.ts b/x-pack/platform/plugins/shared/features/server/feature_privilege_iterator/feature_privilege_iterator.test.ts index 977c6a1eda5fc..fba4b8cf8b54a 100644 --- a/x-pack/platform/plugins/shared/features/server/feature_privilege_iterator/feature_privilege_iterator.test.ts +++ b/x-pack/platform/plugins/shared/features/server/feature_privilege_iterator/feature_privilege_iterator.test.ts @@ -80,6 +80,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], + assign: ['cases-assign-type'], }, ui: ['ui-action'], }, @@ -152,6 +153,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], + assign: ['cases-assign-type'], }, ui: ['ui-action'], }, @@ -223,6 +225,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], + assign: ['cases-assign-type'], }, ui: ['ui-action'], }, @@ -296,6 +299,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], + assign: ['cases-assign-type'], }, ui: ['ui-action'], }, @@ -339,6 +343,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], + assign: ['cases-assign-type'], }, ui: ['ui-action'], }, @@ -403,6 +408,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-sub-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], + assign: ['cases-assign-type'], }, ui: ['ui-sub-type'], }, @@ -452,6 +458,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], + assign: ['cases-assign-type'], }, ui: ['ui-action'], }, @@ -522,6 +529,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], + assign: ['cases-assign-type'], }, ui: ['ui-action'], }, @@ -586,6 +594,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-sub-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], + assign: ['cases-assign-type'], }, ui: ['ui-sub-type'], }, @@ -635,6 +644,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], + assign: ['cases-assign-type'], }, ui: ['ui-action'], }, @@ -705,6 +715,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], + assign: ['cases-assign-type'], }, ui: ['ui-action'], }, @@ -770,6 +781,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-sub-type'], createComment: ['cases-create-comment-sub-type'], reopenCase: ['cases-reopen-sub-type'], + assign: ['cases-assign-sub-type'], }, ui: ['ui-sub-type'], }, @@ -822,7 +834,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type', 'cases-settings-sub-type'], createComment: ['cases-create-comment-type', 'cases-create-comment-sub-type'], reopenCase: ['cases-reopen-type', 'cases-reopen-sub-type'], - assign: [], + assign: ['cases-assign-type', 'cases-assign-sub-type'], }, ui: ['ui-action', 'ui-sub-type'], }, @@ -861,7 +873,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-sub-type'], createComment: ['cases-create-comment-sub-type'], reopenCase: ['cases-reopen-sub-type'], - assign: [], + assign: ['cases-assign-sub-type'], }, ui: ['ui-action', 'ui-sub-type'], }, @@ -907,6 +919,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], + assign: ['cases-assign-type'], }, ui: ['ui-action'], }, @@ -1014,7 +1027,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], - assign: [], + assign: ['cases-assign-type'], }, ui: ['ui-action'], }, @@ -1096,6 +1109,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type'], createComment: ['cases-create-comment-type'], reopenCase: ['cases-reopen-type'], + assign: ['cases-assign-type'], }, ui: ['ui-action'], }, @@ -1161,6 +1175,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-sub-type'], createComment: ['cases-create-comment-sub-type'], reopenCase: ['cases-reopen-sub-type'], + assign: ['cases-assign-sub-type'], }, ui: ['ui-sub-type'], }, @@ -1213,7 +1228,7 @@ describe('featurePrivilegeIterator', () => { settings: ['cases-settings-type', 'cases-settings-sub-type'], createComment: ['cases-create-comment-type', 'cases-create-comment-sub-type'], reopenCase: ['cases-reopen-type', 'cases-reopen-sub-type'], - assign: [], + assign: ['cases-assign-type', 'cases-assign-sub-type'], }, ui: ['ui-action', 'ui-sub-type'], },