Skip to content

Commit

Permalink
[Cases] Cases assignees sub feature (elastic#201654)
Browse files Browse the repository at this point in the history
## Summary

This pr implements a new cases assignee sub-feature, allowing users to
control a role's ability to change the assignee of a case. With the
permission enabled, they can assign any user to any case, with it
disabled, the assignees component is hidden.

Read only + enabled:

![image](https://github.com/user-attachments/assets/ba421784-d976-4ae9-a399-e404c26b3842)


All + assign disabled:

![image](https://github.com/user-attachments/assets/d835b6f9-5a14-4ae0-abed-b3c3252c2692)



### Checklist

- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
kqualters-elastic and elasticmachine authored Jan 30, 2025
1 parent 53cba30 commit 0e7c608
Show file tree
Hide file tree
Showing 105 changed files with 2,118 additions and 200 deletions.

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

Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -46,6 +47,7 @@ const allOperations = [
...settingsOperations,
...createCommentOperations,
...reopenOperations,
...assignOperations,
] as const;

export class FeaturePrivilegeCasesBuilder extends BaseFeaturePrivilegeBuilder {
Expand All @@ -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?.assign),
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,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 = 'cases_assign' as const;

/**
* Cases API Tags
Expand Down
1 change: 1 addition & 0 deletions x-pack/platform/plugins/shared/cases/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export {
CASES_SETTINGS_CAPABILITY,
CREATE_COMMENT_CAPABILITY,
CASES_REOPEN_CAPABILITY,
ASSIGN_CASE_CAPABILITY,
} from './constants';

export type { AttachmentAttributes } from './types/domain';
Expand Down
3 changes: 3 additions & 0 deletions x-pack/platform/plugins/shared/cases/common/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
UPDATE_CASES_CAPABILITY,
CREATE_COMMENT_CAPABILITY,
CASES_REOPEN_CAPABILITY,
ASSIGN_CASE_CAPABILITY,
} from '..';
import type {
CASES_CONNECTORS_CAPABILITY,
Expand Down Expand Up @@ -325,6 +326,7 @@ export interface CasesPermissions {
settings: boolean;
reopenCase: boolean;
createComment: boolean;
assign: boolean;
}

export interface CasesCapabilities {
Expand All @@ -337,4 +339,5 @@ export interface CasesCapabilities {
[CASES_SETTINGS_CAPABILITY]: boolean;
[CREATE_COMMENT_CAPABILITY]: boolean;
[CASES_REOPEN_CAPABILITY]: boolean;
[ASSIGN_CASE_CAPABILITY]: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ describe('createUICapabilities', () => {
"push_cases",
"cases_connectors",
],
"assignCase": Array [
"cases_assign",
],
"createComment": Array [
"create_comment",
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
CASES_SETTINGS_CAPABILITY,
CASES_REOPEN_CAPABILITY,
CREATE_COMMENT_CAPABILITY,
ASSIGN_CASE_CAPABILITY,
} from '../constants';

export interface CasesUiCapabilities {
Expand All @@ -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
Expand All @@ -42,4 +44,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,
});
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -32,9 +32,9 @@ export const canUseCases =
owners: CasesOwners[] = [OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER, GENERAL_CASES_OWNER]
): CasesPermissions => {
const aggregatedPermissions = owners.reduce<CasesPermissions>(
// eslint-disable-next-line complexity
(acc, owner) => {
const userCapabilitiesForOwner = getUICapabilities(capabilities[getFeatureID(owner)]);

acc.create = acc.create || userCapabilitiesForOwner.create;
acc.read = acc.read || userCapabilitiesForOwner.read;
acc.update = acc.update || userCapabilitiesForOwner.update;
Expand All @@ -44,6 +44,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;

const allFromAcc =
acc.create &&
Expand All @@ -54,7 +55,8 @@ export const canUseCases =
acc.connectors &&
acc.settings &&
acc.reopenCase &&
acc.createComment;
acc.createComment &&
acc.assign;

acc.all = acc.all || userCapabilitiesForOwner.all || allFromAcc;

Expand All @@ -71,6 +73,7 @@ export const canUseCases =
settings: false,
reopenCase: false,
createComment: false,
assign: false,
}
);

Expand All @@ -81,8 +84,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`;
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ describe('getUICapabilities', () => {
expect(getUICapabilities(undefined)).toMatchInlineSnapshot(`
Object {
"all": false,
"assign": false,
"connectors": false,
"create": false,
"createComment": false,
Expand All @@ -29,6 +30,7 @@ describe('getUICapabilities', () => {
expect(getUICapabilities()).toMatchInlineSnapshot(`
Object {
"all": false,
"assign": false,
"connectors": false,
"create": false,
"createComment": false,
Expand All @@ -46,6 +48,7 @@ describe('getUICapabilities', () => {
expect(getUICapabilities({ create_cases: true })).toMatchInlineSnapshot(`
Object {
"all": false,
"assign": false,
"connectors": false,
"create": true,
"createComment": false,
Expand All @@ -72,6 +75,7 @@ describe('getUICapabilities', () => {
).toMatchInlineSnapshot(`
Object {
"all": false,
"assign": false,
"connectors": false,
"create": false,
"createComment": false,
Expand All @@ -89,6 +93,7 @@ describe('getUICapabilities', () => {
expect(getUICapabilities({})).toMatchInlineSnapshot(`
Object {
"all": false,
"assign": false,
"connectors": false,
"create": false,
"createComment": false,
Expand All @@ -115,6 +120,7 @@ describe('getUICapabilities', () => {
).toMatchInlineSnapshot(`
Object {
"all": false,
"assign": false,
"connectors": true,
"create": false,
"createComment": false,
Expand Down Expand Up @@ -142,6 +148,7 @@ describe('getUICapabilities', () => {
).toMatchInlineSnapshot(`
Object {
"all": false,
"assign": false,
"connectors": false,
"create": true,
"createComment": false,
Expand Down Expand Up @@ -169,6 +176,7 @@ describe('getUICapabilities', () => {
).toMatchInlineSnapshot(`
Object {
"all": false,
"assign": false,
"connectors": true,
"create": true,
"createComment": false,
Expand All @@ -186,6 +194,7 @@ describe('getUICapabilities', () => {
expect(getUICapabilities({ cases_settings: true })).toMatchInlineSnapshot(`
Object {
"all": false,
"assign": false,
"connectors": false,
"create": false,
"createComment": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
UPDATE_CASES_CAPABILITY,
CASES_REOPEN_CAPABILITY,
CREATE_COMMENT_CAPABILITY,
ASSIGN_CASE_CAPABILITY,
} from '../../../common/constants';

export const getUICapabilities = (
Expand All @@ -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 assignCases = !!featureCapabilities?.[ASSIGN_CASE_CAPABILITY];

const all =
create &&
Expand All @@ -40,7 +42,8 @@ export const getUICapabilities = (
connectors &&
settings &&
reopenCase &&
createComment;
createComment &&
assignCases;

return {
all,
Expand All @@ -53,5 +56,6 @@ export const getUICapabilities = (
settings,
reopenCase,
createComment,
assign: assignCases,
};
};
Loading

0 comments on commit 0e7c608

Please sign in to comment.